1. 首页

各种源码实现,你想要的这里都有

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

  • 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

// 实现继承,通过继承父类 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, 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); // 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 }

参考文章

cloud.tencent.com/developer/a…

www.jianshu.com/p/b4f0425b2…

blog.csdn.net/LL187811327…

作者:前端技匠
链接:https://juejin.im/post/5dc3894051882517a652dbd7

看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程

JS中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。欢迎热爱技术的你一起加入交流与学习,JS中文网的使命是帮助开发者用代码改变世界

本文著作权归作者所有,如若转载,请注明出处

转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com

标题:各种源码实现,你想要的这里都有

链接:https://www.javascriptc.com/3082.html

« 第4篇:也许是终极异步解决方案之细说Async专题
带你开发和调试 VS Code 源码»
Flutter 中文教程资源

相关推荐

QR code