实现类的继承
实现类的继承-简版
类的继承在几年前是重点内容,有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要检测的是某对象,须要有一个前置判断条件
//根本数据类型间接返回false
if(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); // 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
}
// 提早对象用户
// 
// 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 reflect
if(Array.isArray(value)){
// AOP
return 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和setter
Object.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){
//其中一个为值类型或null
if(!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;
}
//深度比拟每一个key
for(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 = 0
return function(...args) {
// 以后工夫
let now = +new Date()
// 将以后工夫和上一次执行函数工夫比照
// 如果差值大于设置的等待时间就执行函数
if (now - lastTime > wait) {
lastTime = now
func.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 = 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
数组去重办法汇总
首先:我晓得多少种去重形式
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
发表回复