安徽建设厅网站施北京网站优化指导
实现类的继承
实现类的继承-简版
类的继承在几年前是重点内容,有n种继承方式各有优劣,es6普及后越来越不重要,那么多种写法有点『回字有四样写法』的意思,如果还想深入理解的去看红宝书即可,我们目前只实现一种最理想的继承方式。
// 寄生组合继承
function Parent(name) {this.name = name
}
Parent.prototype.say = function() {console.log(this.name + ` say`);
}
Parent.prototype.play = function() {console.log(this.name + ` play`);
}function Child(name, parent) {// 将父类的构造函数绑定在子类上Parent.call(this, parent)this.name = name
}/** 1. 这一步不用Child.prototype = Parent.prototype的原因是怕共享内存,修改父类原型对象就会影响子类2. 不用Child.prototype = new Parent()的原因是会调用2次父类的构造方法(另一次是call),会存在一份多余的父类实例属性
3. Object.create是创建了父类原型的副本,与父类原型完全隔离
*/
Child.prototype = Object.create(Parent.prototype);
Child.prototype.say = function() {console.log(this.name + ` say`);
}// 注意记得把子类的构造指向子类本身
Child.prototype.constructor = Child;
// 测试
var parent = new Parent('parent');
parent.say() var child = new Child('child');
child.say()
child.play(); // 继承父类的方法
ES5实现继承-详细
第一种方式是借助call实现继承
function Parent1(){this.name = 'parent1';
}
function Child1(){Parent1.call(this);this.type = 'child1'
}
console.log(new Child1);
这样写的时候子类虽然能够拿到父类的属性值,但是问题是父类中一旦存在方法那么子类无法继承。那么引出下面的方法
第二种方式借助原型链实现继承:
function Parent2() {this.name = 'parent2';this.play = [1, 2, 3]}function Child2() {this.type = 'child2';}Child2.prototype = new Parent2();console.log(new Child2());
看似没有问题,父类的方法和属性都能够访问,但实际上有一个潜在的不足。举个例子:
var s1 = new Child2();var s2 = new Child2();s1.play.push(4);console.log(s1.play, s2.play); // [1,2,3,4] [1,2,3,4]
明明我只改变了s1的play属性,为什么s2也跟着变了呢?很简单,因为两个实例使用的是同一个原型对象
第三种方式:将前两种组合:
function Parent3 () {this.name = 'parent3';this.play = [1, 2, 3];}function Child3() {Parent3.call(this);this.type = 'child3';}Child3.prototype = new Parent3();var s3 = new Child3();var s4 = new Child3();s3.play.push(4);console.log(s3.play, s4.play); // [1,2,3,4] [1,2,3]
之前的问题都得以解决。但是这里又徒增了一个新问题,那就是Parent3的构造函数会多执行了一次(
Child3.prototype = new Parent3()
;)。这是我们不愿看到的。那么如何解决这个问题?
第四种方式: 组合继承的优化1
function Parent4 () {this.name = 'parent4';this.play = [1, 2, 3];}function Child4() {Parent4.call(this);this.type = 'child4';}Child4.prototype = Parent4.prototype;
这里让将父类原型对象直接给到子类,父类构造函数只执行一次,而且父类属性和方法均能访问,但是我们来测试一下
var s3 = new Child4();var s4 = new Child4();console.log(s3)
子类实例的构造函数是Parent4,显然这是不对的,应该是Child4。
第五种方式(最推荐使用):优化2
function Parent5 () {this.name = 'parent5';this.play = [1, 2, 3];}function Child5() {Parent5.call(this);this.type = 'child5';}Child5.prototype = Object.create(Parent5.prototype);Child5.prototype.constructor = Child5;
这是最推荐的一种方式,接近完美的继承。
实现instanceOf
思路:
- 步骤1:先取得当前类的原型,当前实例对象的原型链
- 步骤2:一直循环(执行原型链的查找机制)
- 取得当前实例对象原型链的原型链(
proto = proto.__proto__
,沿着原型链一直向上查找) - 如果 当前实例的原型链
__proto__
上找到了当前类的原型prototype
,则返回true
- 如果 一直找到
Object.prototype.__proto__ == null
,Object
的基类(null
)上面都没找到,则返回false
- 取得当前实例对象原型链的原型链(
// 实例.__ptoto__ === 类.prototype
function _instanceof(example, classFunc) {// 由于instance要检测的是某对象,需要有一个前置判断条件//基本数据类型直接返回falseif(typeof example !== 'object' || example === null) return false;let proto = Object.getPrototypeOf(example);while(true) {if(proto == null) return false;// 在当前实例对象的原型链上,找到了当前类if(proto == classFunc.prototype) return true;// 沿着原型链__ptoto__一层一层向上查proto = Object.getPrototypeof(proto); // 等于proto.__ptoto__}
}console.log('test', _instanceof(null, Array)) // false
console.log('test', _instanceof([], Array)) // true
console.log('test', _instanceof('', Array)) // false
console.log('test', _instanceof({}, Object)) // true
实现一个队列
基于链表结构实现队列
const LinkedList = require('./实现一个链表结构')// 用链表默认使用数组来模拟队列,性能更佳
class Queue {constructor() {this.ll = new LinkedList()}// 向队列中添加offer(elem) {this.ll.add(elem)}// 查看第一个peek() {return this.ll.get(0)}// 队列只能从头部删除remove() {return this.ll.remove(0)}
}var queue = new Queue()queue.offer(1)
queue.offer(2)
queue.offer(3)
var removeVal = queue.remove(3)console.log(queue.ll,'queue.ll')
console.log(removeVal,'queue.remove')
console.log(queue.peek(),'queue.peek')
实现every方法
Array.prototype.myEvery=function(callback, context = window){var len=this.length,flag=true,i = 0;for(;i < len; i++){if(!callback.apply(context,[this[i], i , this])){flag=false;break;} }return flag;}// var obj = {num: 1}// var aa=arr.myEvery(function(v,index,arr){// return v.num>=12;// },obj)// console.log(aa)
实现LRU淘汰算法
LRU
缓存算法是一个非常经典的算法,在很多面试中经常问道,不仅仅包括前端面试
LRU
英文全称是Least Recently Used
,英译过来就是” 最近最少使用 “的意思。LRU
是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间t
,当须淘汰一个页面时,选择现有页面中其t
值最大的,即最近最少使用的页面予以淘汰
通俗的解释:
假如我们有一块内存,专门用来缓存我们最近发访问的网页,访问一个新网页,我们就会往内存中添加一个网页地址,随着网页的不断增加,内存存满了,这个时候我们就需要考虑删除一些网页了。这个时候我们找到内存中最早访问的那个网页地址,然后把它删掉。这一整个过程就可以称之为
LRU
算法
上图就很好的解释了 LRU
算法在干嘛了,其实非常简单,无非就是我们往内存里面添加或者删除元素的时候,遵循最近最少使用原则
使用场景
LRU
算法使用的场景非常多,这里简单举几个例子即可:
- 我们操作系统底层的内存管理,其中就包括有
LRU
算法 - 我们常见的缓存服务,比如
redis
等等 - 比如浏览器的最近浏览记录存储
vue
中的keep-alive
组件使用了LRU
算法
梳理实现 LRU 思路
- 特点分析:
- 我们需要一块有限的存储空间,因为无限的化就没必要使用
LRU
算发删除数据了。 - 我们这块存储空间里面存储的数据需要是有序的,因为我们必须要顺序来删除数据,所以可以考虑使用
Array
、Map
数据结构来存储,不能使用Object
,因为它是无序的。 - 我们能够删除或者添加以及获取到这块存储空间中的指定数据。
- 存储空间存满之后,在添加数据时,会自动删除时间最久远的那条数据。
- 我们需要一块有限的存储空间,因为无限的化就没必要使用
- 实现需求:
- 实现一个
LRUCache
类型,用来充当存储空间 - 采用
Map
数据结构存储数据,因为它的存取时间复杂度为O(1)
,数组为O(n)
- 实现
get
和set
方法,用来获取和添加数据 - 我们的存储空间有长度限制,所以无需提供删除方法,存储满之后,自动删除最久远的那条数据
- 当使用
get
获取数据后,该条数据需要更新到最前面
- 实现一个
具体实现
class LRUCache {constructor(length) {this.length = length; // 存储长度this.data = new Map(); // 存储数据}// 存储数据,通过键值对的方式set(key, value) {const data = this.data;if (data.has(key)) {data.delete(key)}data.set(key, value);// 如果超出了容量,则需要删除最久的数据if (data.size > this.length) {const delKey = data.keys().next().value;data.delete(delKey);}}// 获取数据get(key) {const data = this.data;// 未找到if (!data.has(key)) {return null;}const value = data.get(key); // 获取元素data.delete(key); // 删除元素data.set(key, value); // 重新插入元素return value // 返回获取的值}
}
var lruCache = new LRUCache(5);
set 方法
:往map
里面添加新数据,如果添加的数据存在了,则先删除该条数据,然后再添加。如果添加数据后超长了,则需要删除最久远的一条数据。data.keys().next().value
便是获取最后一条数据的意思。get 方法
:首先从map
对象中拿出该条数据,然后删除该条数据,最后再重新插入该条数据,确保将该条数据移动到最前面
// 测试// 存储数据 set:lruCache.set('name', 'test');
lruCache.set('age', 10);
lruCache.set('sex', '男');
lruCache.set('height', 180);
lruCache.set('weight', '120');
console.log(lruCache);
继续插入数据,此时会超长,代码如下:
lruCache.set('grade', '100');
console.log(lruCache);
此时我们发现存储时间最久的 name 已经被移除了,新插入的数据变为了最前面的一个。
我们使用 get
获取数据,代码如下:
我们发现此时 sex
字段已经跑到最前面去了
总结
LRU
算法其实逻辑非常的简单,明白了原理之后实现起来非常的简单。最主要的是我们需要使用什么数据结构来存储数据,因为map
的存取非常快,所以我们采用了它,当然数组其实也可以实现的。还有一些小伙伴使用链表来实现LRU
,这当然也是可以的。
实现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); // undefinedreturn 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); // undefinedreturn 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); // undefinedreturn 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]可能不是一个promisePromise.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 必须返回一个新的 promisevar 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 () {// 异步执行onRejectedtry {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// 把值传给下个 thenresolutionProcedure(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,就 rejecttry {// 规范 2.3.3.1let then = x.then;// 如果 then 是函数,调用 x.thenif (typeof then === "function") {// 规范 2.3.3.3then.call(x,y => {if (called) return;called = true;// 规范 2.3.3.3.1resolutionProcedure(promise2, y, resolve, reject);},e => {if (called) return;called = true;reject(e);});} else {// 规范 2.3.3.4resolve(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是成功还是失败 传递到下层thenif(x === promise2) {reject(new TypeError('类型错误'))}// 这里的x如果是一个promise的话 可能是其他的promise,可能调用了成功 又调用了失败// 防止resolve的时候 又throw err抛出异常到reject了let called// 如果x是promise 那么就采用他的状态// 有then方法是promiseif(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.valuethen.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 {// 普通对象不是promiseresolve(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()) 需要解析valueif(value instanceof myPromise) {// 不停的解析 直到值不是promisereturn value.then(resolve,reject)}if(this.status === EMUM.PENDING) {this.status = EMUM.FULFILLEDthis.value = valuethis.onResolvedCallbacks.forEach(fn=>fn())}}const reject = reason =>{if(this.status === EMUM.PENDING) {this.status = EMUM.REJECTEDthis.reason = reasonthis.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 => vonRejected = typeof onRejected === 'function' ? onRejected : err => {throw err}// 调用then 创建一个新的promiselet promise2 = new myPromise((resolve,reject)=>{// 根据value判断是resolve 还是reject value也可能是promiseif(this.status === EMUM.FULFILLED) {setTimeout(() => {try {// 成功回调结果let x = onFulFilled(this.value)// 解析promiseresolvePromise(x, promise2,resolve,reject)} catch (error) {reject(error)}}, 0);}if(this.status === EMUM.REJECTED) {setTimeout(() => {try {let x = onRejected(this.reason)// 解析promiseresolvePromise(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)// 解析promiseresolvePromise(x, promise2,resolve,reject)} catch (error) {reject(error)}})this.onRejectedCallbacks.push(()=>{try {let x = onRejected(this.reason)// 解析promiseresolvePromise(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.allstatic all(values) {return new myPromise((resolve,reject)=>{let resultArr = []let orderIndex = 0const processResultByKey = (value,index)=>{resultArr[index] = value // 处理完全部if(++orderIndex === values.length) {resolve(resultArr) // 处理完成的结果返回去}}for (let i = 0; i < values.length; i++) {const value = values[i];// 是promiseif(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 abortlet newPromsie = new myPromise((resolve,reject)=>{abort = reject})// 只要控制newPromsie失败,就可以控制被包装的promise走向失败// Promise.race 任何一个先成功或者失败 就可以获得结果let p = myPromise.race([promise, newPromsie])p.abort = abortreturn 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 = resolvedfd.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
参考 前端进阶面试题详细解答
实现一个简易的MVVM
实现一个简易的
MVVM
我会分为这么几步来:
- 首先我会定义一个类
Vue
,这个类接收的是一个options
,那么其中可能有需要挂载的根元素的id
,也就是el
属性;然后应该还有一个data
属性,表示需要双向绑定的数据 - 其次我会定义一个
Dep
类,这个类产生的实例对象中会定义一个subs
数组用来存放所依赖这个属性的依赖,已经添加依赖的方法addSub
,删除方法removeSub
,还有一个notify
方法用来遍历更新它subs
中的所有依赖,同时Dep类有一个静态属性target
它用来表示当前的观察者,当后续进行依赖收集的时候可以将它添加到dep.subs
中。 - 然后设计一个
observe
方法,这个方法接收的是传进来的data
,也就是options.data
,里面会遍历data
中的每一个属性,并使用Object.defineProperty()
来重写它的get
和set
,那么这里面呢可以使用new Dep()
实例化一个dep
对象,在get
的时候调用其addSub
方法添加当前的观察者Dep.target
完成依赖收集,并且在set
的时候调用dep.notify
方法来通知每一个依赖它的观察者进行更新 - 完成这些之后,我们还需要一个
compile
方法来将HTML模版和数据结合起来。在这个方法中首先传入的是一个node
节点,然后遍历它的所有子级,判断是否有firstElmentChild
,有的话则进行递归调用compile方法,没有firstElementChild
的话且该child.innderHTML
用正则匹配满足有/\{\{(.*)\}\}/
项的话则表示有需要双向绑定的数据,那么就将用正则new Reg('\\{\\{\\s*' + key + '\\s*\\}\\}', 'gm')
替换掉是其为msg
变量。 - 完成变量替换的同时,还需要将
Dep.target
指向当前的这个child
,且调用一下this.opt.data[key]
,也就是为了触发这个数据的get
来对当前的child
进行依赖收集,这样下次数据变化的时候就能通知child
进行视图更新了,不过在最后要记得将Dep.target
指为null
哦(其实在Vue
中是有一个targetStack
栈用来存放target
的指向的) - 那么最后我们只需要监听
document
的DOMContentLoaded
然后在回调函数中实例化这个Vue
对象就可以了
coding :
需要注意的点:
childNodes
会获取到所有的子节点以及文本节点(包括元素标签中的空白节点)firstElementChild
表示获取元素的第一个字元素节点,以此来区分是不是元素节点,如果是的话则调用compile
进行递归调用,否则用正则匹配- 这里面的正则真的不难,大家可以看一下
完整代码如下:
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta http-equiv="X-UA-Compatible" content="ie=edge" /><title>MVVM</title></head><body><div id="app"><h3>姓名</h3><p>{{name}}</p><h3>年龄</h3><p>{{age}}</p></div></body>
</html>
<script>document.addEventListener("DOMContentLoaded",function () {let opt = { el: "#app", data: { name: "等待修改...", age: 20 } };let vm = new Vue(opt);setTimeout(() => {opt.data.name = "jing";}, 2000);},false);class Vue {constructor(opt) {this.opt = opt;this.observer(opt.data);let root = document.querySelector(opt.el);this.compile(root);}observer(data) {Object.keys(data).forEach((key) => {let obv = new Dep();data["_" + key] = data[key];Object.defineProperty(data, key, {get() {Dep.target && obv.addSubNode(Dep.target);return data["_" + key];},set(newVal) {obv.update(newVal);data["_" + key] = newVal;},});});}compile(node) {[].forEach.call(node.childNodes, (child) => {if (!child.firstElementChild && /\{\{(.*)\}\}/.test(child.innerHTML)) {let key = RegExp.$1.trim();child.innerHTML = child.innerHTML.replace(new RegExp("\\{\\{\\s*" + key + "\\s*\\}\\}", "gm"),this.opt.data[key]);Dep.target = child;this.opt.data[key];Dep.target = null;} else if (child.firstElementChild) this.compile(child);});}}class Dep {constructor() {this.subNode = [];}addSubNode(node) {this.subNode.push(node);}update(newVal) {this.subNode.forEach((node) => {node.innerHTML = newVal;});}}
</script>
简化版2
function update(){console.log('数据变化~~~ mock update view')
}
let obj = [1,2,3]
// 变异方法 push shift unshfit reverse sort splice pop
// Object.defineProperty
let oldProto = Array.prototype;
let proto = Object.create(oldProto); // 克隆了一分
['push','shift'].forEach(item=>{proto[item] = function(){update();oldProto[item].apply(this,arguments);}
})
function observer(value){ // proxy reflectif(Array.isArray(value)){// AOPreturn value.__proto__ = proto;// 重写 这个数组里的push shift unshfit reverse sort splice pop}if(typeof value !== 'object'){return value;}for(let key in value){defineReactive(value,key,value[key]);}
}
function defineReactive(obj,key,value){observer(value); // 如果是对象 继续增加getter和setterObject.defineProperty(obj,key,{get(){return value;},set(newValue){if(newValue !== value){observer(newValue);value = newValue;update();}}})
}
observer(obj);
// AOP
// obj.name = {n:200}; // 数据变了 需要更新视图 深度监控
// obj.name.n = 100;
obj.push(123);
obj.push(456);
console.log(obj);
实现JSONP方法
利用
<script>
标签不受跨域限制的特点,缺点是只能支持get
请求
- 创建
script
标签 - 设置
script
标签的src
属性,以问号传递参数,设置好回调函数callback
名称 - 插入到
html
文本中 - 调用回调函数,
res
参数就是获取的数据
function jsonp({url,params,callback}) {return new Promise((resolve,reject)=>{let script = document.createElement('script')window[callback] = function (data) {resolve(data)document.body.removeChild(script)}var arr = []for(var key in params) {arr.push(`${key}=${params[key]}`)}script.type = 'text/javascript'script.src = `${url}?callback=${callback}&${arr.join('&')}`document.body.appendChild(script)})
}
// 测试用例
jsonp({url: 'http://suggest.taobao.com/sug',callback: 'getData',params: {q: 'iphone手机',code: 'utf-8'},
}).then(data=>{console.log(data)})
- 设置
CORS: Access-Control-Allow-Origin:*
postMessage
实现Ajax
步骤
- 创建
XMLHttpRequest
实例 - 发出 HTTP 请求
- 服务器返回 XML 格式的字符串
- JS 解析 XML,并更新局部页面
- 不过随着历史进程的推进,XML 已经被淘汰,取而代之的是 JSON。
了解了属性和方法之后,根据 AJAX 的步骤,手写最简单的 GET 请求。
对象数组列表转成树形结构(处理菜单)
[{id: 1,text: '节点1',parentId: 0 //这里用0表示为顶级节点},{id: 2,text: '节点1_1',parentId: 1 //通过这个字段来确定子父级}...
]转成
[{id: 1,text: '节点1',parentId: 0,children: [{id:2,text: '节点1_1',parentId:1}]}
]
实现代码如下:
function listToTree(data) {let temp = {};let treeData = [];for (let i = 0; i < data.length; i++) {temp[data[i].id] = data[i];}for (let i in temp) {if (+temp[i].parentId != 0) {if (!temp[temp[i].parentId].children) {temp[temp[i].parentId].children = [];}temp[temp[i].parentId].children.push(temp[i]);} else {treeData.push(temp[i]);}}return treeData;
}
异步串行 | 异步并行
// 字节面试题,实现一个异步加法
function asyncAdd(a, b, callback) {setTimeout(function () {callback(null, a + b);}, 500);
}// 解决方案
// 1. promisify
const promiseAdd = (a, b) => new Promise((resolve, reject) => {asyncAdd(a, b, (err, res) => {if (err) {reject(err)} else {resolve(res)}})
})// 2. 串行处理
async function serialSum(...args) {return args.reduce((task, now) => task.then(res => promiseAdd(res, now)), Promise.resolve(0))
}// 3. 并行处理
async function parallelSum(...args) {if (args.length === 1) return args[0]const tasks = []for (let i = 0; i < args.length; i += 2) {tasks.push(promiseAdd(args[i], args[i + 1] || 0))}const results = await Promise.all(tasks)return parallelSum(...results)
}// 测试
(async () => {console.log('Running...');const res1 = await serialSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12)console.log(res1)const res2 = await parallelSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12)console.log(res2)console.log('Done');
})()
手写深度比较isEqual
思路:深度比较两个对象,就是要深度比较对象的每一个元素。=> 递归
- 递归退出条件:
- 被比较的是两个值类型变量,直接用“===”判断
- 被比较的两个变量之一为
null
,直接判断另一个元素是否也为null
- 提前结束递推:
- 两个变量
keys
数量不同 - 传入的两个参数是同一个变量
- 两个变量
- 递推工作:深度比较每一个
key
function isEqual(obj1, obj2){//其中一个为值类型或nullif(!isObject(obj1) || !isObject(obj2)){return obj1 === obj2;}//判断是否两个参数是同一个变量if(obj1 === obj2){return true;}//判断keys数是否相等const obj1Keys = Object.keys(obj1);const obj2Keys = Object.keys(obj2);if(obj1Keys.length !== obj2Keys.length){return false;}//深度比较每一个keyfor(let key in obj1){if(!isEqual(obj1[key], obj2[key])){return false;}}return true;
}
数组中的数据根据key去重
给定一个任意数组,实现一个通用函数,让数组中的数据根据 key 排重:
const dedup = (data, getKey = () => {} ) => {// todo
}
let data = [{ id: 1, v: 1 },{ id: 2, v: 2 },{ id: 1, v: 1 },
];// 以 id 作为排重 key,执行函数得到结果
// data = [
// { id: 1, v: 1 },
// { id: 2, v: 2 },
// ];
实现
const dedup = (data, getKey = () => { }) => {const dateMap = data.reduce((pre, cur) => {const key = getKey(cur)if (!pre[key]) {pre[key] = cur}return pre}, {})return Object.values(dateMap)
}
使用
let data = [{ id: 1, v: 1 },{ id: 2, v: 2 },{ id: 1, v: 1 },
];
console.log(dedup(data, (item) => item.id))// 以 id 作为排重 key,执行函数得到结果
// data = [
// { id: 1, v: 1 },
// { id: 2, v: 2 },
// ];
实现节流函数(throttle)
节流函数原理:指频繁触发事件时,只会在指定的时间段内执行事件回调,即触发事件间隔大于等于指定的时间才会执行回调函数。总结起来就是: 事件,按照一段时间的间隔来进行触发 。
像dom的拖拽,如果用消抖的话,就会出现卡顿的感觉,因为只在停止的时候执行了一次,这个时候就应该用节流,在一定时间内多次执行,会流畅很多
手写简版
使用时间戳的节流函数会在第一次触发事件时立即执行,以后每过 wait 秒之后才执行一次,并且最后一次触发事件不会被执行
时间戳方式:
// func是用户传入需要防抖的函数
// wait是等待时间
const throttle = (func, wait = 50) => {// 上一次执行该函数的时间let lastTime = 0return function(...args) {// 当前时间let now = +new Date()// 将当前时间和上一次执行函数时间对比// 如果差值大于设置的等待时间就执行函数if (now - lastTime > wait) {lastTime = nowfunc.apply(this, args)}}
}setInterval(throttle(() => {console.log(1)}, 500),1
)
定时器方式:
使用定时器的节流函数在第一次触发时不会执行,而是在 delay 秒之后才执行,当最后一次停止触发后,还会再执行一次函数
function throttle(func, delay){var timer = null;returnfunction(){var context = this;var args = arguments;if(!timer){timer = setTimeout(function(){func.apply(context, args);timer = null;},delay);}}
}
适用场景:
DOM
元素的拖拽功能实现(mousemove
)- 搜索联想(
keyup
) - 计算鼠标移动的距离(
mousemove
) Canvas
模拟画板功能(mousemove
)- 监听滚动事件判断是否到页面底部自动加载更多
- 拖拽场景:固定时间内只执行一次,防止超高频次触发位置变动
- 缩放场景:监控浏览器
resize
- 动画场景:避免短时间内多次触发动画引起性能问题
总结
- 函数防抖 :将几次操作合并为一次操作进行。原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。
- 函数节流 :使得一定时间内只触发一次函数。原理是通过判断是否到达一定时间来触发函数。
实现一个 sleep 函数,比如 sleep(1000) 意味着等待1000毫秒
// 使用 promise来实现 sleep
const sleep = (time) => {return new Promise(resolve => setTimeout(resolve, time))
}sleep(1000).then(() => {// 这里写你的骚操作
})
树形结构转成列表(处理菜单)
[{id: 1,text: '节点1',parentId: 0,children: [{id:2,text: '节点1_1',parentId:1}]}
]
转成
[{id: 1,text: '节点1',parentId: 0 //这里用0表示为顶级节点},{id: 2,text: '节点1_1',parentId: 1 //通过这个字段来确定子父级}...
]
实现代码如下:
function treeToList(data) {let res = [];const dfs = (tree) => {tree.forEach((item) => {if (item.children) {dfs(item.children);delete item.children;}res.push(item);});};dfs(data);return res;
}
实现lodash的chunk方法–数组按指定长度拆分
题目
/*** @param input* @param size* @returns {Array}*/
_.chunk(['a', 'b', 'c', 'd'], 2)
// => [['a', 'b'], ['c', 'd']]_.chunk(['a', 'b', 'c', 'd'], 3)
// => [['a', 'b', 'c'], ['d']]_.chunk(['a', 'b', 'c', 'd'], 5)
// => [['a', 'b', 'c', 'd']]_.chunk(['a', 'b', 'c', 'd'], 0)
// => []
实现
function chunk(arr, length) {let newArr = [];for (let i = 0; i < arr.length; i += length) {newArr.push(arr.slice(i, i + length));}return newArr;
}
设计一个方法提取对象中所有value大于2的键值对并返回最新的对象
实现:
var obj = { a: 1, b: 3, c: 4 }
foo(obj) // { b: 3, c: 4 }
方法有很多种,这里提供一种比较简洁的写法,用到了ES10
的Object.fromEntries()
:
var obj = { a: 1, b: 3, c: 4 }
function foo (obj) {return Object.fromEntries(Object.entries(obj).filter(([key, value]) => value > 2))
}
var obj2 = foo(obj) // { b: 3, c: 4 }
console.log(obj2)
// ES8中 Object.entries()的作用:
var obj = { a: 1, b: 2 }
var entries = Object.entries(obj); // [['a', 1], ['b', 2]]
// ES10中 Object.fromEntries()的作用:
Object.fromEntries(entries); // { a: 1, b: 2 }
实现一个链表结构
链表结构
看图理解next层级
// 链表 从头尾删除、增加 性能比较好
// 分为很多类 常用单向链表、双向链表// js模拟链表结构:增删改查// node节点
class Node {constructor(element,next) {this.element = elementthis.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.headthis.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.headthis.head = this.head.next // 移动指针位置return head // 返回删除的元素}else {let current = this.headfor (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.headfor (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
数组去重方法汇总
首先:我知道多少种去重方式
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) === indexreturn 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