各類源碼實現,你想要的這裏都有

閱讀源碼的好處,不用說都知道,首先進大廠必備,還能夠提高本身的能力,學習前人的經驗。源碼每每是前人留下的最佳實踐,咱們跟着前人的腳步去學習會讓咱們事半功倍。前端

  • call、aplly、bind 實現
  • new 實現
  • class 實現繼承
  • async/await 實現
  • reduce 實現
  • 實現一個雙向數據綁定
  • instanceof 實現
  • Array.isArray 實現
  • Object.create 的基本實現原理
  • getOwnPropertyNames 實現
  • promise 實現
  • 手寫一個防抖/節流函數
  • 柯里化函數的實現
  • 手寫一個深拷貝

call、aplly、bind 實現

call、aplly、bind 本質都是改變 this 的指向,不一樣點 call、aplly 是直接調用函數,bind 是返回一個新的函數。callaplly 就只有參數上不一樣。es6

bind 實現

  • 箭頭函數的 this 永遠指向它所在的做用域
  • 函數做爲構造函數用 new 關鍵字調用時,不該該改變其 this 指向,由於 new綁定 的優先級高於 顯示綁定硬綁定
Function.prototype.mybind = function(thisArg) {
    if (typeof this !== 'function') {
      throw TypeError("Bind must be called on a function");
    }
    // 拿到參數,爲了傳給調用者
    const args = Array.prototype.slice.call(arguments, 1),
      // 保存 this
      self = this,
      // 構建一個乾淨的函數,用於保存原函數的原型
      nop = function() {},
      // 綁定的函數
      bound = function() {
        // this instanceof nop, 判斷是否使用 new 來調用 bound
        // 若是是 new 來調用的話,this的指向就是其實例,
        // 若是不是 new 調用的話,就改變 this 指向到指定的對象 o
        return self.apply(
          this instanceof nop ? this : thisArg,
          args.concat(Array.prototype.slice.call(arguments))
        );
      };

    // 箭頭函數沒有 prototype,箭頭函數this永遠指向它所在的做用域
    if (this.prototype) {
      nop.prototype = this.prototype;
    }
    // 修改綁定函數的原型指向
    bound.prototype = new nop();

    return bound;
  }
}
複製代碼
  1. 測試 mybind
const bar = function() {
  console.log(this.name, arguments);
};

bar.prototype.name = 'bar';

const foo = {
  name: 'foo'
};

const bound = bar.mybind(foo, 22, 33, 44);
new bound(); // bar, [22, 33, 44]
bound(); // foo, [22, 33, 44]
複製代碼

call 實現

bind 是封裝了 call 的方法改變了 this 的指向並返回一個新的函數,那麼 call 是如何作到改變 this 的指向呢?原理很簡單,在方法調用模式下,this 老是指向調用它所在方法的對象,this 的指向與所在方法的調用位置有關,而與方法的聲明位置無關(箭頭函數特殊)。先寫一個小 demo 來理解一下下。數組

const foo = { name: 'foo' };

foo.fn = function() {
  // 這裏的 this 指向了 foo
  // 由於 foo 調用了 fn,
  // fn 的 this 就指向了調用它所在方法的對象 foo 上
  console.log(this.name); // foo
};
複製代碼

利用 this 的機制來實現 callpromise

Function.prototype.mycall = function(thisArg) {
    // this指向調用call的對象
    if (typeof this !== 'function') {
      // 調用call的若不是函數則報錯
      throw new TypeError('Error');
    }
    // 聲明一個 Symbol 屬性,防止 fn 被佔用
    const fn = Symbol('fn')
    const args = [...arguments].slice(1);
    thisArg = thisArg || window;
    // 將調用call函數的對象添加到thisArg的屬性中
    thisArg[fn] = this;
    // 執行該屬性
    const result = thisArg[fn](...args);
    // 刪除該屬性
    delete thisArg[fn];
    // 返回函數執行結果
    return result;
  }
複製代碼

aplly 實現

Function.prototype.myapply = function(thisArg) {
  if (typeof this !== 'function') {
    throw this + ' is not a function';
  }

  const args = arguments[1];
  const fn = Symbol('fn')
  thisArg[fn] = this;

  const result = thisArg[fn](...arg);

  delete thisArg[fn];

  return result;
};
複製代碼

測試 mycall myaplly微信

const bar = function() {
  console.log(this.name, arguments);
};

bar.prototype.name = 'bar';

const foo = {
  name: 'foo'
};

bar.mycall(foo, 1, 2, 3); // foo [1, 2, 3]
bar.myaplly(foo, [1, 2, 3]); // foo [1, 2, 3]
複製代碼

reduce 實現原理

arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])閉包

Array.prototype.myreduce = function reduce(callbackfn) {
  // 拿到數組
  const O = this,
    len = O.length;
  // 下標值
  let k = 0,
    // 累加器
    accumulator = undefined,
    // k下標對應的值是否存在
    kPresent = false,
    // 初始值
    initialValue = arguments.length > 1 ? arguments[1] : undefined;

  if (typeof callbackfn !== 'function') {
    throw new TypeError(callbackfn + ' is not a function');
  }

  // 數組爲空,而且有初始值,報錯
  if (len === 0 && arguments.length < 2) {
    throw new TypeError('Reduce of empty array with no initial value');
  }

  // 若是初始值存在
  if (arguments.length > 1) {
    // 設置累加器爲初始值
    accumulator = initialValue;
    // 初始值不存在
  } else {
    accumulator = O[k];
    ++k;
  }

  while (k < len) {
    // 判斷是否爲 empty [,,,]
    kPresent = O.hasOwnProperty(k);

    if (kPresent) {
      const kValue = O[k];
      // 調用 callbackfn
      accumulator = callbackfn.apply(undefined, [accumulator, kValue, k, O]);
    }
    ++k;
  }

  return accumulator;
};
複製代碼

測試app

const rReduce = ['1', null, undefined, , 3, 4].reduce((a, b) => a + b, 3);
const mReduce = ['1', null, undefined, , 3, 4].myreduce((a, b) => a + b, 3);

console.log(rReduce, mReduce);
// 31nullundefined34 31nullundefined34
複製代碼

new 實現

咱們須要知道當 new 的時候作了什麼事情dom

  1. 建立一個新對象;
  2. 將構造函數的做用域賦給新對象(所以 this 就指向了這個新對象)
  3. 執行構造函數中的代碼(爲這個新對象添加屬性)
  4. 返回新對象。

由於 new 沒辦法重寫,咱們使用 myNew 函數來模擬 new異步

function myNew() {
  // 建立一個實例對象
  var obj = new Object();
  // 取得外部傳入的構造器
  var Constructor = Array.prototype.shift.call(arguments);
  // 實現繼承,實例能夠訪問構造器的屬性
  obj.__proto__ = Constructor.prototype;
  // 調用構造器,並改變其 this 指向到實例
  var ret = Constructor.apply(obj, arguments);
  // 若是構造函數返回值是對象則返回這個對象,若是不是對象則返回新的實例對象
  return typeof ret === 'object' && ret !== null ? ret : obj;
}
複製代碼

測試 myNewasync

// ========= 無返回值 =============
const testNewFun = function(name) {
  this.name = name;
};

const newObj = myNew(testNewFun, 'foo');

console.log(newObj); // { name: "foo" }
console.log(newObj instanceof testNewFun); // true
// ========= 有返回值 =============
const testNewFun = function(name) {
  this.name = name;
  return {};
};

const newObj = myNew(testNewFun, 'foo');

console.log(newObj); // {}
console.log(newObj instanceof testNewFun); // false
複製代碼

class 實現繼承

主要使用 es5es6 對比看下 class 繼承的原理

實現繼承 A extends B

使用 es6 語法

class B {
  constructor(opt) {
    this.BName = opt.name;
  }
}
class A extends B {
  constructor() {
    // 向父類傳參
    super({ name: 'B' });
    // this 必須在 super() 下面使用
    console.log(this);
  }
}
複製代碼

使用 es5 語法

使用寄生組合繼承的方式

  1. 原型鏈繼承,使子類能夠調用父類原型上的方法和屬性
  2. 借用構造函數繼承,能夠實現向父類傳參
  3. 寄生繼承,創造乾淨的沒有構造方法的函數,用來寄生父類的 prototype
// 實現繼承,經過繼承父類 prototype
function __extends(child, parent) {
  // 修改對象原型
  Object.setPrototypeOf(child, parent);
  // 寄生繼承,建立一個乾淨的構造函數,用於繼承父類的 prototype
  // 這樣作的好處是,修改子類的 prototype 不會影響父類的 prototype
  function __() {
    // 修正 constructor 指向子類
    this.constructor = child;
  }
  // 原型繼承,繼承父類原型屬性,可是沒法向父類構造函數傳參
  child.prototype =
    parent === null
      ? Object.create(parent)
      : ((__.prototype = parent.prototype), new __());
}

var B = (function() {
  function B(opt) {
    this.name = opt.name;
  }
  return B;
})();

var A = (function(_super) {
  __extends(A, _super);
  function A() {
    // 借用繼承,能夠實現向父類傳參, 使用 super 能夠向父類傳參
    return (_super !== null && _super.apply(this, { name: 'B' })) || this;
  }
  return A;
})(B);
複製代碼

測試 class

const a = new A();

console.log(a.BName, a.constructor); // B ,ƒ A() {}
複製代碼

async/await 實現

原理就是利用 generator(生成器)分割代碼片斷。而後咱們使用一個函數讓其自迭代,每個yieldpromise 包裹起來。執行下一步的時機由 promise 來控制

async/await 是關鍵字,不能重寫它的方法,咱們使用函數來模擬

異步迭代,模擬異步函數

function _asyncToGenerator(fn) {
  return function() {
    var self = this,
      args = arguments;
    // 將返回值promise化
    return new Promise(function(resolve, reject) {
      // 獲取迭代器實例
      var gen = fn.apply(self, args);
      // 執行下一步
      function _next(value) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'next', value);
      }
      // 拋出異常
      function _throw(err) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'throw', err);
      }
      // 第一次觸發
      _next(undefined);
    });
  };
}
複製代碼

執行迭代步驟,處理下次迭代結果

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
  try {
    var info = gen[key](arg);
    var value = info.value;
  } catch (error) {
    reject(error);
    return;
  }
  if (info.done) {
    // 迭代器完成
    resolve(value);
  } else {
    // -- 這行代碼就是精髓 --
    // 將全部值promise化
    // 好比 yield 1
    // const a = Promise.resolve(1) a 是一個 promise
    // const b = Promise.resolve(a) b 是一個 promise
    // 能夠作到統一 promise 輸出
    // 當 promise 執行完以後再執行下一步
    // 遞歸調用 next 函數,直到 done == true
    Promise.resolve(value).then(_next, _throw);
  }
}
複製代碼

測試 _asyncToGenerator

const asyncFunc = _asyncToGenerator(function*() {
  const e = yield new Promise(resolve => {
    setTimeout(() => {
      resolve('e');
    }, 1000);
  });
  const a = yield Promise.resolve('a');
  const d = yield 'd';
  const b = yield Promise.resolve('b');
  const c = yield Promise.resolve('c');
  return [a, b, c, d, e];
});

asyncFunc().then(res => {
  console.log(res); // ['a', 'b', 'c', 'd', 'e']
});
複製代碼

實現一個雙向綁定

defineProperty 版本

// 數據
const data = {
  text: 'default'
};
const input = document.getElementById('input');
const span = document.getElementById('span');
// 數據劫持
Object.defineProperty(data, 'text', {
  // 數據變化 --> 修改視圖
  set(newVal) {
    input.value = newVal;
    span.innerHTML = newVal;
  }
});
// 視圖更改 --> 數據變化
input.addEventListener('keyup', function(e) {
  data.text = e.target.value;
});
複製代碼

proxy 版本

// 數據
const data = {
  text: 'default'
};
const input = document.getElementById('input');
const span = document.getElementById('span');
// 數據劫持
const handler = {
  set(target, key, value) {
    target[key] = value;
    // 數據變化 --> 修改視圖
    input.value = value;
    span.innerHTML = value;
    return value;
  }
};
const proxy = new Proxy(data);

// 視圖更改 --> 數據變化
input.addEventListener('keyup', function(e) {
  proxy.text = e.target.value;
});
複製代碼

Object.create 的基本實現原理

function create(obj) {
  function F() {}
  F.prototype = obj;
  return new F();
}
複製代碼

instanceof 實現

原理: L__proto__ 是否是等於 R.prototype,不等於再找 L.__proto__.__proto__ 直到 __proto__null

// L 表示左表達式,R 表示右表達式
function instance_of(L, R) {
  var O = R.prototype;
  L = L.__proto__;
  while (true) {
    if (L === null) return false;
    // 這裏重點:當 O 嚴格等於 L 時,返回 true
    if (O === L) return true;
    L = L.__proto__;
  }
}
複製代碼

Array.isArray 實現

Array.myIsArray = function(o) {
  return Object.prototype.toString.call(Object(o)) === '[object Array]';
};

console.log(Array.myIsArray([])); // true
複製代碼

getOwnPropertyNames 實現

if (typeof Object.getOwnPropertyNames !== 'function') {
  Object.getOwnPropertyNames = function(o) {
    if (o !== Object(o)) {
      throw TypeError('Object.getOwnPropertyNames called on non-object');
    }
    var props = [],
      p;
    for (p in o) {
      if (Object.prototype.hasOwnProperty.call(o, p)) {
        props.push(p);
      }
    }
    return props;
  };
}
複製代碼

Promise 實現

實現原理:其實就是一個發佈訂閱者模式

  1. 構造函數接收一個 executor 函數,並會在 new Promise() 時當即執行該函數
  2. then 時收集依賴,將回調函數收集到 成功/失敗隊列
  3. executor 函數中調用 resolve/reject 函數
  4. resolve/reject 函數被調用時會通知觸發隊列中的回調

先看一下總體代碼,有一個大體的概念

完整代碼

const isFunction = variable => typeof variable === 'function';

// 定義Promise的三種狀態常量
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
  // 構造函數,new 時觸發
  constructor(handle: Function) {
    try {
      handle(this._resolve, this._reject);
    } catch (err) {
      this._reject(err);
    }
  }
  // 狀態 pending fulfilled rejected
  private _status: string = PENDING;
  // 儲存 value,用於 then 返回
  private _value: string | undefined = undefined;
  // 失敗隊列,在 then 時注入,resolve 時觸發
  private _rejectedQueues: any = [];
  // 成功隊列,在 then 時注入,resolve 時觸發
  private _fulfilledQueues: any = [];
  // resovle 時執行的函數
  private _resolve = val => {
    const run = () => {
      if (this._status !== PENDING) return;
      this._status = FULFILLED;
      // 依次執行成功隊列中的函數,並清空隊列
      const runFulfilled = value => {
        let cb;
        while ((cb = this._fulfilledQueues.shift())) {
          cb(value);
        }
      };
      // 依次執行失敗隊列中的函數,並清空隊列
      const runRejected = error => {
        let cb;
        while ((cb = this._rejectedQueues.shift())) {
          cb(error);
        }
      };
      /* * 若是resolve的參數爲Promise對象, * 則必須等待該Promise對象狀態改變後當前Promsie的狀態纔會改變 * 且狀態取決於參數Promsie對象的狀態 */
      if (val instanceof MyPromise) {
        val.then(
          value => {
            this._value = value;
            runFulfilled(value);
          },
          err => {
            this._value = err;
            runRejected(err);
          }
        );
      } else {
        this._value = val;
        runFulfilled(val);
      }
    };
    // 異步調用
    setTimeout(run);
  };
  // reject 時執行的函數
  private _reject = err => {
    if (this._status !== PENDING) return;
    // 依次執行失敗隊列中的函數,並清空隊列
    const run = () => {
      this._status = REJECTED;
      this._value = err;
      let cb;
      while ((cb = this._rejectedQueues.shift())) {
        cb(err);
      }
    };
    // 爲了支持同步的Promise,這裏採用異步調用
    setTimeout(run);
  };
  // then 方法
  then(onFulfilled?, onRejected?) {
    const { _value, _status } = this;
    // 返回一個新的Promise對象
    return new MyPromise((onFulfilledNext, onRejectedNext) => {
      // 封裝一個成功時執行的函數
      const fulfilled = value => {
        try {
          if (!isFunction(onFulfilled)) {
            onFulfilledNext(value);
          } else {
            const res = onFulfilled(value);
            if (res instanceof MyPromise) {
              // 若是當前回調函數返回MyPromise對象,必須等待其狀態改變後在執行下一個回調
              res.then(onFulfilledNext, onRejectedNext);
            } else {
              //不然會將返回結果直接做爲參數,傳入下一個then的回調函數,並當即執行下一個then的回調函數
              onFulfilledNext(res);
            }
          }
        } catch (err) {
          // 若是函數執行出錯,新的Promise對象的狀態爲失敗
          onRejectedNext(err);
        }
      };

      // 封裝一個失敗時執行的函數
      const rejected = error => {
        try {
          if (!isFunction(onRejected)) {
            onRejectedNext(error);
          } else {
            const res = onRejected(error);
            if (res instanceof MyPromise) {
              // 若是當前回調函數返回MyPromise對象,必須等待其狀態改變後在執行下一個回調
              res.then(onFulfilledNext, onRejectedNext);
            } else {
              //不然會將返回結果直接做爲參數,傳入下一個then的回調函數,並當即執行下一個then的回調函數
              onFulfilledNext(res);
            }
          }
        } catch (err) {
          // 若是函數執行出錯,新的Promise對象的狀態爲失敗
          onRejectedNext(err);
        }
      };

      switch (_status) {
        // 當狀態爲pending時,將then方法回調函數加入執行隊列等待執行
        case PENDING:
          this._fulfilledQueues.push(fulfilled);
          this._rejectedQueues.push(rejected);
          break;
        // 當狀態已經改變時,當即執行對應的回調函數
        case FULFILLED:
          fulfilled(_value);
          break;
        case REJECTED:
          rejected(_value);
          break;
      }
    });
  }
  // catch 方法
  catch(onRejected) {
    return this.then(undefined, onRejected);
  }
  // finally 方法
  finally(cb) {
    return this.then(
      value => MyPromise.resolve(cb()).then(() => value),
      reason =>
        MyPromise.resolve(cb()).then(() => {
          throw reason;
        })
    );
  }
  // 靜態 resolve 方法
  static resolve(value) {
    // 若是參數是MyPromise實例,直接返回這個實例
    if (value instanceof MyPromise) return value;
    return new MyPromise(resolve => resolve(value));
  }
  // 靜態 reject 方法
  static reject(value) {
    return new MyPromise((resolve, reject) => reject(value));
  }
  // 靜態 all 方法
  static all(list) {
    return new MyPromise((resolve, reject) => {
      // 返回值的集合
      let values = [];
      let count = 0;
      for (let [i, p] of list.entries()) {
        // 數組參數若是不是MyPromise實例,先調用MyPromise.resolve
        this.resolve(p).then(
          res => {
            values[i] = res;
            count++;
            // 全部狀態都變成fulfilled時返回的MyPromise狀態就變成fulfilled
            if (count === list.length) resolve(values);
          },
          err => {
            // 有一個被rejected時返回的MyPromise狀態就變成rejected
            reject(err);
          }
        );
      }
    });
  }
  // 添加靜態race方法
  static race(list) {
    return new MyPromise((resolve, reject) => {
      for (let p of list) {
        // 只要有一個實例率先改變狀態,新的MyPromise的狀態就跟着改變
        this.resolve(p).then(
          res => {
            resolve(res);
          },
          err => {
            reject(err);
          }
        );
      }
    });
  }
}
複製代碼

防抖/截流

防抖函數 onscroll 結束時觸發一次,延遲執行

function debounce(func, wait) {
  let timeout;
  return function() {
    let context = this;
    let args = arguments;
    if (timeout) clearTimeout(timeout);
    timeout = setTimeout(() => {
      func.apply(context, args);
    }, wait);
  };
}
// 使用
window.onscroll = debounce(function() {
  console.log('debounce');
}, 1000);
複製代碼

節流函數 onscroll 時,每隔一段時間觸發一次,像水滴同樣

function throttle(fn, delay) {
  var prevTime = Date.now();
  return function() {
    var curTime = Date.now();
    if (curTime - prevTime > delay) {
      fn.apply(this, arguments);
      prevTime = curTime;
    }
  };
}
// 使用
var throtteScroll = throttle(function() {
  console.log('throtte');
}, 1000);
window.onscroll = throtteScroll;
複製代碼

函數柯里化實現

其實咱們無時無刻不在使用柯里化函數,只是沒有將它總結出來而已。它的本質就是將一個參數不少的函數分解成單一參數的多個函數。

實際應用中:

  • 延遲計算 (用閉包把傳入參數保存起來,當傳入參數的數量足夠執行函數時,開始執行函數)
  • 動態建立函數 (參數不夠時會返回接受剩下參數的函數)
  • 參數複用(每一個參數能夠屢次複用)
const curry = fn =>
  (judge = (...args) =>
    args.length === fn.length
      ? fn(...args)
      : (...arg) => judge(...args, ...arg));

const sum = (a, b, c, d) => a + b + c + d;
const currySum = curry(sum);

currySum(1)(2)(3)(4); // 10
currySum(1, 2)(3)(4); // 10
currySum(1)(2, 3)(4); // 10
複製代碼

手寫一個深拷貝

淺拷貝只複製地址值,實際上仍是指向同一堆內存中的數據,深拷貝則是從新建立了一個相同的數據,兩者指向的堆內存的地址值是不一樣的。這個時候修改賦值前的變量數據不會影響賦值後的變量。

要實現一個完美的神拷貝太複雜了,這裏簡單介紹一下吧,能夠應用於大部分場景了

判斷類型函數

function getType(obj) {
  const str = Object.prototype.toString.call(obj);
  const map = {
    '[object Boolean]': 'boolean',
    '[object Number]': 'number',
    '[object String]': 'string',
    '[object Function]': 'function',
    '[object Array]': 'array',
    '[object Date]': 'date',
    '[object RegExp]': 'regExp',
    '[object Undefined]': 'undefined',
    '[object Null]': 'null',
    '[object Object]': 'object'
  };
  if (obj instanceof Element) {
    // 判斷是不是dom元素,如div等
    return 'element';
  }
  return map[str];
}
複製代碼

簡單版深拷貝,列舉三個例子 array object function,能夠自行擴展。主要是引起你們的思考

function deepCopy(ori) {
  const type = getType(ori);
  let copy;
  switch (type) {
    case 'array':
      return copyArray(ori, type, copy);
    case 'object':
      return copyObject(ori, type, copy);
    case 'function':
      return copyFunction(ori, type, copy);
    default:
      return ori;
  }
}

function copyArray(ori, type, copy = []) {
  for (const [index, value] of ori.entries()) {
    copy[index] = deepCopy(value);
  }
  return copy;
}

function copyObject(ori, type, copy = {}) {
  for (const [key, value] of Object.entries(ori)) {
    copy[key] = deepCopy(value);
  }
  return copy;
}

function copyFunction(ori, type, copy = () => {}) {
  const fun = eval(ori.toString());
  fun.prototype = ori.prototype
  return fun
}
複製代碼

最後有幾件小事

  1. 有想入羣的學習前端進階的加我微信 luoxue2479 回覆加羣便可
  2. 有錯誤的話歡迎在留言區指出,一塊兒討論,也能夠加我微信
  3. 鄙人公衆號【前端技匠】,一塊兒來學習吧。

參考文章

cloud.tencent.com/developer/a…

www.jianshu.com/p/b4f0425b2…

blog.csdn.net/LL187811327…

相關文章
相關標籤/搜索