沐鳴總代理_前端各種手寫Js代碼實現

實現淺拷貝

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

用 … 實現

const copy = {...{x:1}}

用 Object.assign 實現

const copy = Object.assign({}, {x:1})

用 slice 實現

let arr = [1, 3, {
  x: 1
}];

let copy = arr.slice();

深拷貝

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

通過 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))

乞丐式遞歸

乞丐版的遞歸,針對常用的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
}

改良版深拷貝

參考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() 代替

深拷貝再優化

深拷貝添加 Map 和 Set 相關,當然你可以再添加 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;
}

各種兼容版本的深拷貝

兼容對象、數組、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 函數。

乞丐版

Function.prototype.myapply = function (context, ...preArgs) {
    return (...args) => this.call(context, ...preArgs, ...args)
}

進階版,做一些異常處理

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事件等: 需要做個複雜計算或者實現一個按鈕的防二次點擊操作。這些需求都可以通過函數防抖動來實現。

乞丐版

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

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

改良版

// 這個是用來獲取當前時間戳的
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()
    }
  }
}

實現一個節流函數

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

乞丐版

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

/**
 * 函數節流方法
 * @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);
    }
  }
};

改良版:參考 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;
  };
};

柯里化函數的實現

柯里化:

把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數

並且返回接受餘下的參數且返回結果的新函數

實現柯里化

固定傳入參數,參數夠了才執行

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

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

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

不固定傳入參數,隨時執行

/**
 * 當然了,柯里化函數的主要作用還是延遲執行,執行的觸發條件不一定是參數個數相等,也可以是其他的條件。
 * 例如參數個為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操作符做了這些事:

創建了一個全新的對象。

被執行[[Prototype]](也就是__proto__)鏈接。

使this指向新創建的對象。。

通過new創建的每個對象將最終被[[Prototype]]鏈接到這個函數的prototype對象上。

如果函數沒有返回對象類型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個狀態 pending、 resolve 和 reject。因為狀態的的確定,所以Promise的結果是可靠的。

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

乞丐版

實現了 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:
    }
  }
}

改良版: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);
  }
}

補充版

// 三種狀態
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

乞丐版

實現了主要功能,但不處理異常場景,也不實現 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))
  }
}

進階版

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;
  }
}

原文:https://zhongmeizhi.github.io/fed-note/review/js_code.html

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

鏈接: http://www.fly63.com/article/detial/9729