共计 40300 个字符,预计需要花费 101 分钟才能阅读完成。
实现公布 - 订阅模式
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 六大状况用法
// 状况 1
Promise.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.3
function 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 所有 api
const 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, reject
const 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
}
// 提早对象用户
// ![](http://img-repo.poetries.top/images/20210509172817.png)
// 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)
// or
window.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); // 1
add(1)(2); // 3
add(1)(2)(3);// 6
add(1)(2, 3); // 6
add(1, 2)(3); // 6
add(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(); // 1
add(1)(2).toString(); // 3
add(1)(2)(3).toString();// 6
add(1)(2, 3).toString(); // 6
add(1, 2)(3).toString(); // 6
add(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') // 帮咱们执行 generator
const 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, __filename
Module._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);
}
}