实现公布-订阅模式
class EventCenter{ // 1. 定义事件容器,用来装事件数组 let handlers = {} // 2. 增加事件办法,参数:事件名 事件办法 addEventListener(type, handler) { // 创立新数组容器 if (!this.handlers[type]) { this.handlers[type] = [] } // 存入事件 this.handlers[type].push(handler) } // 3. 触发事件,参数:事件名 事件参数 dispatchEvent(type, params) { // 若没有注册该事件则抛出谬误 if (!this.handlers[type]) { return new Error('该事件未注册') } // 触发事件 this.handlers[type].forEach(handler => { handler(...params) }) } // 4. 事件移除,参数:事件名 要删除事件,若无第二个参数则删除该事件的订阅和公布 removeEventListener(type, handler) { if (!this.handlers[type]) { return new Error('事件有效') } if (!handler) { // 移除事件 delete this.handlers[type] } else { const index = this.handlers[type].findIndex(el => el === handler) if (index === -1) { return new Error('无该绑定事件') } // 移除事件 this.handlers[type].splice(index, 1) if (this.handlers[type].length === 0) { delete this.handlers[type] } } }}
判断对象是否存在循环援用
循环援用对象原本没有什么问题,然而序列化的时候就会产生问题,比方调用JSON.stringify()
对该类对象进行序列化,就会报错: Converting circular structure to JSON.
上面办法能够用来判断一个对象中是否已存在循环援用:
const isCycleObject = (obj,parent) => { const parentArr = parent || [obj]; for(let i in obj) { if(typeof obj[i] === 'object') { let flag = false; parentArr.forEach((pObj) => { if(pObj === obj[i]){ flag = true; } }) if(flag) return true; flag = isCycleObject(obj[i],[...parentArr,obj[i]]); if(flag) return true; } } return false;}const a = 1;const b = {a};const c = {b};const o = {d:{a:3},c}o.c.b.aa = a;console.log(isCycleObject(o)
查找有序二维数组的目标值:
var findNumberIn2DArray = function(matrix, target) { if (matrix == null || matrix.length == 0) { return false; } let row = 0; let column = matrix[0].length - 1; while (row < matrix.length && column >= 0) { if (matrix[row][column] == target) { return true; } else if (matrix[row][column] > target) { column--; } else { row++; } } return false;};
二维数组斜向打印:
function printMatrix(arr){ let m = arr.length, n = arr[0].length let res = [] // 左上角,从0 到 n - 1 列进行打印 for (let k = 0; k < n; k++) { for (let i = 0, j = k; i < m && j >= 0; i++, j--) { res.push(arr[i][j]); } } // 右下角,从1 到 n - 1 行进行打印 for (let k = 1; k < m; k++) { for (let i = k, j = n - 1; i < m && j >= 0; i++, j--) { res.push(arr[i][j]); } } return res}
手写 Promise
const PENDING = "pending";const RESOLVED = "resolved";const REJECTED = "rejected";function MyPromise(fn) { // 保留初始化状态 var self = this; // 初始化状态 this.state = PENDING; // 用于保留 resolve 或者 rejected 传入的值 this.value = null; // 用于保留 resolve 的回调函数 this.resolvedCallbacks = []; // 用于保留 reject 的回调函数 this.rejectedCallbacks = []; // 状态转变为 resolved 办法 function resolve(value) { // 判断传入元素是否为 Promise 值,如果是,则状态扭转必须期待前一个状态扭转后再进行扭转 if (value instanceof MyPromise) { return value.then(resolve, reject); } // 保障代码的执行程序为本轮事件循环的开端 setTimeout(() => { // 只有状态为 pending 时能力转变, if (self.state === PENDING) { // 批改状态 self.state = RESOLVED; // 设置传入的值 self.value = value; // 执行回调函数 self.resolvedCallbacks.forEach(callback => { callback(value); }); } }, 0); } // 状态转变为 rejected 办法 function reject(value) { // 保障代码的执行程序为本轮事件循环的开端 setTimeout(() => { // 只有状态为 pending 时能力转变 if (self.state === PENDING) { // 批改状态 self.state = REJECTED; // 设置传入的值 self.value = value; // 执行回调函数 self.rejectedCallbacks.forEach(callback => { callback(value); }); } }, 0); } // 将两个办法传入函数执行 try { fn(resolve, reject); } catch (e) { // 遇到谬误时,捕捉谬误,执行 reject 函数 reject(e); }}MyPromise.prototype.then = function(onResolved, onRejected) { // 首先判断两个参数是否为函数类型,因为这两个参数是可选参数 onResolved = typeof onResolved === "function" ? onResolved : function(value) { return value; }; onRejected = typeof onRejected === "function" ? onRejected : function(error) { throw error; }; // 如果是期待状态,则将函数退出对应列表中 if (this.state === PENDING) { this.resolvedCallbacks.push(onResolved); this.rejectedCallbacks.push(onRejected); } // 如果状态曾经凝固,则间接执行对应状态的函数 if (this.state === RESOLVED) { onResolved(this.value); } if (this.state === REJECTED) { onRejected(this.value); }};
实现Promise相干办法
实现Promise的resolve
实现 resolve 静态方法有三个要点:
- 传参为一个
Promise
, 则间接返回它。 - 传参为一个
thenable
对象,返回的Promise
会追随这个对象,采纳它的最终状态作为本人的状态。 - 其余状况,间接返回以该值为胜利状态的
promise
对象。
Promise.resolve = (param) => { if(param instanceof Promise) return param; return new Promise((resolve, reject) => { if(param && param.then && typeof param.then === 'function') { // param 状态变为胜利会调用resolve,将新 Promise 的状态变为胜利,反之亦然 param.then(resolve, reject); }else { resolve(param); } })}
实现 Promise.reject
Promise.reject 中传入的参数会作为一个 reason 一成不变地往下传, 实现如下:
Promise.reject = function (reason) { return new Promise((resolve, reject) => { reject(reason); });}
实现 Promise.prototype.finally
后面的promise
不论胜利还是失败,都会走到finally
中,并且finally
之后,还能够持续then
(阐明它还是一个then办法是要害),并且会将初始的promise
值一成不变的传递给前面的then
.
Promise.prototype.finally最大的作用
finally
里的函数,无论如何都会执行,并会把后面的值一成不变传递给下一个then
办法中- 如果
finally
函数中有promise
等异步工作,会等它们全副执行结束,再联合之前的胜利与否状态,返回值
Promise.prototype.finally六大状况用法
// 状况1Promise.resolve(123).finally((data) => { // 这里传入的函数,无论如何都会执行 console.log(data); // undefined})// 状况2 (这里,finally办法相当于做了两头解决,起一个过渡的作用)Promise.resolve(123).finally((data) => { console.log(data); // undefined}).then(data => { console.log(data); // 123})// 状况3 (这里只有reject,都会走到下一个then的err中)Promise.reject(123).finally((data) => { console.log(data); // undefined}).then(data => { console.log(data);}, err => { console.log(err, 'err'); // 123 err})// 状况4 (一开始就胜利之后,会期待finally里的promise执行结束后,再把后面的data传递到下一个then中)Promise.resolve(123).finally((data) => { console.log(data); // undefined return new Promise((resolve, reject) => { setTimeout(() => { resolve('ok'); }, 3000) })}).then(data => { console.log(data, 'success'); // 123 success}, err => { console.log(err, 'err');})// 状况5 (尽管一开始胜利,然而只有finally函数中的promise失败了,就会把其失败的值传递到下一个then的err中)Promise.resolve(123).finally((data) => { console.log(data); // undefined return new Promise((resolve, reject) => { setTimeout(() => { reject('rejected'); }, 3000) })}).then(data => { console.log(data, 'success');}, err => { console.log(err, 'err'); // rejected err})// 状况6 (尽管一开始失败,然而也要等finally中的promise执行完,能力把一开始的err传递到err的回调中)Promise.reject(123).finally((data) => { console.log(data); // undefined return new Promise((resolve, reject) => { setTimeout(() => { resolve('resolve'); }, 3000) })}).then(data => { console.log(data, 'success');}, err => { console.log(err, 'err'); // 123 err})
源码实现
Promise.prototype.finally = function (callback) { return this.then((data) => { // 让函数执行 外部会调用办法,如果办法是promise,须要期待它实现 // 如果以后promise执行时失败了,会把err传递到,err的回调函数中 return Promise.resolve(callback()).then(() => data); // data 上一个promise的胜利态 }, err => { return Promise.resolve(callback()).then(() => { throw err; // 把之前的失败的err,抛出去 }); })}
实现 Promise.all
对于 all 办法而言,须要实现上面的外围性能:
- 传入参数为一个空的可迭代对象,则间接进行
resolve
。 - 如果参数中有一个
promise
失败,那么Promise.all
返回的promise
对象失败。 - 在任何状况下,
Promise.all
返回的promise
的实现状态的后果都是一个数组
Promise.all = function(promises) { return new Promise((resolve, reject) => { let result = []; let index = 0; let len = promises.length; if(len === 0) { resolve(result); return; } for(let i = 0; i < len; i++) { // 为什么不间接 promise[i].then, 因为promise[i]可能不是一个promise Promise.resolve(promise[i]).then(data => { result[i] = data; index++; if(index === len) resolve(result); }).catch(err => { reject(err); }) } })}
实现promise.allsettle
MDN:Promise.allSettled()
办法返回一个在所有给定的promise
都曾经
fulfilled或
rejected后的
promise,并带有一个对象数组,每个对象示意对应的
promise`后果
当您有多个彼此不依赖的异步工作胜利实现时,或者您总是想晓得每个promise
的后果时,通常应用它。
【译】Promise.allSettled
跟Promise.all
相似, 其参数承受一个Promise
的数组, 返回一个新的Promise
, 惟一的不同在于, 其不会进行短路, 也就是说当Promise全副解决实现后咱们能够拿到每个Promise
的状态, 而不论其是否解决胜利。
用法 | 测试用例
let fs = require('fs').promises;let getName = fs.readFile('./name.txt', 'utf8'); // 读取文件胜利let getAge = fs.readFile('./age.txt', 'utf8');Promise.allSettled([1, getName, getAge, 2]).then(data => { console.log(data);});// 输入后果/* [ { status: 'fulfilled', value: 1 }, { status: 'fulfilled', value: 'zf' }, { status: 'fulfilled', value: '11' }, { status: 'fulfilled', value: 2 } ]*/let getName = fs.readFile('./name123.txt', 'utf8'); // 读取文件失败let getAge = fs.readFile('./age.txt', 'utf8');// 输入后果/* [ { status: 'fulfilled', value: 1 }, { status: 'rejected', value: [Error: ENOENT: no such file or directory, open './name123.txt'] { errno: -2, code: 'ENOENT', syscall: 'open', path: './name123.txt' } }, { status: 'fulfilled', value: '11' }, { status: 'fulfilled', value: 2 } ]*/
实现
function isPromise (val) { return typeof val.then === 'function'; // (123).then => undefined}Promise.allSettled = function(promises) { return new Promise((resolve, reject) => { let arr = []; let times = 0; const setData = (index, data) => { arr[index] = data; if (++times === promises.length) { resolve(arr); } console.log('times', times) } for (let i = 0; i < promises.length; i++) { let current = promises[i]; if (isPromise(current)) { current.then((data) => { setData(i, { status: 'fulfilled', value: data }); }, err => { setData(i, { status: 'rejected', value: err }) }) } else { setData(i, { status: 'fulfilled', value: current }) } } })}
实现 Promise.race
race 的实现相比之下就简略一些,只有有一个 promise 执行完,间接 resolve 并进行执行
Promise.race = function(promises) { return new Promise((resolve, reject) => { let len = promises.length; if(len === 0) return; for(let i = 0; i < len; i++) { Promise.resolve(promise[i]).then(data => { resolve(data); return; }).catch(err => { reject(err); return; }) } })}
实现一个简版Promise
// 应用var promise = new Promise((resolve,reject) => { if (操作胜利) { resolve(value) } else { reject(error) }})promise.then(function (value) { // success},function (value) { // failure})
function myPromise(constructor) { let self = this; self.status = "pending" // 定义状态扭转前的初始状态 self.value = undefined; // 定义状态为resolved的时候的状态 self.reason = undefined; // 定义状态为rejected的时候的状态 function resolve(value) { if(self.status === "pending") { self.value = value; self.status = "resolved"; } } function reject(reason) { if(self.status === "pending") { self.reason = reason; self.status = "rejected"; } } // 捕捉结构异样 try { constructor(resolve,reject); } catch(e) { reject(e); }}
// 增加 then 办法myPromise.prototype.then = function(onFullfilled,onRejected) { let self = this; switch(self.status) { case "resolved": onFullfilled(self.value); break; case "rejected": onRejected(self.reason); break; default: }}var p = new myPromise(function(resolve,reject) { resolve(1)});p.then(function(x) { console.log(x) // 1})
应用class实现
class MyPromise { constructor(fn) { this.resolvedCallbacks = []; this.rejectedCallbacks = []; this.state = 'PENDING'; this.value = ''; fn(this.resolve.bind(this), this.reject.bind(this)); } resolve(value) { if (this.state === 'PENDING') { this.state = 'RESOLVED'; this.value = value; this.resolvedCallbacks.map(cb => cb(value)); } } reject(value) { if (this.state === 'PENDING') { this.state = 'REJECTED'; this.value = value; this.rejectedCallbacks.map(cb => cb(value)); } } then(onFulfilled, onRejected) { if (this.state === 'PENDING') { this.resolvedCallbacks.push(onFulfilled); this.rejectedCallbacks.push(onRejected); } if (this.state === 'RESOLVED') { onFulfilled(this.value); } if (this.state === 'REJECTED') { onRejected(this.value); } }}
Promise 实现-具体
- 能够把
Promise
看成一个状态机。初始是pending
状态,能够通过函数resolve
和reject
,将状态转变为resolved
或者rejected
状态,状态一旦扭转就不能再次变动。 then
函数会返回一个Promise
实例,并且该返回值是一个新的实例而不是之前的实例。因为Promise
标准规定除了pending
状态,其余状态是不能够扭转的,如果返回的是一个雷同实例的话,多个then
调用就失去意义了。- 对于
then
来说,实质上能够把它看成是flatMap
// 三种状态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.3function 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); }}
实现Promisify
const fs = require('fs')const path = require('path')// node中应用// const fs = require('fs').promises 12.18版// const promisify = require('util').promisify// 包装node api promise化 典型的高级函数const promisify = fn=>{ return (...args)=>{ return new Promise((resolve,reject)=>{ fn(...args, (err,data)=>{ if(err) { reject(err) } resolve(data) }) }) }}// const read = promisify(fs.readFile)// read(path.join(__dirname, './promise.js'), 'utf8').then(d=>{// console.log(d)// })// promise化node所有apiconst promisifyAll = target=>{ Reflect.ownKeys(target).forEach(key=>{ if(typeof target[key] === 'function') { target[key+'Async'] = promisify(target[key]) } }) return target}// promise化fs下的函数const promisifyNew = promisifyAll(fs)promisifyNew.readFileAsync(path.join(__dirname, './promise.js'), 'utf8').then(d=>{ console.log(d)})module.exports = { promisify, promisifyAll}
残缺实现Promises/A+标准
/** * Promises/A+标准 实现一个promise * https://promisesaplus.com/*/const EMUM = { PENDING: 'PENDING', FULFILLED: 'FULFILLED', REJECTED: 'REJECTED'}// x 返回值// promise2 then的时候new的promise// promise2的resolve, rejectconst resolvePromise = (x, promise2, resolve, reject)=>{ // 解析promise的值解析promise2是胜利还是失败 传递到上层then if(x === promise2) { reject(new TypeError('类型谬误')) } // 这里的x如果是一个promise的话 可能是其余的promise,可能调用了胜利 又调用了失败 // 避免resolve的时候 又throw err抛出异样到reject了 let called // 如果x是promise 那么就采纳他的状态 // 有then办法是promise if(typeof x === 'object' && typeof x!== null || typeof x === 'function') { // x是对象或函数 try { let then = x.then // 缓存,不必屡次取值 if(typeof then === 'function') { // 是promise,调用then办法外面有this,须要传入this为x能力取到then办法外面的值this.value then.call(x, y=>{// 胜利 // y值可能也是一个promise 如resolve(new Promise()) 此时的y==new Promise() // 递归解析y,直到拿到一般的值resolve(x进来) if(called) return; called = true; resolvePromise(y, promise2, resolve, reject) },r=>{// 一旦失败间接失败 if(called) return; called = true; reject(r) }) } else { // 一般对象不是promise resolve(x) } } catch (e) { // 对象取值可能报错,用defineProperty定义get 抛出异样 if(called) return; called = true; reject(e) } } else { // x是一般值 resolve(x) // 间接胜利 }}class myPromise { constructor(executor) { this.status = EMUM.PENDING // 以后状态 this.value = undefined // resolve接管值 this.reason = undefined // reject失败返回值 /** * 同一个promise能够then屡次(公布订阅模式) * 调用then时 以后状态是期待态,须要将以后胜利或失败的回调寄存起来(订阅) * 调用resolve时 将订阅函数进行执行(公布) */ // 胜利队列 this.onResolvedCallbacks = [] // 失败队列 this.onRejectedCallbacks = [] const resolve = value =>{ // 如果value是一个promise,须要递归解析 // 如 myPromise.resolve(new myPromise()) 须要解析value if(value instanceof myPromise) { // 不停的解析 直到值不是promise return value.then(resolve,reject) } if(this.status === EMUM.PENDING) { this.status = EMUM.FULFILLED this.value = value this.onResolvedCallbacks.forEach(fn=>fn()) } } const reject = reason =>{ if(this.status === EMUM.PENDING) { this.status = EMUM.REJECTED this.reason = reason this.onRejectedCallbacks.forEach(fn=>fn()) } } try { executor(resolve,reject) } catch(e) { reject(e) } } then(onFulFilled, onRejected) { // 透传 解决默认不传的状况 // new Promise((resolve,reject)=>{ // resolve(1) // }).then().then().then(d=>{}) // new Promise((resolve,reject)=>{ // resolve(1) // }).then(v=>v).then(v=>v).then(d=>{}) // new Promise((resolve,reject)=>{ // reject(1) // }).then().then().then(null, e=>{console.log(e)}) // new Promise((resolve,reject)=>{ // reject(1) // }).then(null,e=>{throw e}).then(null,e=>{throw e}).then(null,e=>{console.log(e)}) onFulFilled = typeof onFulFilled === 'function' ? onFulFilled : v => v onRejected = typeof onRejected === 'function' ? onRejected : err => {throw err} // 调用then 创立一个新的promise let promise2 = new myPromise((resolve,reject)=>{ // 依据value判断是resolve 还是reject value也可能是promise if(this.status === EMUM.FULFILLED) { setTimeout(() => { try { // 胜利回调后果 let x = onFulFilled(this.value) // 解析promise resolvePromise(x, promise2,resolve,reject) } catch (error) { reject(error) } }, 0); } if(this.status === EMUM.REJECTED) { setTimeout(() => { try { let x = onRejected(this.reason) // 解析promise resolvePromise(x, promise2,resolve,reject) } catch (error) { reject(error) } }, 0); } // 用户还未调用resolve或reject办法 if(this.status === EMUM.PENDING) { this.onResolvedCallbacks.push(()=>{ try { let x = onFulFilled(this.value) // 解析promise resolvePromise(x, promise2,resolve,reject) } catch (error) { reject(error) } }) this.onRejectedCallbacks.push(()=>{ try { let x = onRejected(this.reason) // 解析promise resolvePromise(x, promise2,resolve,reject) } catch (error) { reject(error) } }) } }) return promise2 } catch(errCallback) { // 等同于没有胜利,把失败放进去而已 return this.then(null, errCallback) } // myPromise.resolve 具备期待性能的 如果参数的promise会期待promise解析结束在向下执行 static resolve(val) { return new myPromise((resolve,reject)=>{ resolve(val) }) } // myPromise.reject 间接将值返回 static reject(reason) { return new myPromise((resolve,reject)=>{ reject(reason) }) } // finally传入的函数 无论胜利或失败都执行 // Promise.reject(100).finally(()=>{console.log(1)}).then(d=>console.log('success',d)).catch(er=>console.log('faild',er)) // Promise.reject(100).finally(()=>new Promise()).then(d=>console.log(d)).catch(er=>) finally(callback) { return this.then((val)=>{ return myPromise.resolve(callback()).then(()=>val) },(err)=>{ return myPromise.resolve(callback()).then(()=>{throw err}) }) } // Promise.all static all(values) { return new myPromise((resolve,reject)=>{ let resultArr = [] let orderIndex = 0 const processResultByKey = (value,index)=>{ resultArr[index] = value // 解决齐全部 if(++orderIndex === values.length) { resolve(resultArr) // 解决实现的后果返回去 } } for (let i = 0; i < values.length; i++) { const value = values[i]; // 是promise if(value && typeof value.then === 'function') { value.then((val)=>{ processResultByKey(val,i) },reject) } else { // 不是promise状况 processResultByKey(value,i) } } }) } static race(promises) { // 采纳最新胜利或失败的作为后果 return new myPromise((resolve,reject)=>{ for (let i = 0; i < promises.length; i++) { let val = promises[i] if(val && typeof val.then === 'function') { // 任何一个promise先调用resolve或reject就返回后果了 也就是返回执行最快的那个promise的后果 val.then(resolve,reject) }else{ // 一般值 resolve(val) } } }) }}/** * =====测试用例-==== */// let promise1 = new myPromise((resolve,reject)=>{// setTimeout(() => {// resolve('胜利')// }, 900);// })// promise1.then(val=>{// console.log('success', val)// },reason=>{// console.log('fail', reason)// })/** * then的应用形式 一般值象征不是promise * * 1、then中的回调有两个办法 胜利或失败 他们的后果返回(一般值)会传递给外层的下一个then中 * 2、能够在胜利或失败中抛出异样,走到下一次then的失败中 * 3、返回的是一个promsie,那么会用这个promise的状态作为后果,会用promise的后果向下传递 * 4、错误处理,会默认先找离本人最新的错误处理,找不到就向下查找,找打了就执行 */// read('./name.txt').then(data=>{// return '123'// }).then(data=>{// }).then(null,err=>{// })// // .catch(err=>{ // catch就是没有胜利的promise// // })/** * promise.then实现原理:通过每次返回一个新的promise来实现(promise一旦胜利就不能失败,失败就不能胜利) * */// function read(data) {// return new myPromise((resolve,reject)=>{// setTimeout(() => {// resolve(new myPromise((resolve,reject)=>resolve(data)))// }, 1000);// })// }// let promise2 = read({name: 'poetry'}).then(data=>{// return data// }).then().then().then(data=>{// console.log(data,'-data-')// },(err)=>{// console.log(err,'-err-')// })// finally测试// myPromise// .resolve(100)// .finally(()=>{// return new myPromise((resolve,reject)=>setTimeout(() => {// resolve(100)// }, 100))// })// .then(d=>console.log('finally success',d))// .catch(er=>console.log(er, 'finally err'))/** * promise.all 测试 * * myPromise.all 解决并发问题 多个异步并发获取最终的后果*/// myPromise.all([1,2,3,4,new myPromise((resolve,reject)=>{// setTimeout(() => {// resolve('ok1')// }, 1000);// }),new myPromise((resolve,reject)=>{// setTimeout(() => {// resolve('ok2')// }, 1000);// })]).then(d=>{// console.log(d,'myPromise.all.resolve')// }).catch(err=>{// console.log(err,'myPromise.all.reject')// })// 实现promise中断请求let promise = new Promise((resolve,reject)=>{ setTimeout(() => { // 模仿接口调用 ajax调用超时 resolve('胜利') }, 10000);})function promiseWrap(promise) { // 包装一个promise 能够管制原来的promise是胜利 还是失败 let abort let newPromsie = new myPromise((resolve,reject)=>{ abort = reject }) // 只有管制newPromsie失败,就能够管制被包装的promise走向失败 // Promise.race 任何一个先胜利或者失败 就能够取得后果 let p = myPromise.race([promise, newPromsie]) p.abort = abort return p}let newPromise = promiseWrap(promise)setTimeout(() => { // 超过3秒超时 newPromise.abort('申请超时')}, 3000);newPromise.then(d=>{ console.log('d',d)}).catch(err=>{ console.log('err',err)})// 应用promises-aplus-tests 测试写的promise是否标准// 全局装置 cnpm i -g promises-aplus-tests// 命令行执行 promises-aplus-tests promise.js// 测试入口 产生提早对象myPromise.defer = myPromise.deferred = function () { let dfd = {} dfd.promise = new myPromise((resolve,reject)=>{ dfd.resolve = resolve dfd.reject = reject }) return dfd}// 提早对象用户// // promise解决嵌套问题// function readData(url) {// let dfd = myPromise.defer()// fs.readFile(url, 'utf8', function (err,data) {// if(err) {// dfd.reject()// }// dfd.resolve(data)// })// return dfd.promise// }// readData().then(d=>{// return d// })module.exports = myPromise
实现观察者模式
观察者模式(基于公布订阅模式) 有观察者,也有被观察者
观察者须要放到被观察者中,被观察者的状态变动须要告诉观察者 我变动了 外部也是基于公布订阅模式,收集观察者,状态变动后要被动告诉观察者
class Subject { // 被观察者 学生 constructor(name) { this.state = 'happy' this.observers = []; // 存储所有的观察者 } // 收集所有的观察者 attach(o){ // Subject. prototype. attch this.observers.push(o) } // 更新被观察者 状态的办法 setState(newState) { this.state = newState; // 更新状态 // this 指被观察者 学生 this.observers.forEach(o => o.update(this)) // 告诉观察者 更新它们的状态 }}class Observer{ // 观察者 父母和老师 constructor(name) { this.name = name } update(student) { console.log('以后' + this.name + '被告诉了', '以后学生的状态是' + student.state) }}let student = new Subject('学生'); let parent = new Observer('父母'); let teacher = new Observer('老师'); // 被观察者存储观察者的前提,须要先接收观察者student. attach(parent); student. attach(teacher); student. setState('被欺侮了');
图片懒加载
// <img src="default.png" data-src="https://xxxx/real.png">function isVisible(el) { const position = el.getBoundingClientRect() const windowHeight = document.documentElement.clientHeight // 顶部边缘可见 const topVisible = position.top > 0 && position.top < windowHeight; // 底部边缘可见 const bottomVisible = position.bottom < windowHeight && position.bottom > 0; return topVisible || bottomVisible;}function imageLazyLoad() { const images = document.querySelectorAll('img') for (let img of images) { const realSrc = img.dataset.src if (!realSrc) continue if (isVisible(img)) { img.src = realSrc img.dataset.src = '' } }}// 测试window.addEventListener('load', imageLazyLoad)window.addEventListener('scroll', imageLazyLoad)// orwindow.addEventListener('scroll', throttle(imageLazyLoad, 1000))
参考 前端进阶面试题具体解答
实现一个JSON.parse
JSON.parse(text[, reviver])
用来解析JSON字符串,结构由字符串形容的JavaScript值或对象。提供可选的reviver函数用以在返回之前对所失去的对象执行变换(操作)
第一种:间接调用 eval
function jsonParse(opt) { return eval('(' + opt + ')');}jsonParse(jsonStringify({x : 5}))// Object { x: 5}jsonParse(jsonStringify([1, "false", false]))// [1, "false", falsr]jsonParse(jsonStringify({b: undefined}))// Object { b: "undefined"}
防止在不必要的状况下应用eval
,eval()
是一个危险的函数,他执行的代码领有着执行者的权力。如果你用eval()
运行的字符串代码被歹意方(不怀好意的人)操控批改,您最终可能会在您的网页/扩大程序的权限下,在用户计算机上运行恶意代码。它会执行JS代码,有XSS破绽。
如果你只想记这个办法,就得对参数json做校验。
var rx_one = /^[\],:{}\s]*$/;var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;var rx_four = /(?:^|:|,)(?:\s*\[)+/g;if ( rx_one.test( json .replace(rx_two, "@") .replace(rx_three, "]") .replace(rx_four, "") )) { var obj = eval("(" +json + ")");}
第二种:Function
外围:Function与eval有雷同的字符串参数个性
var func = new Function(arg1, arg2, ..., functionBody);
在转换JSON的理论利用中,只须要这么做
var jsonStr = '{ "age": 20, "name": "jack" }'var json = (new Function('return ' + jsonStr))();
eval
与Function
都有着动静编译js代码的作用,然而在理论的编程中并不举荐应用
请实现一个 add 函数,满足以下性能
add(1); // 1add(1)(2); // 3add(1)(2)(3);// 6add(1)(2, 3); // 6add(1, 2)(3); // 6add(1, 2, 3); // 6
function add(...args) { // 在外部申明一个函数,利用闭包的个性保留并收集所有的参数值 let fn = function(...newArgs) { return add.apply(null, args.concat(newArgs)) } // 利用toString隐式转换的个性,当最初执行时隐式转换,并计算最终的值返回 fn.toString = function() { return args.reduce((total,curr)=> total + curr) } return fn}
考点:
- 应用闭包, 同时要对JavaScript 的作用域链(原型链)有深刻的了解
- 重写函数的
toSting()
办法
// 测试,调用toString办法触发求值add(1).toString(); // 1add(1)(2).toString(); // 3add(1)(2)(3).toString();// 6add(1)(2, 3).toString(); // 6add(1, 2)(3).toString(); // 6add(1, 2, 3).toString(); // 6
实现一个链表构造
链表构造
看图了解next层级
// 链表 从头尾删除、减少 性能比拟好// 分为很多类 罕用单向链表、双向链表// js模仿链表构造:增删改查// node节点class Node { constructor(element,next) { this.element = element this.next = next } }class LinkedList { constructor() { this.head = null // 默认应该指向第一个节点 this.size = 0 // 通过这个长度能够遍历这个链表 } // 减少O(n) add(index,element) { if(arguments.length === 1) { // 向开端增加 element = index // 以后元素等于传递的第一项 index = this.size // 索引指向最初一个元素 } if(index < 0 || index > this.size) { throw new Error('增加的索引不失常') } if(index === 0) { // 间接找到头部 把头部改掉 性能更好 let head = this.head this.head = new Node(element,head) } else { // 获取以后头指针 let current = this.head // 不停遍历 直到找到最初一项 增加的索引是1就找到第0个的next赋值 for (let i = 0; i < index-1; i++) { // 找到它的前一个 current = current.next } // 让创立的元素指向上一个元素的下一个 // 看图了解next层级 current.next = new Node(element,current.next) // 让以后元素指向下一个元素的next } this.size++; } // 删除O(n) remove(index) { if(index < 0 || index >= this.size) { throw new Error('删除的索引不失常') } this.size-- if(index === 0) { let head = this.head this.head = this.head.next // 挪动指针地位 return head // 返回删除的元素 }else { let current = this.head for (let i = 0; i < index-1; i++) { // index-1找到它的前一个 current = current.next } let returnVal = current.next // 返回删除的元素 // 找到待删除的指针的上一个 current.next.next // 如删除200, 100=>200=>300 找到200的上一个100的next的next为300,把300赋值给100的next即可 current.next = current.next.next return returnVal } } // 查找O(n) get(index) { if(index < 0 || index >= this.size) { throw new Error('查找的索引不失常') } let current = this.head for (let i = 0; i < index; i++) { current = current.next } return current }}var ll = new LinkedList()ll.add(0,100) // Node { ellement: 100, next: null }ll.add(0,200) // Node { element: 200, next: Node { element: 100, next: null } }ll.add(1,500) // Node {element: 200,next: Node { element: 100, next: Node { element: 500, next: null } } }ll.add(300)ll.remove(0)console.log(ll.get(2),'get')console.log(ll.head)module.exports = LinkedList
实现Object.is
Object.is
不会转换被比拟的两个值的类型,这点和===
更为类似,他们之间也存在一些区别
NaN
在===
中是不相等的,而在Object.is
中是相等的+0
和-
0在===
中是相等的,而在Object.is
中是不相等的
Object.is = function (x, y) { if (x === y) { // 当前情况下,只有一种状况是非凡的,即 +0 -0 // 如果 x !== 0,则返回true // 如果 x === 0,则须要判断+0和-0,则能够间接应用 1/+0 === Infinity 和 1/-0 === -Infinity来进行判断 return x !== 0 || 1 / x === 1 / y; } // x !== y 的状况下,只须要判断是否为NaN,如果x!==x,则阐明x是NaN,同理y也一样 // x和y同时为NaN时,返回true return x !== x && y !== y;};
实现ES6的const
因为ES5环境没有block
的概念,所以是无奈百分百实现const
,只能是挂载到某个对象下,要么是全局的windo
w,要么就是自定义一个object
来当容器
var __const = function __const (data, value) { window.data = value // 把要定义的data挂载到window下,并赋值value Object.defineProperty(window, data, { // 利用Object.defineProperty的能力劫持以后对象,并批改其属性描述符 enumerable: false, configurable: false, get: function () { return value }, set: function (data) { if (data !== value) { // 当要对以后属性进行赋值时,则抛出谬误! throw new TypeError('Assignment to constant variable.') } else { return value } } }) } __const('a', 10) console.log(a) delete a console.log(a) for (let item in window) { // 因为const定义的属性在global下也是不存在的,所以用到了enumerable: false来模仿这一性能 if (item === 'a') { // 因为不可枚举,所以不执行 console.log(window[item]) } } a = 20 // 报错
Vue
目前双向绑定的外围实现思路就是利用Object.defineProperty
对get
跟set
进行劫持,监听用户对属性进行调用以及赋值时的具体情况,从而实现的双向绑定
实现apply办法
思路: 利用this
的上下文个性。apply
其实就是改一下参数的问题
Function.prototype.myApply = function(context = window, args) { // this-->func context--> obj args--> 传递过去的参数 // 在context上加一个惟一值不影响context上的属性 let key = Symbol('key') context[key] = this; // context为调用的上下文,this此处为函数,将这个函数作为context的办法 // let args = [...arguments].slice(1) //第一个参数为obj所以删除,伪数组转为数组 let result = context[key](...args); // 这里和call传参不一样 // 革除定义的this 不删除会导致context属性越来越多 delete context[key]; // 返回后果 return result;}
// 应用function f(a,b){ console.log(a,b) console.log(this.name)}let obj={ name:'张三'}f.myApply(obj,[1,2]) //arguments[1]
实现async/await
剖析
// generator生成器 生成迭代器iterator// 默认这样写的类数组是不能被迭代的,短少迭代办法let likeArray = {'0': 1, '1': 2, '2': 3, '3': 4, length: 4}// // 应用迭代器使得能够开展数组// // Symbol有很多元编程办法,能够改js自身性能// likeArray[Symbol.iterator] = function () {// // 迭代器是一个对象 对象中有next办法 每次调用next 都须要返回一个对象 {value,done}// let index = 0// return {// next: ()=>{// // 会主动调用这个办法// console.log('index',index)// return {// // this 指向likeArray// value: this[index],// done: index++ === this.length// }// }// }// }// let arr = [...likeArray]// console.log('arr', arr)// 应用生成器返回迭代器// likeArray[Symbol.iterator] = function *() {// let index = 0// while (index != this.length) {// yield this[index++]// }// }// let arr = [...likeArray]// console.log('arr', arr)// 生成器 碰到yield就会暂停// function *read(params) {// yield 1;// yield 2;// }// 生成器返回的是迭代器// let it = read()// console.log(it.next())// console.log(it.next())// console.log(it.next())// 通过generator来优化promise(promise的毛病是不停的链式调用)const fs = require('fs')const path = require('path')// const co = require('co') // 帮咱们执行generatorconst promisify = fn=>{ return (...args)=>{ return new Promise((resolve,reject)=>{ fn(...args, (err,data)=>{ if(err) { reject(err) } resolve(data) }) }) }}// promise化let asyncReadFile = promisify(fs.readFile)function * read() { let content1 = yield asyncReadFile(path.join(__dirname,'./data/name.txt'),'utf8') let content2 = yield asyncReadFile(path.join(__dirname,'./data/' + content1),'utf8') return content2}// 这样写太繁琐 须要借助co来实现// let re = read()// let {value,done} = re.next()// value.then(data=>{// // 除了第一次传参没有意义外 剩下的传参都赋予了上一次的返回值 // let {value,done} = re.next(data) // value.then(d=>{// let {value,done} = re.next(d)// console.log(value,done)// })// }).catch(err=>{// re.throw(err) // 手动抛出谬误 能够被try catch捕捉// })// 实现co原理function co(it) {// it 迭代器 return new Promise((resolve,reject)=>{ // 异步迭代 须要依据函数来实现 function next(data) { // 递归得有停止条件 let {value,done} = it.next(data) if(done) { resolve(value) // 间接让promise变成胜利 用以后返回的后果 } else { // Promise.resolve(value).then(data=>{ // next(data) // }).catch(err=>{ // reject(err) // }) // 简写 Promise.resolve(value).then(next,reject) } } // 首次调用 next() })}co(read()).then(d=>{ console.log(d)}).catch(err=>{ console.log(err,'--')})
整体看一下构造
function asyncToGenerator(generatorFunc) { return function() { const gen = generatorFunc.apply(this, arguments) return new Promise((resolve, reject) => { function step(key, arg) { let generatorResult try { generatorResult = gen[key](arg) } catch (error) { return reject(error) } const { value, done } = generatorResult if (done) { return resolve(value) } else { return Promise.resolve(value).then(val => step('next', val), err => step('throw', err)) } } step("next") }) }}
剖析
function asyncToGenerator(generatorFunc) { // 返回的是一个新的函数 return function() { // 先调用generator函数 生成迭代器 // 对应 var gen = testG() const gen = generatorFunc.apply(this, arguments) // 返回一个promise 因为内部是用.then的形式 或者await的形式去应用这个函数的返回值的 // var test = asyncToGenerator(testG) // test().then(res => console.log(res)) return new Promise((resolve, reject) => { // 外部定义一个step函数 用来一步一步的跨过yield的妨碍 // key有next和throw两种取值,别离对应了gen的next和throw办法 // arg参数则是用来把promise resolve进去的值交给下一个yield function step(key, arg) { let generatorResult // 这个办法须要包裹在try catch中 // 如果报错了 就把promise给reject掉 内部通过.catch能够获取到谬误 try { generatorResult = gen[key](arg) } catch (error) { return reject(error) } // gen.next() 失去的后果是一个 { value, done } 的构造 const { value, done } = generatorResult if (done) { // 如果曾经实现了 就间接resolve这个promise // 这个done是在最初一次调用next后才会为true // 以本文的例子来说 此时的后果是 { done: true, value: 'success' } // 这个value也就是generator函数最初的返回值 return resolve(value) } else { // 除了最初完结的时候外,每次调用gen.next() // 其实是返回 { value: Promise, done: false } 的构造, // 这里要留神的是Promise.resolve能够承受一个promise为参数 // 并且这个promise参数被resolve的时候,这个then才会被调用 return Promise.resolve( // 这个value对应的是yield前面的promise value ).then( // value这个promise被resove的时候,就会执行next // 并且只有done不是true的时候 就会递归的往下解开promise // 对应gen.next().value.then(value => { // gen.next(value).value.then(value2 => { // gen.next() // // // 此时done为true了 整个promise被resolve了 // // 最内部的test().then(res => console.log(res))的then就开始执行了 // }) // }) function onResolve(val) { step("next", val) }, // 如果promise被reject了 就再次进入step函数 // 不同的是,这次的try catch中调用的是gen.throw(err) // 那么天然就被catch到 而后把promise给reject掉啦 function onReject(err) { step("throw", err) }, ) } } step("next") }) }}
数组去重办法汇总
首先:我晓得多少种去重形式
1. 双层 for 循环
function distinct(arr) { for (let i=0, len=arr.length; i<len; i++) { for (let j=i+1; j<len; j++) { if (arr[i] == arr[j]) { arr.splice(j, 1); // splice 会扭转数组长度,所以要将数组长度 len 和下标 j 减一 len--; j--; } } } return arr;}
思维: 双重for
循环是比拟蠢笨的办法,它实现的原理很简略:先定义一个蕴含原始数组第一个元素的数组,而后遍历原始数组,将原始数组中的每个元素与新数组中的每个元素进行比对,如果不反复则增加到新数组中,最初返回新数组;因为它的工夫复杂度是O(n^2)
,如果数组长度很大,效率会很低
2. Array.filter() 加 indexOf/includes
function distinct(a, b) { let arr = a.concat(b); return arr.filter((item, index)=> { //return arr.indexOf(item) === index return arr.includes(item) })}
思维: 利用indexOf
检测元素在数组中第一次呈现的地位是否和元素当初的地位相等,如果不等则阐明该元素是反复元素
3. ES6 中的 Set 去重
function distinct(array) { return Array.from(new Set(array));}
思维: ES6 提供了新的数据结构 Set,Set 构造的一个个性就是成员值都是惟一的,没有反复的值。
4. reduce 实现对象数组去反复
var resources = [ { name: "张三", age: "18" }, { name: "张三", age: "19" }, { name: "张三", age: "20" }, { name: "李四", age: "19" }, { name: "王五", age: "20" }, { name: "赵六", age: "21" }]var temp = {};resources = resources.reduce((prev, curv) => { // 如果长期对象中有这个名字,什么都不做 if (temp[curv.name]) { }else { // 如果长期对象没有就把这个名字加进去,同时把以后的这个对象退出到prev中 temp[curv.name] = true; prev.push(curv); } return prev}, []);console.log("后果", resources);
这种办法是利用高阶函数reduce
进行去重, 这里只须要留神initialValue
得放一个空数组[],不然没法push
深克隆(deepclone)
简略版:
const newObj = JSON.parse(JSON.stringify(oldObj));
局限性:
- 他无奈实现对函数 、RegExp等非凡对象的克隆
- 会摈弃对象的constructor,所有的构造函数会指向Object
- 对象有循环援用,会报错
面试版:
/** * deep clone * @param {[type]} parent object 须要进行克隆的对象 * @return {[type]} 深克隆后的对象 */const clone = parent => { // 判断类型 const isType = (obj, type) => { if (typeof obj !== "object") return false; const typeString = Object.prototype.toString.call(obj); let flag; switch (type) { case "Array": flag = typeString === "[object Array]"; break; case "Date": flag = typeString === "[object Date]"; break; case "RegExp": flag = typeString === "[object RegExp]"; break; default: flag = false; } return flag; }; // 解决正则 const getRegExp = re => { var flags = ""; if (re.global) flags += "g"; if (re.ignoreCase) flags += "i"; if (re.multiline) flags += "m"; return flags; }; // 保护两个贮存循环援用的数组 const parents = []; const children = []; const _clone = parent => { if (parent === null) return null; if (typeof parent !== "object") return parent; let child, proto; if (isType(parent, "Array")) { // 对数组做非凡解决 child = []; } else if (isType(parent, "RegExp")) { // 对正则对象做非凡解决 child = new RegExp(parent.source, getRegExp(parent)); if (parent.lastIndex) child.lastIndex = parent.lastIndex; } else if (isType(parent, "Date")) { // 对Date对象做非凡解决 child = new Date(parent.getTime()); } else { // 解决对象原型 proto = Object.getPrototypeOf(parent); // 利用Object.create切断原型链 child = Object.create(proto); } // 解决循环援用 const index = parents.indexOf(parent); if (index != -1) { // 如果父数组存在本对象,阐明之前曾经被援用过,间接返回此对象 return children[index]; } parents.push(parent); children.push(child); for (let i in parent) { // 递归 child[i] = _clone(parent[i]); } return child; }; return _clone(parent);};
局限性:
- 一些非凡状况没有解决: 例如Buffer对象、Promise、Set、Map
- 另外对于确保没有循环援用的对象,咱们能够省去对循环援用的非凡解决,因为这很耗费工夫
原理详解实现深克隆
实现数组去重
给定某无序数组,要求去除数组中的反复数字并且返回新的无反复数组。
ES6办法(应用数据结构汇合):
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];Array.from(new Set(array)); // [1, 2, 3, 5, 9, 8]
ES5办法:应用map存储不反复的数字
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];uniqueArray(array); // [1, 2, 3, 5, 9, 8]function uniqueArray(array) { let map = {}; let res = []; for(var i = 0; i < array.length; i++) { if(!map.hasOwnProperty([array[i]])) { map[array[i]] = 1; res.push(array[i]); } } return res;}
实现一下hash路由
根底的html
代码:
<html> <style> html, body { margin: 0; height: 100%; } ul { list-style: none; margin: 0; padding: 0; display: flex; justify-content: center; } .box { width: 100%; height: 100%; background-color: red; } </style> <body> <ul> <li> <a href="#red">红色</a> </li> <li> <a href="#green">绿色</a> </li> <li> <a href="#purple">紫色</a> </li> </ul> </body></html>
简略实现:
<script> const box = document.getElementsByClassName('box')[0]; const hash = location.hash window.onhashchange = function (e) { const color = hash.slice(1) box.style.background = color }</script>
封装成一个class:
<script> const box = document.getElementsByClassName('box')[0]; const hash = location.hash class HashRouter { constructor (hashStr, cb) { this.hashStr = hashStr this.cb = cb this.watchHash() this.watch = this.watchHash.bind(this) window.addEventListener('hashchange', this.watch) } watchHash () { let hash = window.location.hash.slice(1) this.hashStr = hash this.cb(hash) } } new HashRouter('red', (color) => { box.style.background = color })</script>
实现Node的require办法
require 基本原理
require 查找门路
require
和module.exports
干的事件并不简单,咱们先假如有一个全局对象{}
,初始状况下是空的,当你require
某个文件时,就将这个文件拿进去执行,如果这个文件外面存在module.exports
,当运行到这行代码时将module.exports
的值退出这个对象,键为对应的文件名,最终这个对象就长这样:
{ "a.js": "hello world", "b.js": function add(){}, "c.js": 2, "d.js": { num: 2 }}
当你再次require
某个文件时,如果这个对象外面有对应的值,就间接返回给你,如果没有就反复后面的步骤,执行指标文件,而后将它的module.exports
退出这个全局对象,并返回给调用者。这个全局对象其实就是咱们常常据说的缓存。所以require
和module.exports
并没有什么黑魔法,就只是运行并获取指标文件的值,而后退出缓存,用的时候拿进去用就行
手写实现一个require
const path = require('path'); // 门路操作const fs = require('fs'); // 文件读取const vm = require('vm'); // 文件执行// node模块化的实现// node中是自带模块化机制的,每个文件就是一个独自的模块,并且它遵循的是CommonJS标准,也就是应用require的形式导入模块,通过module.export的形式导出模块。// node模块的运行机制也很简略,其实就是在每一个模块外层包裹了一层函数,有了函数的包裹就能够实现代码间的作用域隔离// require加载模块// require依赖node中的fs模块来加载模块文件,fs.readFile读取到的是一个字符串。// 在javascrpt中咱们能够通过eval或者new Function的形式来将一个字符串转换成js代码来运行。// eval// const name = 'poetry';// const str = 'const a = 123; console.log(name)';// eval(str); // poetry;// new Function// new Function接管的是一个要执行的字符串,返回的是一个新的函数,调用这个新的函数字符串就会执行了。如果这个函数须要传递参数,能够在new Function的时候顺次传入参数,最初传入的是要执行的字符串。比方这里传入参数b,要执行的字符串str// const b = 3;// const str = 'let a = 1; return a + b';// const fun = new Function('b', str);// console.log(fun(b, str)); // 4// 能够看到eval和Function实例化都能够用来执行javascript字符串,仿佛他们都能够来实现require模块加载。不过在node中并没有选用他们来实现模块化,起因也很简略因为他们都有一个致命的问题,就是都容易被不属于他们的变量所影响。// 如下str字符串中并没有定义a,然而确能够应用下面定义的a变量,这显然是不对的,在模块化机制中,str字符串应该具备本身独立的运行空间,本身不存在的变量是不能够间接应用的// const a = 1;// const str = 'console.log(a)';// eval(str);// const func = new Function(str);// func();// node存在一个vm虚拟环境的概念,用来运行额定的js文件,他能够保障javascript执行的独立性,不会被内部所影响// vm 内置模块// 尽管咱们在内部定义了hello,然而str是一个独立的模块,并不在村hello变量,所以会间接报错。// 引入vm模块, 不须要装置,node 自建模块// const vm = require('vm');// const hello = 'poetry';// const str = 'console.log(hello)';// wm.runInThisContext(str); // 报错// 所以node执行javascript模块时能够采纳vm来实现。就能够保障模块的独立性了// 剖析实现步骤// 1.导入相干模块,创立一个Require办法。// 2.抽离通过Module._load办法,用于加载模块。// 3.Module.resolveFilename 依据相对路径,转换成绝对路径。// 4.缓存模块 Module._cache,同一个模块不要反复加载,晋升性能。// 5.创立模块 id: 保留的内容是 exports = {}相当于this。// 6.利用tryModuleLoad(module, filename) 尝试加载模块。// 7.Module._extensions应用读取文件。// 8.Module.wrap: 把读取到的js包裹一个函数。// 9.将拿到的字符串应用runInThisContext运行字符串。// 10.让字符串执行并将this改编成exports// 定义导入类,参数为模块门路function Require(modulePath) { // 获取以后要加载的绝对路径 let absPathname = path.resolve(__dirname, modulePath); // 主动给模块增加后缀名,实现省略后缀名加载模块,其实也就是如果文件没有后缀名的时候遍历一下所有的后缀名看一下文件是否存在 // 获取所有后缀名 const extNames = Object.keys(Module._extensions); let index = 0; // 存储原始文件门路 const oldPath = absPathname; function findExt(absPathname) { if (index === extNames.length) { throw new Error('文件不存在'); } try { fs.accessSync(absPathname); return absPathname; } catch(e) { const ext = extNames[index++]; findExt(oldPath + ext); } } // 递归追加后缀名,判断文件是否存在 absPathname = findExt(absPathname); // 从缓存中读取,如果存在,间接返回后果 if (Module._cache[absPathname]) { return Module._cache[absPathname].exports; } // 创立模块,新建Module实例 const module = new Module(absPathname); // 增加缓存 Module._cache[absPathname] = module; // 加载以后模块 tryModuleLoad(module); // 返回exports对象 return module.exports;}// Module的实现很简略,就是给模块创立一个exports对象,tryModuleLoad执行的时候将内容退出到exports中,id就是模块的绝对路径// 定义模块, 增加文件id标识和exports属性function Module(id) { this.id = id; // 读取到的文件内容会放在exports中 this.exports = {};}Module._cache = {};// 咱们给Module挂载动态属性wrapper,外面定义一下这个函数的字符串,wrapper是一个数组,数组的第一个元素就是函数的参数局部,其中有exports,module. Require,__dirname, __filename, 都是咱们模块中罕用的全局变量。留神这里传入的Require参数是咱们本人定义的Require// 第二个参数就是函数的完结局部。两局部都是字符串,应用的时候咱们将他们包裹在模块的字符串内部就能够了Module.wrapper = [ "(function(exports, module, Require, __dirname, __filename) {", "})"]// _extensions用于针对不同的模块扩展名应用不同的加载形式,比方JSON和javascript加载形式必定是不同的。JSON应用JSON.parse来运行。// javascript应用vm.runInThisContext来运行,能够看到fs.readFileSync传入的是module.id也就是咱们Module定义时候id存储的是模块的绝对路径,读取到的content是一个字符串,咱们应用Module.wrapper来包裹一下就相当于在这个模块内部又包裹了一个函数,也就实现了公有作用域。// 应用call来执行fn函数,第一个参数扭转运行的this咱们传入module.exports,前面的参数就是函数里面包裹参数exports, module, Require, __dirname, __filenameModule._extensions = { '.js'(module) { const content = fs.readFileSync(module.id, 'utf8'); const fnStr = Module.wrapper[0] + content + Module.wrapper[1]; const fn = vm.runInThisContext(fnStr); fn.call(module.exports, module.exports, module, Require,__filename,__dirname); }, '.json'(module) { const json = fs.readFileSync(module.id, 'utf8'); module.exports = JSON.parse(json); // 把文件的后果放在exports属性上 }}// tryModuleLoad函数接管的是模块对象,通过path.extname来获取模块的后缀名,而后应用Module._extensions来加载模块// 定义模块加载办法function tryModuleLoad(module) { // 获取扩展名 const extension = path.extname(module.id); // 通过后缀加载以后模块 Module._extensions[extension](module);}// 至此Require加载机制咱们根本就写完了,咱们来从新看一下。Require加载模块的时候传入模块名称,在Require办法中应用path.resolve(__dirname, modulePath)获取到文件的绝对路径。而后通过new Module实例化的形式创立module对象,将模块的绝对路径存储在module的id属性中,在module中创立exports属性为一个json对象// 应用tryModuleLoad办法去加载模块,tryModuleLoad中应用path.extname获取到文件的扩展名,而后依据扩展名来执行对应的模块加载机制// 最终将加载到的模块挂载module.exports中。tryModuleLoad执行结束之后module.exports曾经存在了,间接返回就能够了// 给模块增加缓存// 增加缓存也比较简单,就是文件加载的时候将文件放入缓存中,再去加载模块时先看缓存中是否存在,如果存在间接应用,如果不存在再去从新,加载之后再放入缓存// 测试let json = Require('./test.json');let test2 = Require('./test2.js');console.log(json);console.log(test2);
实现JSON.parse
var json = '{"name":"cxk", "age":25}';var obj = eval("(" + json + ")");
此办法属于黑魔法,极易容易被xss攻打,还有一种new Function
大同小异。
手写 instanceof 办法
instanceof 运算符用于判断构造函数的 prototype 属性是否呈现在对象的原型链中的任何地位。
实现步骤:
- 首先获取类型的原型
- 而后取得对象的原型
- 而后始终循环判断对象的原型是否等于类型的原型,直到对象原型为
null
,因为原型链最终为null
具体实现:
function myInstanceof(left, right) { let proto = Object.getPrototypeOf(left), // 获取对象的原型 prototype = right.prototype; // 获取构造函数的 prototype 对象 // 判断构造函数的 prototype 对象是否在对象的原型链上 while (true) { if (!proto) return false; if (proto === prototype) return true; proto = Object.getPrototypeOf(proto); }}