<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          死磕 36 個(gè) JS 手寫(xiě)題(搞懂后,提升真的大)

          共 80425字,需瀏覽 161分鐘

           ·

          2021-04-01 15:56

          這是布蘭的第 22 篇原創(chuàng)

          為什么要寫(xiě)這類(lèi)文章

          作為一個(gè)程序員,代碼能力毋庸置疑是非常非常重要的,就像現(xiàn)在為什么大廠面試基本都問(wèn)什么 API 怎么實(shí)現(xiàn)可見(jiàn)其重要性。我想說(shuō)的是居然手寫(xiě)這么重要,那我們就必須掌握它,所以文章標(biāo)題用了死磕,一點(diǎn)也不過(guò)分,也希望不被認(rèn)為是標(biāo)題黨。

          作為一個(gè)普通前端,我是真的寫(xiě)不出 Promise A+ 規(guī)范,但是沒(méi)關(guān)系,我們可以站在巨人的肩膀上,要相信我們現(xiàn)在要走的路,前人都走過(guò),所以可以找找現(xiàn)在社區(qū)已經(jīng)存在的那些優(yōu)秀的文章,比如工業(yè)聚大佬寫(xiě)的 100 行代碼實(shí)現(xiàn) Promises/A+ 規(guī)范,找到這些文章后不是收藏夾吃灰,得找個(gè)時(shí)間踏踏實(shí)實(shí)的學(xué),一行一行的磨,直到搞懂為止。我現(xiàn)在就是這么干的。

          能收獲什么

          這篇文章總體上分為 2 類(lèi)手寫(xiě)題,前半部分可以歸納為是常見(jiàn)需求,后半部分則是對(duì)現(xiàn)有技術(shù)的實(shí)現(xiàn);

          • 對(duì)常用的需求進(jìn)行手寫(xiě)實(shí)現(xiàn),比如數(shù)據(jù)類(lèi)型判斷函數(shù)、深拷貝等可以直接用于往后的項(xiàng)目中,提高了項(xiàng)目開(kāi)發(fā)效率;
          • 對(duì)現(xiàn)有關(guān)鍵字和 API 的實(shí)現(xiàn),可能需要用到別的知識(shí)或 API,比如在寫(xiě) forEach 的時(shí)候用到了無(wú)符號(hào)位右移的操作,平時(shí)都不怎么能夠接觸到這玩意,現(xiàn)在遇到了就可以順手把它掌握了。所以手寫(xiě)這些實(shí)現(xiàn)能夠潛移默化的擴(kuò)展并鞏固自己的 JS 基礎(chǔ);
          • 通過(guò)寫(xiě)各種測(cè)試用例,你會(huì)知道各種 API 的邊界情況,比如 Promise.all, 你得考慮到傳入?yún)?shù)的各種情況,從而加深了對(duì)它們的理解及使用;

          閱讀的時(shí)候需要做什么

          閱讀的時(shí)候,你需要把每行代碼都看懂,知道它在干什么,為什么要這么寫(xiě),能寫(xiě)得更好嘛?比如在寫(xiě)圖片懶加載的時(shí)候,一般我們都是根據(jù)當(dāng)前元素的位置和視口進(jìn)行判斷是否要加載這張圖片,普通程序員寫(xiě)到這就差不多完成了。而大佬程序員則是會(huì)多考慮一些細(xì)節(jié)的東西,比如性能如何更優(yōu)?代碼如何更精簡(jiǎn)?比如 yeyan1996 寫(xiě)的圖片懶加載就多考慮了 2 點(diǎn):比如圖片全部加載完成的時(shí)候得把事件監(jiān)聽(tīng)給移除;比如加載完一張圖片的時(shí)候,得把當(dāng)前 img 從 imgList 里移除,起到優(yōu)化內(nèi)存的作用。

          除了讀通代碼之外,還可以打開(kāi) Chrome 的 Script snippet 去寫(xiě)測(cè)試用例跑跑代碼,做到更好的理解以及使用。

          在看了幾篇以及寫(xiě)了很多測(cè)試用例的前提下,嘗試自己手寫(xiě)實(shí)現(xiàn),看看自己到底掌握了多少。條條大路通羅馬,你還能有別的方式實(shí)現(xiàn)嘛?或者你能寫(xiě)得比別人更好嘛?

          好了,還楞著干啥,開(kāi)始干活。

          數(shù)據(jù)類(lèi)型判斷

          typeof 可以正確識(shí)別:Undefined、Boolean、Number、String、Symbol、Function 等類(lèi)型的數(shù)據(jù),但是對(duì)于其他的都會(huì)認(rèn)為是 object,比如 Null、Date 等,所以通過(guò) typeof 來(lái)判斷數(shù)據(jù)類(lèi)型會(huì)不準(zhǔn)確。但是可以使用 Object.prototype.toString 實(shí)現(xiàn)。

          function typeOf(obj{
              let res = Object.prototype.toString.call(obj).split(' ')[1]
              res = res.substring(0, res.length - 1).toLowerCase()
              return res
          }
          typeOf([])        // 'array'
          typeOf({})        // 'object'
          typeOf(new Date)  // 'date'

          繼承

          原型鏈繼承

          function Animal({
              this.colors = ['black''white']
          }
          Animal.prototype.getColor = function({
              return this.colors
          }
          function Dog({}
          Dog.prototype =  new Animal()

          let dog1 = new Dog()
          dog1.colors.push('brown')
          let dog2 = new Dog()
          console.log(dog2.colors)  // ['black', 'white', 'brown']

          原型鏈繼承存在的問(wèn)題:

          • 問(wèn)題1:原型中包含的引用類(lèi)型屬性將被所有實(shí)例共享;
          • 問(wèn)題2:子類(lèi)在實(shí)例化的時(shí)候不能給父類(lèi)構(gòu)造函數(shù)傳參;

          借用構(gòu)造函數(shù)實(shí)現(xiàn)繼承

          function Animal(name{
              this.name = name
              this.getName = function({
                  return this.name
              }
          }
          function Dog(name{
              Animal.call(this, name)
          }
          Dog.prototype =  new Animal()

          借用構(gòu)造函數(shù)實(shí)現(xiàn)繼承解決了原型鏈繼承的 2 個(gè)問(wèn)題:引用類(lèi)型共享問(wèn)題以及傳參問(wèn)題。但是由于方法必須定義在構(gòu)造函數(shù)中,所以會(huì)導(dǎo)致每次創(chuàng)建子類(lèi)實(shí)例都會(huì)創(chuàng)建一遍方法。

          組合繼承

          組合繼承結(jié)合了原型鏈和盜用構(gòu)造函數(shù),將兩者的優(yōu)點(diǎn)集中了起來(lái)。基本的思路是使用原型鏈繼承原型上的屬性和方法,而通過(guò)盜用構(gòu)造函數(shù)繼承實(shí)例屬性。這樣既可以把方法定義在原型上以實(shí)現(xiàn)重用,又可以讓每個(gè)實(shí)例都有自己的屬性。

          function Animal(name{
              this.name = name
              this.colors = ['black''white']
          }
          Animal.prototype.getName = function({
              return this.name
          }
          function Dog(name, age{
              Animal.call(this, name)
              this.age = age
          }
          Dog.prototype =  new Animal()
          Dog.prototype.constructor = Dog

          let dog1 = new Dog('奶昔'2)
          dog1.colors.push('brown')
          let dog2 = new Dog('哈赤'1)
          console.log(dog2) 
          // { name: "哈赤", colors: ["black", "white"], age: 1 }

          寄生式組合繼承

          組合繼承已經(jīng)相對(duì)完善了,但還是存在問(wèn)題,它的問(wèn)題就是調(diào)用了 2 次父類(lèi)構(gòu)造函數(shù),第一次是在 new Animal(),第二次是在 Animal.call() 這里。

          所以解決方案就是不直接調(diào)用父類(lèi)構(gòu)造函數(shù)給子類(lèi)原型賦值,而是通過(guò)創(chuàng)建空函數(shù) F 獲取父類(lèi)原型的副本。

          寄生式組合繼承寫(xiě)法上和組合繼承基本類(lèi)似,區(qū)別是如下這里:

          - Dog.prototype =  new Animal()
          - Dog.prototype.constructor = Dog

          + function F() {}
          + F.prototype = Animal.prototype
          + let f = new F()
          + f.constructor = Dog
          + Dog.prototype = f

          稍微封裝下上面添加的代碼后:

          function object(o{
              function F({}
              F.prototype = o
              return new F()
          }
          function inheritPrototype(child, parent{
              let prototype = object(parent.prototype)
              prototype.constructor = child
              child.prototype = prototype
          }
          inheritPrototype(Dog, Animal)

          如果你嫌棄上面的代碼太多了,還可以基于組合繼承的代碼改成最簡(jiǎn)單的寄生式組合繼承:

          - Dog.prototype =  new Animal()
          - Dog.prototype.constructor = Dog

          + Dog.prototype =  Object.create(Animal.prototype)
          + Dog.prototype.constructor = Dog

          class 實(shí)現(xiàn)繼承

          class Animal {
              constructor(name) {
                  this.name = name
              } 
              getName() {
                  return this.name
              }
          }
          class Dog extends Animal {
              constructor(name, age) {
                  super(name)
                  this.age = age
              }
          }

          數(shù)組去重

          ES5 實(shí)現(xiàn):

          function unique(arr{
              var res = arr.filter(function(item, index, array{
                  return array.indexOf(item) === index
              })
              return res
          }

          ES6 實(shí)現(xiàn):

          var unique = arr => [...new Set(arr)]

          數(shù)組扁平化

          數(shù)組扁平化就是將 [1, [2, [3]]] 這種多層的數(shù)組拍平成一層 [1, 2, 3]。使用 Array.prototype.flat 可以直接將多層數(shù)組拍平成一層:

          [1, [2, [3]]].flat(2)  // [1, 2, 3]

          現(xiàn)在就是要實(shí)現(xiàn) flat 這種效果。

          ES5 實(shí)現(xiàn):遞歸。

          function flatten(arr{
              var result = [];
              for (var i = 0, len = arr.length; i < len; i++) {
                  if (Array.isArray(arr[i])) {
                      result = result.concat(flatten(arr[i]))
                  } else {
                      result.push(arr[i])
                  }
              }
              return result;
          }

          ES6 實(shí)現(xiàn):

          function flatten(arr{
              while (arr.some(item => Array.isArray(item))) {
                  arr = [].concat(...arr);
              }
              return arr;
          }

          深淺拷貝

          淺拷貝:只考慮對(duì)象類(lèi)型。

          function shallowCopy(obj{
              if (typeof obj !== 'object'return
              
              let newObj = obj instanceof Array ? [] : {}
              for (let key in obj) {
                  if (obj.hasOwnProperty(key)) {
                      newObj[key] = obj[key]
                  }
              }
              return newObj
          }

          簡(jiǎn)單版深拷貝:只考慮普通對(duì)象屬性,不考慮內(nèi)置對(duì)象和函數(shù)。

          function deepClone(obj{
              if (typeof obj !== 'object'return;
              var newObj = obj instanceof Array ? [] : {};
              for (var key in obj) {
                  if (obj.hasOwnProperty(key)) {
                      newObj[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key];
                  }
              }
              return newObj;
          }

          復(fù)雜版深克隆:基于簡(jiǎn)單版的基礎(chǔ)上,還考慮了內(nèi)置對(duì)象比如 Date、RegExp 等對(duì)象和函數(shù)以及解決了循環(huán)引用的問(wèn)題。

          const isObject = (target) => (typeof target === "object" || typeof target === "function") && target !== null;

          function deepClone(target, map = new WeakMap()) {
              if (map.get(target)) {
                  return target;
              }
              // 獲取當(dāng)前值的構(gòu)造函數(shù):獲取它的類(lèi)型
              let constructor = target.constructor;
              // 檢測(cè)當(dāng)前對(duì)象target是否與正則、日期格式對(duì)象匹配
              if (/^(RegExp|Date)$/i.test(constructor.name)) {
                  // 創(chuàng)建一個(gè)新的特殊對(duì)象(正則類(lèi)/日期類(lèi))的實(shí)例
                  return new constructor(target);  
              }
              if (isObject(target)) {
                  map.set(target, true);  // 為循環(huán)引用的對(duì)象做標(biāo)記
                  const cloneTarget = Array.isArray(target) ? [] : {};
                  for (let prop in target) {
                      if (target.hasOwnProperty(prop)) {
                          cloneTarget[prop] = deepClone(target[prop], map);
                      }
                  }
                  return cloneTarget;
              } else {
                  return target;
              }
          }

          事件總線(xiàn)(發(fā)布訂閱模式)

          class EventEmitter {
              constructor() {
                  this.cache = {}
              }
              on(name, fn) {
                  if (this.cache[name]) {
                      this.cache[name].push(fn)
                  } else {
                      this.cache[name] = [fn]
                  }
              }
              off(name, fn) {
                  let tasks = this.cache[name]
                  if (tasks) {
                      const index = tasks.findIndex(f => f === fn || f.callback === fn)
                      if (index >= 0) {
                          tasks.splice(index, 1)
                      }
                  }
              }
              emit(name, once = false, ...args) {
                  if (this.cache[name]) {
                      // 創(chuàng)建副本,如果回調(diào)函數(shù)內(nèi)繼續(xù)注冊(cè)相同事件,會(huì)造成死循環(huán)
                      let tasks = this.cache[name].slice()
                      for (let fn of tasks) {
                          fn(...args)
                      }
                      if (once) {
                          delete this.cache[name]
                      }
                  }
              }
          }

          // 測(cè)試
          let eventBus = new EventEmitter()
          let fn1 = function(name, age{
              console.log(`${name} ${age}`)
          }
          let fn2 = function(name, age{
              console.log(`hello, ${name} ${age}`)
          }
          eventBus.on('aaa', fn1)
          eventBus.on('aaa', fn2)
          eventBus.emit('aaa'false'布蘭'12)
          // '布蘭 12'
          // 'hello, 布蘭 12'

          解析 URL 參數(shù)為對(duì)象

          function parseParam(url{
              const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 將 ? 后面的字符串取出來(lái)
              const paramsArr = paramsStr.split('&'); // 將字符串以 & 分割后存到數(shù)組中
              let paramsObj = {};
              // 將 params 存到對(duì)象中
              paramsArr.forEach(param => {
                  if (/=/.test(param)) { // 處理有 value 的參數(shù)
                      let [key, val] = param.split('='); // 分割 key 和 value
                      val = decodeURIComponent(val); // 解碼
                      val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判斷是否轉(zhuǎn)為數(shù)字
              
                      if (paramsObj.hasOwnProperty(key)) { // 如果對(duì)象有 key,則添加一個(gè)值
                          paramsObj[key] = [].concat(paramsObj[key], val);
                      } else { // 如果對(duì)象沒(méi)有這個(gè) key,創(chuàng)建 key 并設(shè)置值
                          paramsObj[key] = val;
                      }
                  } else { // 處理沒(méi)有 value 的參數(shù)
                      paramsObj[param] = true;
                  }
              })
              
              return paramsObj;
          }

          字符串模板

          function render(template, data{
              const reg = /\{\{(\w+)\}\}/// 模板字符串正則
              if (reg.test(template)) { // 判斷模板里是否有模板字符串
                  const name = reg.exec(template)[1]; // 查找當(dāng)前模板里第一個(gè)模板字符串的字段
                  template = template.replace(reg, data[name]); // 將第一個(gè)模板字符串渲染
                  return render(template, data); // 遞歸的渲染并返回渲染后的結(jié)構(gòu)
              }
              return template; // 如果模板沒(méi)有模板字符串直接返回
          }

          測(cè)試:

          let template = '我是{{name}},年齡{{age}},性別{{sex}}';
          let person = {
              name'布蘭',
              age12
          }
          render(template, person); // 我是布蘭,年齡12,性別undefined

          圖片懶加載

          與普通的圖片懶加載不同,如下這個(gè)多做了 2 個(gè)精心處理:

          • 圖片全部加載完成后移除事件監(jiān)聽(tīng);
          • 加載完的圖片,從 imgList 移除;
          let imgList = [...document.querySelectorAll('img')]
          let length = imgList.length

          const imgLazyLoad = function({
              let count = 0
              return function({
                  let deleteIndexList = []
                  imgList.forEach((img, index) => {
                      let rect = img.getBoundingClientRect()
                      if (rect.top < window.innerHeight) {
                          img.src = img.dataset.src
                          deleteIndexList.push(index)
                          count++
                          if (count === length) {
                              document.removeEventListener('scroll', imgLazyLoad)
                          }
                      }
                  })
                  imgList = imgList.filter((img, index) => !deleteIndexList.includes(index))
              }
          }

          // 這里最好加上防抖處理
          document.addEventListener('scroll', imgLazyLoad)

          參考:圖片懶加載[1]

          函數(shù)防抖

          觸發(fā)高頻事件 N 秒后只會(huì)執(zhí)行一次,如果 N 秒內(nèi)事件再次觸發(fā),則會(huì)重新計(jì)時(shí)。

          簡(jiǎn)單版:函數(shù)內(nèi)部支持使用 this 和 event 對(duì)象;

          function debounce(func, wait{
              var timeout;
              return function ({
                  var context = this;
                  var args = arguments;
                  clearTimeout(timeout)
                  timeout = setTimeout(function(){
                      func.apply(context, args)
                  }, wait);
              }
          }

          使用:

          var node = document.getElementById('layout')
          function getUserAction(e{
              console.log(this, e)  // 分別打印:node 這個(gè)節(jié)點(diǎn) 和 MouseEvent
              node.innerHTML = count++;
          };
          node.onmousemove = debounce(getUserAction, 1000)

          最終版:除了支持 this 和 event 外,還支持以下功能:

          • 支持立即執(zhí)行;
          • 函數(shù)可能有返回值;
          • 支持取消功能;
          function debounce(func, wait, immediate{
              var timeout, result;
              
              var debounced = function ({
                  var context = this;
                  var args = arguments;
                  
                  if (timeout) clearTimeout(timeout);
                  if (immediate) {
                      // 如果已經(jīng)執(zhí)行過(guò),不再執(zhí)行
                      var callNow = !timeout;
                      timeout = setTimeout(function(){
                          timeout = null;
                      }, wait)
                      if (callNow) result = func.apply(context, args)
                  } else {
                      timeout = setTimeout(function(){
                          func.apply(context, args)
                      }, wait);
                  }
                  return result;
              };

              debounced.cancel = function({
                  clearTimeout(timeout);
                  timeout = null;
              };

              return debounced;
          }

          使用:

          var setUseAction = debounce(getUserAction, 10000true);
          // 使用防抖
          node.onmousemove = setUseAction

          // 取消防抖
          setUseAction.cancel()

          參考:JavaScript專(zhuān)題之跟著underscore學(xué)防抖

          函數(shù)節(jié)流

          觸發(fā)高頻事件,且 N 秒內(nèi)只執(zhí)行一次。

          簡(jiǎn)單版:使用時(shí)間戳來(lái)實(shí)現(xiàn),立即執(zhí)行一次,然后每 N 秒執(zhí)行一次。

          function throttle(func, wait{
              var context, args;
              var previous = 0;

              return function({
                  var now = +new Date();
                  context = this;
                  args = arguments;
                  if (now - previous > wait) {
                      func.apply(context, args);
                      previous = now;
                  }
              }
          }

          最終版:支持取消節(jié)流;另外通過(guò)傳入第三個(gè)參數(shù),options.leading 來(lái)表示是否可以立即執(zhí)行一次,opitons.trailing 表示結(jié)束調(diào)用的時(shí)候是否還要執(zhí)行一次,默認(rèn)都是 true。注意設(shè)置的時(shí)候不能同時(shí)將 leading 或 trailing 設(shè)置為 false。

          function throttle(func, wait, options{
              var timeout, context, args, result;
              var previous = 0;
              if (!options) options = {};

              var later = function({
                  previous = options.leading === false ? 0 : new Date().getTime();
                  timeout = null;
                  func.apply(context, args);
                  if (!timeout) context = args = null;
              };

              var throttled = function({
                  var now = new Date().getTime();
                  if (!previous && options.leading === false) previous = now;
                  var remaining = wait - (now - previous);
                  context = this;
                  args = arguments;
                  if (remaining <= 0 || remaining > wait) {
                      if (timeout) {
                          clearTimeout(timeout);
                          timeout = null;
                      }
                      previous = now;
                      func.apply(context, args);
                      if (!timeout) context = args = null;
                  } else if (!timeout && options.trailing !== false) {
                      timeout = setTimeout(later, remaining);
                  }
              };
              
              throttled.cancel = function({
                  clearTimeout(timeout);
                  previous = 0;
                  timeout = null;
              }
              return throttled;
          }

          節(jié)流的使用就不拿代碼舉例了,參考防抖的寫(xiě)就行。

          參考:JavaScript專(zhuān)題之跟著 underscore 學(xué)節(jié)流

          函數(shù)柯里化

          什么叫函數(shù)柯里化?其實(shí)就是將使用多個(gè)參數(shù)的函數(shù)轉(zhuǎn)換成一系列使用一個(gè)參數(shù)的函數(shù)的技術(shù)。還不懂?來(lái)舉個(gè)例子。

          function add(a, b, c{
              return a + b + c
          }
          add(123)
          let addCurry = curry(add)
          addCurry(1)(2)(3)

          現(xiàn)在就是要實(shí)現(xiàn) curry 這個(gè)函數(shù),使函數(shù)從一次調(diào)用傳入多個(gè)參數(shù)變成多次調(diào)用每次傳一個(gè)參數(shù)。

          function curry(fn{
              let judge = (...args) => {
                  if (args.length == fn.length) return fn(...args)
                  return (...arg) => judge(...args, ...arg)
              }
              return judge
          }

          偏函數(shù)

          什么是偏函數(shù)?偏函數(shù)就是將一個(gè) n 參的函數(shù)轉(zhuǎn)換成固定 x 參的函數(shù),剩余參數(shù)(n - x)將在下次調(diào)用全部傳入。舉個(gè)例子:

          function add(a, b, c{
              return a + b + c
          }
          let partialAdd = partial(add, 1)
          partialAdd(23)

          發(fā)現(xiàn)沒(méi)有,其實(shí)偏函數(shù)和函數(shù)柯里化有點(diǎn)像,所以根據(jù)函數(shù)柯里化的實(shí)現(xiàn),能夠能很快寫(xiě)出偏函數(shù)的實(shí)現(xiàn):

          function partial(fn, ...args{
              return (...arg) => {
                  return fn(...args, ...arg)
              }
          }

          如上這個(gè)功能比較簡(jiǎn)單,現(xiàn)在我們希望偏函數(shù)能和柯里化一樣能實(shí)現(xiàn)占位功能,比如:

          function clg(a, b, c{
              console.log(a, b, c)
          }
          let partialClg = partial(clg, '_'2)
          partialClg(13)  // 依次打印:1, 2, 3

          _ 占的位其實(shí)就是 1 的位置。相當(dāng)于:partial(clg, 1, 2),然后 partialClg(3)。明白了原理,我們就來(lái)寫(xiě)實(shí)現(xiàn):

          function partial(fn, ...args{
              return (...arg) => {
                  args[index] = 
                  return fn(...args, ...arg)
              }
          }

          JSONP

          JSONP 核心原理:script 標(biāo)簽不受同源策略約束,所以可以用來(lái)進(jìn)行跨域請(qǐng)求,優(yōu)點(diǎn)是兼容性好,但是只能用于 GET 請(qǐng)求;

          const jsonp = ({ url, params, callbackName }) => {
              const generateUrl = () => {
                  let dataSrc = ''
                  for (let key in params) {
                      if (params.hasOwnProperty(key)) {
                          dataSrc += `${key}=${params[key]}&`
                      }
                  }
                  dataSrc += `callback=${callbackName}`
                  return `${url}?${dataSrc}`
              }
              return new Promise((resolve, reject) => {
                  const scriptEle = document.createElement('script')
                  scriptEle.src = generateUrl()
                  document.body.appendChild(scriptEle)
                  window[callbackName] = data => {
                      resolve(data)
                      document.removeChild(scriptEle)
                  }
              })
          }

          AJAX

          const getJSON = function(url{
              return new Promise((resolve, reject) => {
                  const xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Mscrosoft.XMLHttp');
                  xhr.open('GET', url, false);
                  xhr.setRequestHeader('Accept''application/json');
                  xhr.onreadystatechange = function({
                      if (xhr.readyState !== 4return;
                      if (xhr.status === 200 || xhr.status === 304) {
                          resolve(xhr.responseText);
                      } else {
                          reject(new Error(xhr.responseText));
                      }
                  }
                  xhr.send();
              })
          }

          實(shí)現(xiàn)數(shù)組原型方法

          forEach

          Array.prototype.forEach2 = function(callback, thisArg{
              if (this == null) {
                  throw new TypeError('this is null or not defined')
              }
              if (typeof callback !== "function") {
                  throw new TypeError(callback + ' is not a function')
              }
              const O = Object(this)  // this 就是當(dāng)前的數(shù)組
              const len = O.length >>> 0  // 后面有解釋
              let k = 0
              while (k < len) {
                  if (k in O) {
                      callback.call(thisArg, O[k], k, O);
                  }
                  k++;
              }
          }

          參考:forEach#polyfill[2]

          O.length >>> 0 是什么操作?就是無(wú)符號(hào)右移 0 位,那有什么意義嘛?就是為了保證轉(zhuǎn)換后的值為正整數(shù)。其實(shí)底層做了 2 層轉(zhuǎn)換,第一是非 number 轉(zhuǎn)成 number 類(lèi)型,第二是將 number 轉(zhuǎn)成 Uint32 類(lèi)型。感興趣可以閱讀 something >>> 0是什么意思?[3]

          map

          基于 forEach 的實(shí)現(xiàn)能夠很容易寫(xiě)出 map 的實(shí)現(xiàn):

          - Array.prototype.forEach2 = function(callback, thisArg) {
          + Array.prototype.map2 = function(callback, thisArg) {
              if (this == null) {
                  throw new TypeError('this is null or not defined')
              }
              if (typeof callback !== "function") {
                  throw new TypeError(callback + ' is not a function')
              }
              const O = Object(this)
              const len = O.length >>> 0
          -   let k = 0
          +   let k = 0, res = []
              while (k < len) {
                  if (k in O) {
          -           callback.call(thisArg, O[k], k, O);
          +           res[k] = callback.call(thisArg, O[k], k, O);
                  }
                  k++;
              }
          +   return res
          }

          filter

          同樣,基于 forEach 的實(shí)現(xiàn)能夠很容易寫(xiě)出 filter 的實(shí)現(xiàn):

          - Array.prototype.forEach2 = function(callback, thisArg) {
          + Array.prototype.filter2 = function(callback, thisArg) {
              if (this == null) {
                  throw new TypeError('this is null or not defined')
              }
              if (typeof callback !== "function") {
                  throw new TypeError(callback + ' is not a function')
              }
              const O = Object(this)
              const len = O.length >>> 0
          -   let k = 0
          +   let k = 0, res = []
              while (k < len) {
                  if (k in O) {
          -           callback.call(thisArg, O[k], k, O);
          +           if (callback.call(thisArg, O[k], k, O)) {
          +               res.push(O[k])                
          +           }
                  }
                  k++;
              }
          +   return res
          }

          some

          同樣,基于 forEach 的實(shí)現(xiàn)能夠很容易寫(xiě)出 some 的實(shí)現(xiàn):

          - Array.prototype.forEach2 = function(callback, thisArg) {
          + Array.prototype.some2 = function(callback, thisArg) {
              if (this == null) {
                  throw new TypeError('this is null or not defined')
              }
              if (typeof callback !== "function") {
                  throw new TypeError(callback + ' is not a function')
              }
              const O = Object(this)
              const len = O.length >>> 0
              let k = 0
              while (k < len) {
                  if (k in O) {
          -           callback.call(thisArg, O[k], k, O);
          +           if (callback.call(thisArg, O[k], k, O)) {
          +               return true
          +           }
                  }
                  k++;
              }
          +   return false
          }

          reduce

          Array.prototype.reduce2 = function(callback, initialValue{
              if (this == null) {
                  throw new TypeError('this is null or not defined')
              }
              if (typeof callback !== "function") {
                  throw new TypeError(callback + ' is not a function')
              }
              const O = Object(this)
              const len = O.length >>> 0
              let k = 0, acc
              
              if (arguments.length > 1) {
                  acc = initialValue
              } else {
                  // 沒(méi)傳入初始值的時(shí)候,取數(shù)組中第一個(gè)非 empty 的值為初始值
                  while (k < len && !(k in O)) {
                      k++
                  }
                  if (k > len) {
                      throw new TypeError'Reduce of empty array with no initial value' );
                  }
                  acc = O[k++]
              }
              while (k < len) {
                  if (k in O) {
                      acc = callback(acc, O[k], k, O)
                  }
                  k++
              }
              return acc
          }

          實(shí)現(xiàn)函數(shù)原型方法

          call

          使用一個(gè)指定的 this 值和一個(gè)或多個(gè)參數(shù)來(lái)調(diào)用一個(gè)函數(shù)。

          實(shí)現(xiàn)要點(diǎn):

          • this 可能傳入 null;
          • 傳入不固定個(gè)數(shù)的參數(shù);
          • 函數(shù)可能有返回值;
          Function.prototype.call2 = function (context{
              var context = context || window;
              context.fn = this;

              var args = [];
              for(var i = 1, len = arguments.length; i < len; i++) {
                  args.push('arguments[' + i + ']');
              }

              var result = eval('context.fn(' + args +')');

              delete context.fn
              return result;
          }

          apply

          apply 和 call 一樣,唯一的區(qū)別就是 call 是傳入不固定個(gè)數(shù)的參數(shù),而 apply 是傳入一個(gè)數(shù)組。

          實(shí)現(xiàn)要點(diǎn):

          • this 可能傳入 null;
          • 傳入一個(gè)數(shù)組;
          • 函數(shù)可能有返回值;
          Function.prototype.apply2 = function (context, arr{
              var context = context || window;
              context.fn = this;

              var result;
              if (!arr) {
                  result = context.fn();
              } else {
                  var args = [];
                  for (var i = 0, len = arr.length; i < len; i++) {
                      args.push('arr[' + i + ']');
                  }
                  result = eval('context.fn(' + args + ')')
              }

              delete context.fn
              return result;
          }

          bind

          bind 方法會(huì)創(chuàng)建一個(gè)新的函數(shù),在 bind() 被調(diào)用時(shí),這個(gè)新函數(shù)的 this 被指定為 bind() 的第一個(gè)參數(shù),而其余參數(shù)將作為新函數(shù)的參數(shù),供調(diào)用時(shí)使用。

          實(shí)現(xiàn)要點(diǎn):

          • bind() 除了 this 外,還可傳入多個(gè)參數(shù);
          • bing 創(chuàng)建的新函數(shù)可能傳入多個(gè)參數(shù);
          • 新函數(shù)可能被當(dāng)做構(gòu)造函數(shù)調(diào)用;
          • 函數(shù)可能有返回值;
          Function.prototype.bind2 = function (context{
              var self = this;
              var args = Array.prototype.slice.call(arguments1);

              var fNOP = function ({};

              var fBound = function ({
                  var bindArgs = Array.prototype.slice.call(arguments);
                  return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
              }

              fNOP.prototype = this.prototype;
              fBound.prototype = new fNOP();
              return fBound;
          }

          實(shí)現(xiàn) new 關(guān)鍵字

          new 運(yùn)算符用來(lái)創(chuàng)建用戶(hù)自定義的對(duì)象類(lèi)型的實(shí)例或者具有構(gòu)造函數(shù)的內(nèi)置對(duì)象的實(shí)例。

          實(shí)現(xiàn)要點(diǎn):

          • new 會(huì)產(chǎn)生一個(gè)新對(duì)象;
          • 新對(duì)象需要能夠訪(fǎng)問(wèn)到構(gòu)造函數(shù)的屬性,所以需要重新指定它的原型;
          • 構(gòu)造函數(shù)可能會(huì)顯示返回;
          function objectFactory({
              var obj = new Object()
              Constructor = [].shift.call(arguments);
              obj.__proto__ = Constructor.prototype;
              var ret = Constructor.apply(obj, arguments);
              
              // ret || obj 這里這么寫(xiě)考慮了構(gòu)造函數(shù)顯示返回 null 的情況
              return typeof ret === 'object' ? ret || obj : obj;
          };

          使用:

          function person(name, age{
              this.name = name
              this.age = age
          }
          let p = objectFactory(person, '布蘭'12)
          console.log(p)  // { name: '布蘭', age: 12 }

          實(shí)現(xiàn) instanceof 關(guān)鍵字

          instanceof 就是判斷構(gòu)造函數(shù)的 prototype 屬性是否出現(xiàn)在實(shí)例的原型鏈上。

          function instanceOf(left, right{
              let proto = left.__proto__
              while (true) {
                  if (proto === nullreturn false
                  if (proto === right.prototype) {
                      return true
                  }
                  proto = proto.__proto__
              }
          }

          上面的 left.proto 這種寫(xiě)法可以換成 Object.getPrototypeOf(left)。

          實(shí)現(xiàn) Object.create

          Object.create()方法創(chuàng)建一個(gè)新對(duì)象,使用現(xiàn)有的對(duì)象來(lái)提供新創(chuàng)建的對(duì)象的__proto__。

          Object.create2 = function(proto, propertyObject = undefined{
              if (typeof proto !== 'object' && typeof proto !== 'function') {
                  throw new TypeError('Object prototype may only be an Object or null.')
              if (propertyObject == null) {
                  new TypeError('Cannot convert undefined or null to object')
              }
              function F({}
              F.prototype = proto
              const obj = new F()
              if (propertyObject != undefined) {
                  Object.defineProperties(obj, propertyObject)
              }
              if (proto === null) {
                  // 創(chuàng)建一個(gè)沒(méi)有原型對(duì)象的對(duì)象,Object.create(null)
                  obj.__proto__ = null
              }
              return obj
          }

          實(shí)現(xiàn) Object.assign

          Object.assign2 = function(target, ...source{
              if (target == null) {
                  throw new TypeError('Cannot convert undefined or null to object')
              }
              let ret = Object(target) 
              source.forEach(function(obj{
                  if (obj != null) {
                      for (let key in obj) {
                          if (obj.hasOwnProperty(key)) {
                              ret[key] = obj[key]
                          }
                      }
                  }
              })
              return ret
          }

          實(shí)現(xiàn) JSON.stringify

          JSON.stringify([, replacer [, space]) 方法是將一個(gè) JavaScript 值(對(duì)象或者數(shù)組)轉(zhuǎn)換為一個(gè) JSON 字符串。此處模擬實(shí)現(xiàn),不考慮可選的第二個(gè)參數(shù) replacer 和第三個(gè)參數(shù) space,如果對(duì)這兩個(gè)參數(shù)的作用還不了解,建議閱讀 MDN[4] 文檔。

          1. 基本數(shù)據(jù)類(lèi)型:
            • undefined 轉(zhuǎn)換之后仍是 undefined(類(lèi)型也是 undefined)
            • boolean 值轉(zhuǎn)換之后是字符串 "false"/"true"
            • number 類(lèi)型(除了 NaN 和 Infinity)轉(zhuǎn)換之后是字符串類(lèi)型的數(shù)值
            • symbol 轉(zhuǎn)換之后是 undefined
            • null 轉(zhuǎn)換之后是字符串 "null"
            • string 轉(zhuǎn)換之后仍是string
            • NaN 和 Infinity 轉(zhuǎn)換之后是字符串 "null"
          2. 函數(shù)類(lèi)型:轉(zhuǎn)換之后是 undefined
          3. 如果是對(duì)象類(lèi)型(非函數(shù))
            • 如果有 toJSON() 方法,那么序列化 toJSON() 的返回值。
            • 如果屬性值中出現(xiàn)了 undefined、任意的函數(shù)以及 symbol 值,忽略。
            • 所有以 symbol 為屬性鍵的屬性都會(huì)被完全忽略掉。
            • 如果是一個(gè)數(shù)組:如果屬性值中出現(xiàn)了 undefined、任意的函數(shù)以及 symbol,轉(zhuǎn)換成字符串 "null" ;
            • 如果是 RegExp 對(duì)象:返回 {} (類(lèi)型是 string);
            • 如果是 Date 對(duì)象,返回 Date 的 toJSON 字符串值;
            • 如果是普通對(duì)象;
          4. 對(duì)包含循環(huán)引用的對(duì)象(對(duì)象之間相互引用,形成無(wú)限循環(huán))執(zhí)行此方法,會(huì)拋出錯(cuò)誤。
          function jsonStringify(data{
              let dataType = typeof data;
              
              if (dataType !== 'object') {
                  let result = data;
                  //data 可能是 string/number/null/undefined/boolean
                  if (Number.isNaN(data) || data === Infinity) {
                      //NaN 和 Infinity 序列化返回 "null"
                      result = "null";
                  } else if (dataType === 'function' || dataType === 'undefined' || dataType === 'symbol') {
                      //function 、undefined 、symbol 序列化返回 undefined
                      return undefined;
                  } else if (dataType === 'string') {
                      result = '"' + data + '"';
                  }
                  //boolean 返回 String()
                  return String(result);
              } else if (dataType === 'object') {
                  if (data === null) {
                      return "null"
                  } else if (data.toJSON && typeof data.toJSON === 'function') {
                      return jsonStringify(data.toJSON());
                  } else if (data instanceof Array) {
                      let result = [];
                      //如果是數(shù)組
                      //toJSON 方法可以存在于原型鏈中
                      data.forEach((item, index) => {
                          if (typeof item === 'undefined' || typeof item === 'function' || typeof item === 'symbol') {
                              result[index] = "null";
                          } else {
                              result[index] = jsonStringify(item);
                          }
                      });
                      result = "[" + result + "]";
                      return result.replace(/'/g'"');
                      
                  } else {
                      //普通對(duì)象
                      /**
                       * 循環(huán)引用拋錯(cuò)(暫未檢測(cè),循環(huán)引用時(shí),堆棧溢出)
                       * symbol key 忽略
                       * undefined、函數(shù)、symbol 為屬性值,被忽略
                       */

                      let result = [];
                      Object.keys(data).forEach((item, index) => {
                          if (typeof item !== 'symbol') {
                              //key 如果是symbol對(duì)象,忽略
                              if (data[item] !== undefined && typeof data[item] !== 'function'
                                  && typeof data[item] !== 'symbol') {
                                  //鍵值如果是 undefined、函數(shù)、symbol 為屬性值,忽略
                                  result.push('"' + item + '"' + ":" + jsonStringify(data[item]));
                              }
                          }
                      });
                      return ("{" + result + "}").replace(/'/g'"');
                  }
              }
          }

          參考:實(shí)現(xiàn) JSON.stringify[5]

          實(shí)現(xiàn) JSON.parse

          介紹 2 種方法實(shí)現(xiàn):

          • eval 實(shí)現(xiàn);
          • new Function 實(shí)現(xiàn);

          eval 實(shí)現(xiàn)

          第一種方式最簡(jiǎn)單,也最直觀,就是直接調(diào)用 eval,代碼如下:

          var json = '{"a":"1", "b":2}';
          var obj = eval("(" + json + ")");  // obj 就是 json 反序列化之后得到的對(duì)象

          但是直接調(diào)用 eval 會(huì)存在安全問(wèn)題,如果數(shù)據(jù)中可能不是 json 數(shù)據(jù),而是可執(zhí)行的 JavaScript 代碼,那很可能會(huì)造成 XSS 攻擊。因此,在調(diào)用 eval 之前,需要對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)。

          var rx_one = /^[\],:{}\s]*$/;
          var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;
          var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
          var rx_four = /(?:^|:|,)(?:\s*\[)+/g;

          if (
              rx_one.test(
                  json.replace(rx_two, "@")
                      .replace(rx_three, "]")
                      .replace(rx_four, "")
              )
          ) {
              var obj = eval("(" +json + ")");
          }

          參考:JSON.parse 三種實(shí)現(xiàn)方式[6]

          new Function 實(shí)現(xiàn)

          Function 與 eval 有相同的字符串參數(shù)特性。

          var json = '{"name":"小姐姐", "age":20}';
          var obj = (new Function('return ' + json))();

          實(shí)現(xiàn) Promise

          實(shí)現(xiàn) Promise 需要完全讀懂 Promise A+ 規(guī)范[7],不過(guò)從總體的實(shí)現(xiàn)上看,有如下幾個(gè)點(diǎn)需要考慮到:

          • then 需要支持鏈?zhǔn)秸{(diào)用,所以得返回一個(gè)新的 Promise;
          • 處理異步問(wèn)題,所以得先用 onResolvedCallbacks 和 onRejectedCallbacks 分別把成功和失敗的回調(diào)存起來(lái);
          • 為了讓鏈?zhǔn)秸{(diào)用正常進(jìn)行下去,需要判斷 onFulfilled 和 onRejected 的類(lèi)型;
          • onFulfilled 和 onRejected 需要被異步調(diào)用,這里用 setTimeout 模擬異步;
          • 處理 Promise 的 resolve;
          const PENDING = 'pending';
          const FULFILLED = 'fulfilled';
          const REJECTED = 'rejected';

          class Promise {
              constructor(executor) {
                  this.status = PENDING;
                  this.value = undefined;
                  this.reason = undefined;
                  this.onResolvedCallbacks = [];
                  this.onRejectedCallbacks = [];
                  
                  let resolve = (value) = > {
                      if (this.status === PENDING) {
                          this.status = FULFILLED;
                          this.value = value;
                          this.onResolvedCallbacks.forEach((fn) = > fn());
                      }
                  };
                  
                  let reject = (reason) = > {
                      if (this.status === PENDING) {
                          this.status = REJECTED;
                          this.reason = reason;
                          this.onRejectedCallbacks.forEach((fn) = > fn());
                      }
                  };
                  
                  try {
                      executor(resolve, reject);
                  } catch (error) {
                      reject(error);
                  }
              }
              
              then(onFulfilled, onRejected) {
                  // 解決 onFufilled,onRejected 沒(méi)有傳值的問(wèn)題
                  onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (v) = > v;
                  // 因?yàn)殄e(cuò)誤的值要讓后面訪(fǎng)問(wèn)到,所以這里也要拋出錯(cuò)誤,不然會(huì)在之后 then 的 resolve 中捕獲
                  onRejected = typeof onRejected === "function" ? onRejected : (err) = > {
                      throw err;
                  };
                  // 每次調(diào)用 then 都返回一個(gè)新的 promise
                  let promise2 = new Promise((resolve, reject) = > {
                      if (this.status === FULFILLED) {
                          //Promise/A+ 2.2.4 --- setTimeout
                          setTimeout(() = > {
                              try {
                                  let x = onFulfilled(this.value);
                                  // x可能是一個(gè)proimise
                                  resolvePromise(promise2, x, resolve, reject);
                              } catch (e) {
                                  reject(e);
                              }
                          }, 0);
                      }
                  
                      if (this.status === REJECTED) {
                          //Promise/A+ 2.2.3
                          setTimeout(() = > {
                              try {
                                  let x = onRejected(this.reason);
                                  resolvePromise(promise2, x, resolve, reject);
                              } catch (e) {
                                  reject(e);
                              }
                          }, 0);
                      }
                      
                      if (this.status === PENDING) {
                          this.onResolvedCallbacks.push(() = > {
                              setTimeout(() = > {
                                  try {
                                      let x = onFulfilled(this.value);
                                      resolvePromise(promise2, x, resolve, reject);
                                  } catch (e) {
                                      reject(e);
                                  }
                              }, 0);
                          });
                      
                          this.onRejectedCallbacks.push(() = > {
                              setTimeout(() = > {
                                  try {
                                      let x = onRejected(this.reason);
                                      resolvePromise(promise2, x, resolve, reject);
                                  } catch (e) {
                                      reject(e);
                                  }
                              }, 0);
                          });
                      }
                  });
                  
                  return promise2;
              }
          }
          const resolvePromise = (promise2, x, resolve, reject) = > {
              // 自己等待自己完成是錯(cuò)誤的實(shí)現(xiàn),用一個(gè)類(lèi)型錯(cuò)誤,結(jié)束掉 promise  Promise/A+ 2.3.1
              if (promise2 === x) {
                  return reject(
                      new TypeError("Chaining cycle detected for promise #<Promise>"));
              }
              // Promise/A+ 2.3.3.3.3 只能調(diào)用一次
              let called;
              // 后續(xù)的條件要嚴(yán)格判斷 保證代碼能和別的庫(kù)一起使用
              if ((typeof x === "object" && x != null) || typeof x === "function") {
                  try {
                      // 為了判斷 resolve 過(guò)的就不用再 reject 了(比如 reject 和 resolve 同時(shí)調(diào)用的時(shí)候)  Promise/A+ 2.3.3.1
                      let then = x.then;
                      if (typeof then === "function") {
                      // 不要寫(xiě)成 x.then,直接 then.call 就可以了 因?yàn)?nbsp;x.then 會(huì)再次取值,Object.defineProperty  Promise/A+ 2.3.3.3
                          then.call(
                              x, (y) = > {
                                  // 根據(jù) promise 的狀態(tài)決定是成功還是失敗
                                  if (called) return;
                                  called = true;
                                  // 遞歸解析的過(guò)程(因?yàn)榭赡?nbsp;promise 中還有 promise) Promise/A+ 2.3.3.3.1
                                  resolvePromise(promise2, y, resolve, reject);
                              }, (r) = > {
                                  // 只要失敗就失敗 Promise/A+ 2.3.3.3.2
                                  if (called) return;
                                  called = true;
                                  reject(r);
                              });
                      } else {
                          // 如果 x.then 是個(gè)普通值就直接返回 resolve 作為結(jié)果  Promise/A+ 2.3.3.4
                          resolve(x);
                      }
                  } catch (e) {
                      // Promise/A+ 2.3.3.2
                      if (called) return;
                      called = true;
                      reject(e);
                  }
              } else {
                  // 如果 x 是個(gè)普通值就直接返回 resolve 作為結(jié)果  Promise/A+ 2.3.4
                  resolve(x);
              }
          };

          Promise 寫(xiě)完之后可以通過(guò) promises-aplus-tests 這個(gè)包對(duì)我們寫(xiě)的代碼進(jìn)行測(cè)試,看是否符合 A+ 規(guī)范。不過(guò)測(cè)試前還得加一段代碼:

          // promise.js
          // 這里是上面寫(xiě)的 Promise 全部代碼
          Promise.defer = Promise.deferred = function ({
              let dfd = {}
              dfd.promise = new Promise((resolve,reject)=>{
                  dfd.resolve = resolve;
                  dfd.reject = reject;
              });
              return dfd;
          }
          module.exports = Promise;

          全局安裝:

          npm i promises-aplus-tests -g

          終端下執(zhí)行驗(yàn)證命令:

          promises-aplus-tests promise.js

          上面寫(xiě)的代碼可以順利通過(guò)全部 872 個(gè)測(cè)試用例。

          參考:

          • BAT前端經(jīng)典面試問(wèn)題:史上最最最詳細(xì)的手寫(xiě)Promise教程[8]
          • 100 行代碼實(shí)現(xiàn) Promises/A+ 規(guī)范[9]

          Promise.resolve

          Promsie.resolve(value) 可以將任何值轉(zhuǎn)成值為 value 狀態(tài)是 fulfilled 的 Promise,但如果傳入的值本身是 Promise 則會(huì)原樣返回它。

          Promise.resolve = function(value{
              // 如果是 Promsie,則直接輸出它
              if(value instanceof Promise){
                  return value
              }
              return new Promise(resolve => resolve(value))
          }

          參考:深入理解 Promise[10]

          Promise.reject

          和 Promise.resolve() 類(lèi)似,Promise.reject() 會(huì)實(shí)例化一個(gè) rejected 狀態(tài)的 Promise。但與 Promise.resolve() 不同的是,如果給 Promise.reject() 傳遞一個(gè) Promise 對(duì)象,則這個(gè)對(duì)象會(huì)成為新 Promise 的值。

          Promise.reject = function(reason{
              return new Promise((resolve, reject) => reject(reason))
          }

          Promise.all

          Promise.all 的規(guī)則是這樣的:

          • 傳入的所有 Promsie 都是 fulfilled,則返回由他們的值組成的,狀態(tài)為 fulfilled 的新 Promise;
          • 只要有一個(gè) Promise 是 rejected,則返回 rejected 狀態(tài)的新 Promsie,且它的值是第一個(gè) rejected 的 Promise 的值;
          • 只要有一個(gè) Promise 是 pending,則返回一個(gè) pending 狀態(tài)的新 Promise;
          Promise.all = function(promiseArr{
              let index = 0, result = []
              return new Promise((resolve, reject) => {
                  promiseArr.forEach((p, i) => {
                      Promise.resolve(p).then(val => {
                          index++
                          result[i] = val
                          if (index === promiseArr.length) {
                              resolve(result)
                          }
                      }, err => {
                          reject(err)
                      })
                  })
              })
          }

          Promise.race

          Promise.race 會(huì)返回一個(gè)由所有可迭代實(shí)例中第一個(gè) fulfilled 或 rejected 的實(shí)例包裝后的新實(shí)例。

          Promise.race = function(promiseArr{
              return new Promise((resolve, reject) => {
                  promiseArr.forEach(p => {
                      Promise.resolve(p).then(val => {
                          resolve(val)
                      }, err => {
                          rejecte(err)
                      })
                  })
              })
          }

          Promise.allSettled

          Promise.allSettled 的規(guī)則是這樣:

          • 所有 Promise 的狀態(tài)都變化了,那么新返回一個(gè)狀態(tài)是 fulfilled 的 Promise,且它的值是一個(gè)數(shù)組,數(shù)組的每項(xiàng)由所有 Promise 的值和狀態(tài)組成的對(duì)象;
          • 如果有一個(gè)是 pending 的 Promise,則返回一個(gè)狀態(tài)是 pending 的新實(shí)例;
          Promise.allSettled = function(promiseArr{
              let result = []
                  
              return new Promise((resolve, reject) => {
                  promiseArr.forEach((p, i) => {
                      Promise.resolve(p).then(val => {
                          result.push({
                              status'fulfilled',
                              value: val
                          })
                          if (result.length === promiseArr.length) {
                              resolve(result) 
                          }
                      }, err => {
                          result.push({
                              status'rejected',
                              reason: err
                          })
                          if (result.length === promiseArr.length) {
                              resolve(result) 
                          }
                      })
                  })  
              })   
          }

          Promise.any

          Promise.any 的規(guī)則是這樣:

          • 空數(shù)組或者所有 Promise 都是 rejected,則返回狀態(tài)是 rejected 的新 Promsie,且值為 AggregateError 的錯(cuò)誤;
          • 只要有一個(gè)是 fulfilled 狀態(tài)的,則返回第一個(gè)是 fulfilled 的新實(shí)例;
          • 其他情況都會(huì)返回一個(gè) pending 的新實(shí)例;
          Promise.any = function(promiseArr{
              let index = 0
              return new Promise((resolve, reject) => {
                  if (promiseArr.length === 0return 
                  promiseArr.forEach((p, i) => {
                      Promise.resolve(p).then(val => {
                          resolve(val)
                          
                      }, err => {
                          index++
                          if (index === promiseArr.length) {
                            reject(new AggregateError('All promises were rejected'))
                          }
                      })
                  })
              })
          }

          后話(huà)

          能看到這里的對(duì)代碼都是真愛(ài)了,畢竟代碼這玩意看起來(lái)是真的很枯燥,但是如果看懂了后,就會(huì)像打游戲贏了一樣開(kāi)心,而且這玩意會(huì)上癮,當(dāng)你通關(guān)了越多的關(guān)卡后,你的能力就會(huì)拔高一個(gè)層次。用標(biāo)題的話(huà)來(lái)說(shuō)就是:搞懂后,提升真的大。加油吧??,干飯人

          噢不,代碼人。


          覺(jué)得還行,就來(lái)個(gè)點(diǎn)贊收藏分享 3連吧。

          參考資料

          [1]

          圖片懶加載: https://juejin.cn/post/6844903856489365518#heading-19

          [2]

          forEach#polyfill: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach#polyfill

          [3]

          something >>> 0是什么意思: https://zhuanlan.zhihu.com/p/100790268

          [4]

          stringify: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify

          [5]

          實(shí)現(xiàn) JSON.stringify: https://github.com/YvetteLau/Step-By-Step/issues/39#issuecomment-508327280

          [6]

          JSON.parse 三種實(shí)現(xiàn)方式: https://github.com/youngwind/blog/issues/115#issue-300869613

          [7]

          Promise A+ 規(guī)范: https://promisesaplus.com/

          [8]

          BAT前端經(jīng)典面試問(wèn)題:史上最最最詳細(xì)的手寫(xiě)Promise教程: https://juejin.cn/post/6844903625769091079

          [9]

          100 行代碼實(shí)現(xiàn) Promises/A+ 規(guī)范: https://mp.weixin.qq.com/s/qdJ0Xd8zTgtetFdlJL3P1g

          [10]

          深入理解 Promise: https://bubuzou.com/2020/10/22/promise/


          瀏覽 46
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  北条麻妃中文字幕久 | 日韩成人AV毛片 | 久操福利在线 | 可以在线观看的AV | 亚洲日韩欧美在线中文18 |