JS手写系列
1、Promise系列
Promise手写
//极简的实现+链式调用+延迟机制+状态 class Promise { callbacks = []; state = 'pending';//增加状态 value = null;//保存结果 constructor(fn) { fn(this._resolve.bind(this)); } then(onFulfilled) { if (this.state === 'pending') {//在resolve之前,跟之前逻辑一样,添加到callbacks中 this.callbacks.push(onFulfilled); } else {//在resolve之后,直接执行回调,返回结果了 onFulfilled(this.value); } return this; } _resolve(value) { this.state = 'fulfilled';//改变状态 this.value = value;//保存结果 this.callbacks.forEach(fn => fn(value)); } }
Promise.resolve:
-
Promise.resolve
最终结果还是一个Promise
,并且与Promise.resolve(该值)
传入的值息息相关 -
传入的参数可以是一个
Promise实例
,那么该函数执行的结果是直接将实例返回Promise.myResolve = function(val){ if(val && (typeof(val) === 'object') && (val instanceof Promise)){ return val } return new Promise(rs=>{ rs(val) }) }
Promise.reject:
Promise.myReject = function(val){ return new Promise((rs,rj)=>{ rj(val) }) }
Promise.all:
Promise.all()
方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
只要是一个失败了,结果即进入失败状态
Promise.myAll = (promises) => { return new Promise((rs,rj) => { const result = [] let count = 0 if(!promises.length) return rs([]) promises.forEach((p,i) => { Promise.resolve(p).then(res=>{ result[i] = p count += 1 if(count === promises.length){ rs(result) } } ).catch(rj) }); }) }
Promise.allSettled
-
不管是全部成功还是有部分失败,最终都会进入
Promise.allSettled
的.then
回调中 -
最后的返回值中,成功和失败的项都有
status
属性,成功时值是fulfilled
,失败时是rejected
-
最后的返回值中,成功含有
value
属性,而失败则是reason
属性
Promise.myAllSettled = (promises) => { return new Promise((rs, rj) => { let count = 0 let result = [] const len = promises.length // 数组是空的话,直接返回空数据 if (len === 0) { return rs([]) } promises.forEach((p, i) => { Promise.resolve(p).then((res) => { count += 1 // 成功属性设置 result[ i ] = { status: 'fulfilled', value: res } if (count === len) { rs(result) } }).catch((err) => { count += 1 // 失败属性设置 result[i] = { status: 'rejected', reason: err } if (count === len) { rs(result) } }) }) }) }
Promise.race
Promise.myRace = (promises) => { return new Promise((rs, rj) => { promises.forEach((p) => { // 对p进行一次包装,防止非Promise对象 // 并且对齐进行监听,将我们自己返回的Promise的resolve,reject传递给p,哪个先改变状态,我们返回的Promise也将会是什么状态 Promise.resolve(p).then(rs).catch(rj) }) }) }
2、发布订阅模式-----EventEmitter
class EventEmitter { constructor(){ this.cache = {} } on(name,fn){ if(this.cache[name]){ this.cache[name].push(fn) }else{ this.cache[name] = [fn] } } emit(name,isOnce){ if (this.cache[name]){ const tasks = this.cache[name].slice() for (let fn of tasks){ fn() } if(isOnce){ delete this.cache[name] } } } off(name,fn){ const tasks = this.cache[name] if (tasks){ const index = tasks.findIndex(f=>f === fn || f.callback === fn) if(index >= 0){ tasks.splice(index,1) } } } }
// 测试 const eventBus = new EventEmitter() const task1 = () => { console.log('task1'); } const task2 = () => { console.log('task2'); } eventBus.on('task', task1) eventBus.on('task', task2) eventBus.off('task', task1) setTimeout(() => { eventBus.emit('task') // task2 }, 1000)
3、手写深拷贝
- 基础版
deepClone(obj) { let cloneObj = {}; for (let key in obj) { if (typeof obj[key] === 'object') { cloneObj[key] = deepClone(obj[key]); } else { cloneObj[key] = obj[key]; } } return cloneObj; },
缺点:
- 这个深拷贝函数并不能复制不可枚举的属性以及
Symbol
类型; - 这种方法
只是针对普通的引用类型的值做递归复制
,而对于Array、Date、RegExp、Error、Function
这样的引用类型并不能正确地拷贝; - 对象的属性里面成环,即
循环引用没有解决
。
const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null) const deepClone = function (obj, hash = new WeakMap()) { if (obj.constructor === Date) { return new Date(obj) // 日期对象直接返回一个新的日期对象 } if (obj.constructor === RegExp){ return new RegExp(obj) //正则对象直接返回一个新的正则对象 } // 普通值或函数不需要深拷贝 if (typeof obj !== "object") return obj; //如果循环引用了就用 weakMap 来解决 if (hash.has(obj)) { return hash.get(obj) } let allDesc = Object.getOwnPropertyDescriptors(obj) //遍历传入参数所有键的特性 let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc) // 把cloneObj原型复制到obj上 hash.set(obj, cloneObj) for (let key of Reflect.ownKeys(obj)) { //普通值或函数不需要深拷贝 cloneObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ? deepClone(obj[key], hash) : obj[key] } return cloneObj } // 下面是验证代码 let obj = { num: 0, str: '', boolean: true, unf: undefined, nul: null, obj: { name: '我是一个对象', id: 1 }, arr: [0, 1, 2], func: function () { console.log('我是一个函数') }, date: new Date(0), reg: new RegExp('/我是一个正则/ig'), [Symbol('1')]: 1, }; Object.defineProperty(obj, 'innumerable', { enumerable: false, value: '不可枚举属性' } ); obj = Object.create(obj, Object.getOwnPropertyDescriptors(obj)) obj.loop = obj // 设置loop成循环引用的属性 let cloneObj = deepClone(obj) cloneObj.arr.push(4) console.log('obj', obj) console.log('cloneObj', cloneObj)
- 这个深拷贝函数并不能复制不可枚举的属性以及
- 针对能够遍历对象的不可枚举属性以及
Symbol
类型,可以使用Reflect.ownKeys
方法; - 当参数为
Date、RegExp
类型,则直接生成一个新的实例返回; - 利用
Object
的getOwnPropertyDescriptors
方法可以获得对象的所有属性,以及对应的特性,顺便结合Object.create
方法创建一个新对象,并继承传入原对象的原型链; - 利用
WeakMap
类型作为Hash
表,因为WeakMap
是弱引用类型,可以有效防止内存泄漏(你可以关注一下Map
和weakMap
的关键区别,这里要用weakMap
),作为检测循环引用很有帮助,如果存在循环,则引用直接返回WeakMap
存储的值
4、Object.assign
用于将所有可枚举(Object.propertyIsEnumerable()
返回 true
)和自有(Object.hasOwnProperty()
返回 true
)属性的值从一个或多个源对象复制到目标对象。它将返回修改后的目标对象(请注意这个操作是浅拷贝)。
Object.assign = function(target, ...source) { if(target == null) { throw new TypeError('Cannot convert undefined or null to object'); } let res = Object(target); source.forEach(function(obj) { if(obj != null) { // for...in 只会遍历对象自身的和继承的可枚举的属性(不含 Symbol 属性) // hasOwnProperty 方法只考虑对象自身的属性 for(let key in obj) { if(obj.hasOwnProperty(key)) { res[key] = obj[key]; } } } }); return res; }
5、New操作符
- 首先创建一个对象,对象原型指向构造函数原型;
- 其次调用构造函数,并将
this
绑定到该对象; -
最后构造函数执行返回值,如果是非引用类型,返回创建的对象,否则直接返回构造函数的返回值;
function myNew(Fn) { // ES6 中 new.target 指向构造函数 myNew.target = Fn // const obj = {} // obj.__proto__=Fn.prototype // 创建一个对象,对象原型指向构造函数原型 const obj = Object.create(Fn.prototype) // 调用构造函数,并将this绑定到该对象 const result = Fn.apply(obj, [...arguments]) // 构造函数执行返回值,如果是非引用类型,返回创建的对象,否则直接返回构造函数的返回值
returnresult instanceof Object ? result : obj
eg:new操作符返回一个对象时:这个返回值会被正常使用
function Test(name) { this.name = name console.log(this) // Test { name: 'xxx' } return { age: 26 } } const t = new Test('xxx') console.log(t) // { age: 26 } console.log(t.name) // 'undefined'
返回一个原始值时:构造函数中返回一个原始值,然而这个返回值并没有作用
function Test(name) { this.name = name return 1 } const t = new Test('xxx') console.log(t.name) // 'xxx'
new关键字主要做了以下工作:
-
创建一个新的对象
obj
-
将对象与构建函数通过原型链连接起来
-
将构建函数中的
this
绑定到新建的对象obj
上 -
根据构建函数返回类型作判断,如果是原始值则被忽略,如果是返回对象,需要正常处理
6、手写reduce
Array.prototype.myReduce = function (cb, initValue) { const array = this; // 获取数组 let pre = initValue || array[0]; const startIndex = initValue ? 1 : 0; for (let i = startIndex; i < array.length; i++) { const cur = array[i]; pre = cb(pre, cur, i, array); } return pre; };
7、手写call、apply和bind
Function.prototype.myCall = function (context, ...args) { if (!context || context === null) { context = window; } // 创造唯一的key值 作为我们构造的context内部方法名 let fn = Symbol(); context[fn] = this; //this指向调用call的函数 // 执行函数并返回结果 相当于把自身作为传入的context的方法进行调用了 return context[fn](...args); };
apply原理一致 只是第二个参数是传入的数组
Function.prototype.myApply = function (context, args) { if (!context || context === null) { context = window; } // 创造唯一的key值 作为我们构造的context内部方法名 let fn = Symbol(); context[fn] = this; // 执行函数并返回结果 return context[fn](...args); };
bind实现要复杂一点 因为他考虑的情况比较多 还要涉及到参数合并(类似函数柯里化)
Function.prototype.myBind = function (context, ...args) { if (!context || context === null) { context = window; } // 创造唯一的key值 作为我们构造的context内部方法名 let fn = Symbol(); context[fn] = this; let _this = this; // bind情况要复杂一点 const result = function (...innerArgs) { // 第一种情况 :若是将 bind 绑定之后的函数当作构造函数,通过 new 操作符使用,则不绑定传入的 this,而是将 this 指向实例化出来的对象 // 此时由于new操作符作用 this指向result实例对象 而result又继承自传入的_this 根据原型链知识可得出以下结论 // this.__proto__ === result.prototype //this instanceof result =>true // this.__proto__.__proto__ === result.prototype.__proto__ === _this.prototype; //this instanceof _this =>true if (this instanceof _this === true) { // 此时this指向指向result的实例 这时候不需要改变this指向 this[fn] = _this; this[fn](...[...args, ...innerArgs]); //这里使用es6的方法让bind支持参数合并 } else { // 如果只是作为普通函数调用 那就很简单了 直接改变this指向为传入的context context[fn](...[...args, ...innerArgs]); } }; // 如果绑定的是构造函数 那么需要继承构造函数原型属性和方法 // 实现继承的方式: 使用Object.create result.prototype = Object.create(this.prototype); return result; };
8、instanceof
function myInstanceof(left, right) { while (true) { if (left === null) { return false; } if (left.__proto__ === right.prototype) { return true; } left = left.__proto__; } }
9、ajax
const getJSON = function (url) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open("GET", url, false); xhr.setRequestHeader("Content-Type", "application/json"); xhr.onreadystatechange = function () { if (xhr.readyState !== 4) return; if (xhr.status === 200 || xhr.status === 304) { resolve(xhr.responseText); } else { reject(new Error(xhr.responseText)); } }; xhr.send(); }); };
10、sleep
function sleep(delay) { var start = (new Date()).getTime(); while ((new Date()).getTime() - start < delay) { continue; } } function test() { console.log('111'); sleep(2000); console.log('222'); } test()