博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
各种源码实现,你想要的这里都有
阅读量:4087 次
发布时间:2019-05-25

本文共 18154 字,大约阅读时间需要 60 分钟。

阅读源码的好处,不用说都知道,首先进大厂必备,还可以提升自己的能力,学习前人的经验。源码往往是前人留下的最佳实践,我们跟着前人的脚步去学习会让我们事半功倍。

  • call、apply、bind 实现
  • new 实现
  • class 实现继承
  • async/await 实现
  • reduce 实现
  • 实现一个双向数据绑定
  • instanceof 实现
  • Array.isArray 实现
  • Object.create 的基本实现原理
  • getOwnPropertyNames 实现
  • promise 实现
  • 手写一个防抖/节流函数
  • 柯里化函数的实现
  • 手写一个深拷贝

call、apply、bind 实现

call、apply、bind 本质都是改变 this 的指向,不同点 call、apply 是直接调用函数,bind 是返回一个新的函数。callapply 就只有参数上不同。

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 的机制来实现 call

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;  }复制代码

apply 实现

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 myapply

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.myapply(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;};复制代码

测试

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 的时候做了什么事情

  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;}复制代码

测试 myNew

// ========= 无返回值 =============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
// 实现继承,通过继承父类 prototypefunction __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, handler);// 视图更改 --> 数据变化input.addEventListener('keyup', function(e) {  proxy.text = e.target.value;});复制代码

Object.create 的基本实现原理

if (typeof Object.create !== "function") {  Object.create = function (prototype, properties) {    if (typeof prototype !== "object") { throw TypeError(); }    function Ctor() {}    Ctor.prototype = prototype;    var o = new Ctor();    if (prototype) { o.constructor = Ctor; }    if (properties !== undefined) {      if (properties !== Object(properties)) { throw TypeError(); }      Object.defineProperties(o, properties);    }    return o;  };}复制代码

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); // 10currySum(1, 2)(3)(4); // 10currySum(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}

作者:前端技匠
链接:https://juejin.im/post/5dc3894051882517a652dbd7
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

你可能感兴趣的文章
Android初学第30天
查看>>
Branch Prediction
查看>>
Ajax自动完成(autocomplete)-响应文本框输入后显示模糊数据列表
查看>>
1.2计算机网络性能指标
查看>>
C/C++知识点
查看>>
搜索 录音功能 Android api
查看>>
vue-router 根据路由动态添加目录 控制目录权限
查看>>
zigbee协议栈应用与组网(四):组播-多终端控制协调器 LED
查看>>
BZOJ1263 (求导后高精)
查看>>
centos yum 安装mongo
查看>>
我的第一个爬虫实验
查看>>
Dubbo源码解读
查看>>
swagger2 导出离线Word/PDF/HTML文档
查看>>
使用<span>标签为文字设置单独样式
查看>>
LeetCode - 验证二叉搜索树
查看>>
有序数组转化成二叉搜索数
查看>>
halcon保存带有region的图片算子
查看>>
QT-利用C++仿制windown自带的记事本程序V1.0
查看>>
估计点云中的表面法线
查看>>
《从零开始搭建游戏服务器》架构和工具分析
查看>>