高頻面試手寫代碼滿分答案! (2w字)

循序漸進,越往後越難!!!

實現淺拷貝

如果給一個變量賦值一個對象,那麼兩者的值會是同一個引用,其中一方改變,另一方也會相應改變。針對引用類型我們需要實現數據的拷貝。

  1. ... 實現
const copy = {...{x:1}}
  1. Object.assign 實現
const copy = Object.assign({}, {x:1})
  1. slice 實現
let arr = [1, 3, {
  x: 1
}];

let copy = arr.slice();

深拷貝

通常淺拷貝就能解決大部分問題,但是隻解決了第一層的問題,如果接下去的值中還有對象的話,那麼我們需要使用深拷貝。

  1. 通過 JSON 轉換實現

缺點:

  • 對於 function、undefined,會丟失這些屬性。
  • 對於 RegExp、Error 對象,只會得到空對象
  • 對於 date 對象,得到的結果是 string,而不是 date 對象
  • 對於 NaN、Infinity、-Infinity,會變成 null
  • 無法處理循環引用
const obj = {a: 1, b: {x: 3}}
const copy = JSON.parse(JSON.stringify(obj))
  1. 乞丐式遞歸

乞丐版的遞歸,針對常用的JS類型(基礎類型、數組、對象),雖然解決了大部分JSON.parse(JSON.stringify(oldObj))的問題,但依然無法解決循環引用的問題。

function deepClone(obj) {
  let copy = obj instanceof Array ? [] : {}
  for (let i in obj) {
    if (obj.hasOwnProperty(i)) {
      copy[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i]
    }
  }
  return copy
}
  1. 改良版深拷貝

參考vuex的deepCopy源碼,解決循環引用

function deepCopy (obj, cache = []) {
  // typeof [] => 'object'
  // typeof {} => 'object'
  if (obj === null || typeof obj !== 'object') {
    return obj
  }
  // 如果傳入的對象與緩存的相等, 則遞歸結束, 這樣防止循環
  /**
   * 類似下面這種
   * var a = {b:1}
   * a.c = a
   * 資料: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value
   */
  const hit = cache.filter(c => c.original === obj)[0]
  if (hit) {
    return hit.copy
  }

  const copy = Array.isArray(obj) ?  [] :   {}
  // 將copy首先放入cache, 因爲我們需要在遞歸deepCopy的時候引用它
  cache.push({
    original: obj,
    copy
  })
  Object.keys(obj).forEach(key => {
    copy[key] = deepCopy(obj[key], cache)
  })

  return copy
}

當然: cache 可以使用 new WeakMap() 代替

  1. 深拷貝再優化
  • 深拷貝添加 MapSet 相關,當然你可以再添加 Date 之類的補充
  • 使用 WeakMap 代替 []
function clone(target, map = new WeakMap()) {
  // 克隆原始類型
  if (!isObject(target)) {
    return target;
  }

  // 初始化
  const type = getType(target);
  let cloneTarget;
  if (deepTag.includes(type)) {
    cloneTarget = getInit(target, type);
  }

  // 防止循環引用
  if (map.get(target)) {
    return map.get(target);
  }
  map.set(target, cloneTarget);

  // 克隆set
  if (type === setTag) {
    target.forEach(value => {
      cloneTarget.add(clone(value,map));
    });
    return cloneTarget;
  }

  // 克隆map
  if (type === mapTag) {
    target.forEach((value, key) => {
      cloneTarget.set(key, clone(value,map));
    });
    return cloneTarget;
  }

  // 克隆對象和數組
  const keys = type === arrayTag ? undefined : Object.keys(target);
  forEach(keys || target, (value, key) => {
    if (keys) {
      key = value;
    }
    cloneTarget[key] = clone(target[key], map);
  });

  return cloneTarget;
}
  1. 各種兼容版本的深拷貝

兼容對象、數組、Symbol、正則、Error、Date、基礎類型。

const mapTag = '[object Map]';
const setTag = '[object Set]';
const arrayTag = '[object Array]';
const objectTag = '[object Object]';
const argsTag = '[object Arguments]';

const boolTag = '[object Boolean]';
const dateTag = '[object Date]';
const numberTag = '[object Number]';
const stringTag = '[object String]';
const symbolTag = '[object Symbol]';
const errorTag = '[object Error]';
const regexpTag = '[object RegExp]';
const funcTag = '[object Function]';

const deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag];

function forEach(array, iteratee) {
  let index = -1;
  const length = array.length;
  while (++index < length) {
    iteratee(array[index], index);
  }
  return array;
}

function isObject(target) {
  const type = typeof target;
  return target !== null && (type === 'object' || type === 'function');
}

function getType(target) {
  return Object.prototype.toString.call(target);
}

function getInit(target) {
  const Ctor = target.constructor;
  return new Ctor();
}

function cloneSymbol(targe) {
  return Object(Symbol.prototype.valueOf.call(targe));
}

function cloneReg(targe) {
  const reFlags = /\w*$/;
  const result = new targe.constructor(targe.source, reFlags.exec(targe));
  result.lastIndex = targe.lastIndex;
  return result;
}

function cloneFunction(func) {
  const bodyReg = /(?<={)(.|\n)+(?=})/m;
  const paramReg = /(?<=\().+(?=\)\s+{)/;
  const funcString = func.toString();

  if (func.prototype) {
    const param = paramReg.exec(funcString);
    const body = bodyReg.exec(funcString);
    if (body) {
      if (param) {
        const paramArr = param[0].split(',');
        return new Function(...paramArr, body[0]);
      } else {
        return new Function(body[0]);
      }
    } else {
      return null;
    }
  } else {
    return eval(funcString);
  }
}

function cloneOtherType(targe, type) {
  const Ctor = targe.constructor;
  switch (type) {
    case boolTag:
    case numberTag:
    case stringTag:
    case errorTag:
    case dateTag:
      return new Ctor(targe);
    case regexpTag:
      return cloneReg(targe);
    case symbolTag:
      return cloneSymbol(targe);
    case funcTag:
      return cloneFunction(targe);
    default:
      return null;
  }
}

function clone(target, map = new WeakMap()) {

  // 克隆原始類型
  if (!isObject(target)) {
    return target;
  }

  // 初始化
  const type = getType(target);
  let cloneTarget;
  if (deepTag.includes(type)) {
    cloneTarget = getInit(target, type);
  } else {
    return cloneOtherType(target, type);
  }

  // 防止循環引用
  if (map.get(target)) {
    return map.get(target);
  }
  map.set(target, cloneTarget);

  // 克隆set
  if (type === setTag) {
    target.forEach(value => {
      cloneTarget.add(clone(value, map));
    });
    return cloneTarget;
  }

  // 克隆map
  if (type === mapTag) {
    target.forEach((value, key) => {
      cloneTarget.set(key, clone(value, map));
    });
    return cloneTarget;
  }

  // 克隆對象和數組
  const keys = type === arrayTag ? undefined : Object.keys(target);
  forEach(keys || target, (value, key) => {
    if (keys) {
      key = value;
    }
    cloneTarget[key] = clone(target[key], map);
  });

  return cloneTarget;
}

實現一個 bind 函數

通過閉包調用 call || apply 可以實現 bind 函數。

  1. 乞丐版
Function.prototype.myapply = function (context, ...preArgs) {
    return (...args) => this.call(context, ...preArgs, ...args)
}
  1. 進階版,做一些異常處理
Function.prototype.mybind = function (context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  let _this = this
  let arg = [...arguments].slice(1)
  return function F() {
    // 處理函數使用new的情況
    if (this instanceof F) {
      return new _this(...arg, ...arguments)
    } else {
      return _this.apply(context, arg.concat(...arguments))
    }
  }
}

實現一個 apply 函數

實現 bind 需要調用 apply || call,那麼如何實現?

思路:將要改變this指向的方法掛到目標this上執行並返回

Function.prototype.myapply = function (context) {
  if (typeof this !== 'function') {
    throw new TypeError('not funciton')
  }

  context = context || window
  context.fn = this
  let result

  if (arguments[1]) {
    result = context.fn(...arguments[1])
  } else {
    result = context.fn()
  }

  delete context.fn

  return result
}

實現防抖函數

滾動事件、resize事件、input事件等: 需要做個複雜計算或者實現一個按鈕的防二次點擊操作。這些需求都可以通過函數防抖動來實現。

  1. 乞丐版

缺點:這個防抖只能在最後調用。一般的防抖會有immediate選項,表示是否立即調用。

const debounce = (func, wait = 50) => {
  // 緩存一個定時器id
  let timer = 0
  // 這裏返回的函數是每次用戶實際調用的防抖函數
  // 如果已經設定過定時器了就清空上一次的定時器
  // 開始一個新的定時器,延遲執行用戶傳入的方法
  return function(...args) {
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => {
      func.apply(this, args)
    }, wait)
  }
}
  1. 改良版
// 這個是用來獲取當前時間戳的
function now() {
  return +new Date()
}

/**
 * 防抖函數,返回函數連續調用時,空閒時間必須大於或等於 wait,func 纔會執行
 *
 * @param  {function} func        回調函數
 * @param  {number}   wait        表示時間窗口的間隔
 * @param  {boolean}  immediate   設置爲ture時,是否立即調用函數
 * @return {function}             返回客戶調用函數
 */
function debounce (func, wait = 50, immediate = true) {
  let timer, context, args

  // 延遲執行函數
  const later = () => setTimeout(() => {
    // 延遲函數執行完畢,清空緩存的定時器序號
    timer = null
    // 延遲執行的情況下,函數會在延遲函數中執行
    // 使用到之前緩存的參數和上下文
    if (!immediate) {
      func.apply(context, args)
      context = args = null
    }
  }, wait)

  // 這裏返回的函數是每次實際調用的函數
  return function(...params) {
    // 如果沒有創建延遲執行函數(later),就創建一個
    if (!timer) {
      timer = later()
      // 如果是立即執行,調用函數
      // 否則緩存參數和調用上下文
      if (immediate) {
        func.apply(this, params)
      } else {
        context = this
        args = params
      }
    // 如果已有延遲執行函數(later),調用的時候清除原來的並重新設定一個
    // 這樣做延遲函數會重新計時
    } else {
      clearTimeout(timer)
      timer = later()
    }
  }
}

實現一個節流函數

節流的本質和防抖差不多。

  1. 乞丐版

缺點:這個節流函數缺少首尾調用的開關

/**
 * 函數節流方法
 * @param Function fn 延時調用函數
 * @param Number delay 延遲多長時間
 * @param Number atleast 至少多長時間觸發一次
 * @return Function 延遲執行的方法
 */
var throttle = function (fn, delay, atleast) {
  var timer = null;
  var previous = null;

  return function () {
    var now = Date.now();

    if ( !previous ) previous = now;

    if ( now - previous > atleast ) {
      fn();
      // 重置上一次開始時間爲本次結束時間
      previous = now;
    } else {
      clearTimeout(timer);
      timer = setTimeout(function() {
        fn();
      }, delay);
    }
  }
};
  1. 改良版:參考 lodash 經典的節流函數
/**
 * underscore 節流函數,返回函數連續調用時,func 執行頻率限定爲 次 / wait
 *
 * @param  function   func      回調函數
 * @param  number     wait      表示時間窗口的間隔
 * @param  object     options   如果想忽略開始函數的的調用,傳入{leading: false}。
 *                                如果想忽略結尾函數的調用,傳入{trailing: false}
 *                                兩者不能共存,否則函數不能執行
 * @return function             返回客戶調用函數
 */
const throttle = function(func, wait, options) {
  var context, args, result;
  var timeout = null;
  // 之前的時間戳
  var previous = 0;
  // 如果 options 沒傳則設爲空對象
  if (!options) options = {};

  // 定時器回調函數
  var later = function() {
    // 如果設置了 leading,就將 previous 設爲 0
    // 用於下面函數的第一個 if 判斷
    previous = options.leading === false ? 0 : Date.now();
    // 置空一是爲了防止內存泄漏,二是爲了下面的定時器判斷
    timeout = null;
    result = func.apply(context, args);
    if (!timeout) context = args = null;
  };

  return function() {
    // 獲得當前時間戳
    var now = Date.now();
    // 首次進入前者肯定爲 true
    // 如果需要第一次不執行函數
    // 就將上次時間戳設爲當前的
    // 這樣在接下來計算 remaining 的值時會大於0
    if (!previous && options.leading === false) previous = now;
    // 計算剩餘時間
    var remaining = wait - (now - previous);
    context = this;
    args = arguments;

    // 如果當前調用已經大於上次調用時間 + wait
    // 或者用戶手動調了時間
    // 如果設置了 trailing,只會進入這個條件
    // 如果沒有設置 leading,那麼第一次會進入這個條件
    // 還有一點,你可能會覺得開啓了定時器那麼應該不會進入這個 if 條件了
    // 其實還是會進入的,因爲定時器的延時
    // 並不是準確的時間,很可能你設置了2秒
    // 但是他需要2.2秒才觸發,這時候就會進入這個條件
    if (remaining <= 0 || remaining > wait) {
      // 如果存在定時器就清理掉否則會調用二次回調
      if (timeout) {
        clearTimeout(timeout);
        timeout = null;
      }
      previous = now;
      result = func.apply(context, args);
      if (!timeout) context = args = null;
    } else if (!timeout && options.trailing !== false) {
      // 判斷是否設置了定時器和 trailing
    // 沒有的話就開啓一個定時器
      // 並且不能不能同時設置 leading 和 trailing
      timeout = setTimeout(later, remaining);
    }
    return result;
  };
};

柯里化函數的實現

柯里化:

  • 把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數
  • 並且返回接受餘下的參數且返回結果的新函數

實現柯里化

  1. 固定傳入參數,參數夠了才執行
/**
 * 實現要點:柯里化函數接收到足夠參數後,就會執行原函數,那麼我們如何去確定何時達到足夠的參數呢?
 * 柯里化函數需要記住你已經給過他的參數,如果沒給的話,則默認爲一個空數組。
 * 接下來每次調用的時候,需要檢查參數是否給夠,如果夠了,則執行fn,沒有的話則返回一個新的 curry 函數,將現有的參數塞給他。
 * 
 */
// 待柯里化處理的函數
let sum = (a, b, c, d) => {
  return a + b + c + d
}

// 柯里化函數,返回一個被處理過的函數 
let curry = (fn, ...arr) => {  // arr 記錄已有參數
  return (...args) => {   // args 接收新參數
    if (fn.length <= (...arr,...args)) {  // 參數夠時,觸發執行
      return fn(...arr, ...args)
    } else {  // 繼續添加參數
      return curry(fn, [...arr, ...args])
    }
  }
}

var sumPlus = curry(sum)
sumPlus(1)(2)(3)(4)
sumPlus(1, 2)(3)(4)
sumPlus(1, 2, 3)(4)
  1. 不固定傳入參數,隨時執行
/**
 * 當然了,柯里化函數的主要作用還是延遲執行,執行的觸發條件不一定是參數個數相等,也可以是其他的條件。
 * 例如參數個爲0的情況,那麼我們需要對上面curry函數稍微做修改
 */
// 待柯里化處理的函數
let sum = arr => {
  return arr.reduce((a, b) => {
    return a + b
  })
}

let curry = (fn, ...arr) => {  // arr 記錄已有參數
  return (...args) => {  // args 接收新參數
    if (args.length === 0) {  // 參數爲空時,觸發執行
      return fn(...arr, ...args)
    } else {  // 繼續添加參數
      return curry(fn, ...arr, ...args)
    }
  }
}

var sumPlus = curry(sum)
sumPlus(1)(2)(3)(4)()
sumPlus(1, 2)(3)(4)()
sumPlus(1, 2, 3)(4)()

數組扁平化

const flattenDeep = (arr) => Array.isArray(arr)
  ? arr.reduce( (a, b) => [...a, ...flattenDeep(b)] , [])
  : [arr]

flattenDeep([1, [[2], [3, [4]], 5]])

實現一個new操作符

new操作符做了這些事:

  1. 創建了一個全新的對象。
  2. 被執行[[Prototype]](也就是proto)鏈接。
  3. 使this指向新創建的對象。。
  4. 通過new創建的每個對象將最終被[[Prototype]]鏈接到這個函數的prototype對象上。
  5. 如果函數沒有返回對象類型Object(包含Functoin, Array, Date, RegExg, Error),那麼new表達式中的函數調用將返回該對象引用。
function New(func) {
  var res = {};
  if (func.prototype !== null) {
    res.__proto__ = func.prototype;
  }
  var ret = func.apply(res, Array.prototype.slice.call(arguments, 1));
  if ((typeof ret === "object" || typeof ret === "function") && ret !== null) {
    return ret;
  }
  return res;
}

var obj = New(A, 1, 2);

實現一個 instanceOf

instanceOf 的內部機制是通過判斷對象的原型鏈中是不是能找到該類型的 prototype

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

Promise 實現

Promise 有3個狀態 pendingresolvereject。因爲狀態的的確定,所以Promise的結果是可靠的。

Promise 還能解決回調地獄的問題。

  1. 乞丐版

實現了 Promise 的主要功能,缺少異步處理等其他情況

class Promise {
  constructor (fn) {
    // 三個狀態
    this.state = 'pending'
    this.value = undefined
    this.reason = undefined

    let resolve = value => {
      if (this.state === 'pending') {
        this.state = 'fulfilled'
        this.value = value
      }
    }
    let reject = value => {
      if (this.state === 'pending') {
        this.state = 'rejected'
        this.reason = value
      }
    }
    // 自動執行函數
    try {
      fn(resolve, reject)
    } catch (e) {
      reject(e)
    }
  }

  // then
  then(onFulfilled, onRejected) {
    switch (this.state) {
      case 'fulfilled':
        onFulfilled(this.value)
        break
      case 'rejected':
        onRejected(this.value)
        break
      default:
    }
  }
}
  1. 改良版:yck 小冊裏面實現的了Promise的主要功能(沒有catch、finally、靜態調用等)
// 三種狀態
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";

// promise 接收一個函數參數,該函數會立即執行
function MyPromise(fn) {
  let _this = this;
  _this.currentState = PENDING;
  _this.value = undefined;
  // 用於保存 then 中的回調,只有當 promise
  // 狀態爲 pending 時纔會緩存,並且每個實例至多緩存一個
  _this.resolvedCallbacks = [];
  _this.rejectedCallbacks = [];

  _this.resolve = function (value) {
    if (value instanceof MyPromise) {
      // 如果 value 是個 Promise,遞歸執行
      return value.then(_this.resolve, _this.reject)
    }
    setTimeout(() => { // 異步執行,保證執行順序
      if (_this.currentState === PENDING) {
        _this.currentState = RESOLVED;
        _this.value = value;
        _this.resolvedCallbacks.forEach(cb => cb());
      }
    })
  };

  _this.reject = function (reason) {
    setTimeout(() => { // 異步執行,保證執行順序
      if (_this.currentState === PENDING) {
        _this.currentState = REJECTED;
        _this.value = reason;
        _this.rejectedCallbacks.forEach(cb => cb());
      }
    })
  }
  // 用於解決以下問題
  // new Promise(() => throw Error('error))
  try {
    fn(_this.resolve, _this.reject);
  } catch (e) {
    _this.reject(e);
  }
}

MyPromise.prototype.then = function (onResolved, onRejected) {
  var self = this;
  // 規範 2.2.7,then 必須返回一個新的 promise
  var promise2;
  // 規範 2.2.onResolved 和 onRejected 都爲可選參數
  // 如果類型不是函數需要忽略,同時也實現了透傳
  // Promise.resolve(4).then().then((value) => console.log(value))
  onResolved = typeof onResolved === 'function' ? onResolved : v => v;
  onRejected = typeof onRejected === 'function' ? onRejected : r => {throw r};

  if (self.currentState === RESOLVED) {
    return (promise2 = new MyPromise(function (resolve, reject) {
      // 規範 2.2.4,保證 onFulfilled,onRjected 異步執行
      // 所以用了 setTimeout 包裹下
      setTimeout(function () {
        try {
          var x = onResolved(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      });
    }));
  }

  if (self.currentState === REJECTED) {
    return (promise2 = new MyPromise(function (resolve, reject) {
      setTimeout(function () {
        // 異步執行onRejected
        try {
          var x = onRejected(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      });
    }));
  }

  if (self.currentState === PENDING) {
    return (promise2 = new MyPromise(function (resolve, reject) {
      self.resolvedCallbacks.push(function () {
        // 考慮到可能會有報錯,所以使用 try/catch 包裹
        try {
          var x = onResolved(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (r) {
          reject(r);
        }
      });

      self.rejectedCallbacks.push(function () {
        try {
          var x = onRejected(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (r) {
          reject(r);
        }
      });
    }));
  }
};

// 規範 2.3
function resolutionProcedure(promise2, x, resolve, reject) {
  // 規範 2.3.1,x 不能和 promise2 相同,避免循環引用
  if (promise2 === x) {
    return reject(new TypeError("Error"));
  }
  // 規範 2.3.2
  // 如果 x 爲 Promise,狀態爲 pending 需要繼續等待否則執行
  if (x instanceof MyPromise) {
    if (x.currentState === PENDING) {
      x.then(function (value) {
        // 再次調用該函數是爲了確認 x resolve 的
        // 參數是什麼類型,如果是基本類型就再次 resolve
        // 把值傳給下個 then
        resolutionProcedure(promise2, value, resolve, reject);
      }, reject);
    } else {
      x.then(resolve, reject);
    }
    return;
  }
  // 規範 2.3.3.3.3
  // reject 或者 resolve 其中一個執行過得話,忽略其他的
  let called = false;
  // 規範 2.3.3,判斷 x 是否爲對象或者函數
  if (x !== null && (typeof x === "object" || typeof x === "function")) {
    // 規範 2.3.3.2,如果不能取出 then,就 reject
    try {
      // 規範 2.3.3.1
      let then = x.then;
      // 如果 then 是函數,調用 x.then
      if (typeof then === "function") {
        // 規範 2.3.3.3
        then.call(
          x,
          y => {
            if (called) return;
            called = true;
            // 規範 2.3.3.3.1
            resolutionProcedure(promise2, y, resolve, reject);
          },
          e => {
            if (called) return;
            called = true;
            reject(e);
          }
        );
      } else {
        // 規範 2.3.3.4
        resolve(x);
      }
    } catch (e) {
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    // 規範 2.3.4,x 爲基本類型
    resolve(x);
  }
}
  1. 補充版
// 三種狀態
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";

function MyPromise(fn) {
  let _this = this;
  _this.currentState = PENDING;
  _this.value = undefined;
  _this.resolvedCallbacks = [];
  _this.rejectedCallbacks = [];

  _this.resolve = function (value) {
    if (value instanceof MyPromise) {
      return value.then(_this.resolve, _this.reject)
    }
    setTimeout(() => {
      if (_this.currentState === PENDING) {
        _this.currentState = RESOLVED;
        _this.value = value;
        _this.resolvedCallbacks.forEach(cb => cb());
      }
    })
  };

  _this.reject = function (reason) {
    setTimeout(() => {
      if (_this.currentState === PENDING) {
        _this.currentState = REJECTED;
        _this.value = reason;
        _this.rejectedCallbacks.forEach(cb => cb());
      }
    })
  }
  try {
    fn(_this.resolve, _this.reject);
  } catch (e) {
    _this.reject(e);
  }
}

MyPromise.prototype.then = function (onResolved, onRejected) {
  var self = this;
  var promise2;
  onResolved = typeof onResolved === 'function' ? onResolved : v => v;
  onRejected = typeof onRejected === 'function' ? onRejected : r => {throw r};

  if (self.currentState === RESOLVED) {
    return (promise2 = new MyPromise(function (resolve, reject) {
      setTimeout(function () {
        try {
          var x = onResolved(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      });
    }));
  }

  if (self.currentState === REJECTED) {
    return (promise2 = new MyPromise(function (resolve, reject) {
      setTimeout(function () {
        try {
          var x = onRejected(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      });
    }));
  }

  if (self.currentState === PENDING) {
    return (promise2 = new MyPromise(function (resolve, reject) {
      self.resolvedCallbacks.push(function () {
        try {
          var x = onResolved(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (r) {
          reject(r);
        }
      });

      self.rejectedCallbacks.push(function () {
        try {
          var x = onRejected(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (r) {
          reject(r);
        }
      });
    }));
  }
};

// 規範 2.3
function resolutionProcedure(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError("Error"));
  }
  if (x instanceof MyPromise) {
    if (x.currentState === PENDING) {
      x.then(function (value) {
        resolutionProcedure(promise2, value, resolve, reject);
      }, reject);
    } else {
      x.then(resolve, reject);
    }
    return;
  }
  let called = false;
  if (x !== null && (typeof x === "object" || typeof x === "function")) {
    try {
      let then = x.then;
      if (typeof then === "function") {
        then.call(
          x,
          y => {
            if (called) return;
            called = true;
            resolutionProcedure(promise2, y, resolve, reject);
          },
          e => {
            if (called) return;
            called = true;
            reject(e);
          }
        );
      } else {
        resolve(x);
      }
    } catch (e) {
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    resolve(x);
  }
}

// catch方法
MyPromise.prototype.catch = function (rejectFn) {
  return this.then(undefined, rejectFn)
}

//finally方法
MyPromise.prototype.finally = function (callback) {
  return this.then(
    value => MyPromise.resolve(callback()).then(() => value),
    reason => MyPromise.resolve(callback()).then(() => { throw reason })
  )
}

/* 
  靜態方法添加
 */

// resolve方法
MyPromise.resolve = function(val){
  return new MyPromise((resolve,reject)=>{
    resolve(val)
  });
}

//reject方法
MyPromise.reject = function(val){
  return new MyPromise((resolve,reject)=>{
    reject(val)
  });
}

//race方法 
MyPromise.race = function(promises){
  return new MyPromise((resolve,reject)=>{
    for(let i=0;i<promises.length;i++){
      promises[i].then(resolve, reject)
    };
  })
}

//all方法(獲取所有的promise,都執行then,把結果放到數組,一起返回)
MyPromise.all = function(promises){
  let arr = [];
  let i = 0;

  function processData(index,data){
    arr[index] = data;
    i++;
    if(i == promises.length){
      resolve(arr);
    };
  };

  return new Promise((resolve,reject)=>{
    for(let i=0;i<promises.length;i++){
      promises[i].then(data=>{
        processData(i,data);
      },reject);
    };
  });
}

實現 EventBus

  1. 乞丐版

實現了主要功能,但不處理異常場景,也不實現 remove

class EventEmitter {
  constructor () {
    // 存儲事件
    this.events = this.events || new Map()
  }

  // 監聽事件
  addListener (type, fn) {
    if (!this.events.get(type)) {
      this.events.set(type, fn)
    }
  }

  // 觸發事件
  emit (type) {
    let handle = this.events.get(type)
    handle.apply(this, [...arguments].slice(1))
  }
}
  1. 進階版
class EventEmitter{
  constructor(){
    if(this._events === undefined){
      this._events = Object.create(null);//定義事件對象
      this._eventsCount = 0;
    }
  }

  emit(type,...args){
    const events=this._events;
    const handler=events[type];
    //判斷相應type的執行函數是否爲一個函數還是一個數組
    if(typeof handler==='function'){
      Reflect.apply(handler,this,args);
    }else{
      const len=handler.length;
      for(var i=0;li<len;i++){
        Reflect.apply(handler[i],this,args);
      }
    }
    return true;
  }

  on(type,listener,prepend){
    var m;
    var events;
    var existing;
    events=this._events;
    //添加事件的
    if(events.newListener!==undefined){
       this.emit('namelessListener',type,listener);
       events=target._events;
    }
    existing=events[type];
    //判斷相應的type的方法是否存在
    if(existing===undefined){
      //如果相應的type的方法不存在,這新增一個相應type的事件
      existing=events[type]=listener;
      ++this._eventsCount;
    }else{
      //如果存在相應的type的方法,判斷相應的type的方法是一個數組還是僅僅只是一個方法
      //如果僅僅是
      if(typeof existing==='function'){
        //如果僅僅是一個方法,則添加
        existing=events[type]=prepend?[listener,existing]:[existing,listener];
      }else if(prepend){
        existing.unshift(listener);
      }else{
        existing.push(listener);
      }
    }
    //鏈式調用
    return this;
  }

  removeListener(type,listener){
    var list,events,position,i,originalListener;
    events=this._events;
    list=events[type];
    //如果相應的事件對象的屬性值是一個函數,也就是說事件只被一個函數監聽
    if(list===listener){
      if(--this._eventsCount===0){
        this._events=Object.create(null);
      }else{
        delete events[type];
        //如果存在對移除事件removeListener的監聽函數,則觸發removeListener
        if(events.removeListener)
          this.emit('removeListener',type,listener);
      }
    }else if(typeof list!=='function'){
      //如果相應的事件對象屬性值是一個函數數組
      //遍歷這個數組,找出listener對應的那個函數,在數組中的位置
      for(i=list.length-1;i>=0;i--){
        if(list[i]===listener){
          position=i;
          break;
        }
      }
      //沒有找到這個函數,則返回不做任何改動的對象
      if(position){
        return this;
      }
      //如果數組的第一個函數纔是所需要刪除的對應listener函數,則直接移除
      if(position===0){
        list.shift();
      }else{
        list.splice(position,1);
      }
      if(list.length===1)
        events[type]=list[0];
      if(events.removeListener!==undefined)
        this.emit('removeListener',type,listener);
    }
    return this;
  }
}

最後

  1. 覺得有用的請點個贊
  2. 歡迎關注公衆號「前端進階課」認真學前端,一起進階。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章