建设部网站下载,长沙马拉松线上,企业网站建设有几种,网易企业邮箱彻底删除的邮件还能恢复吗实现类的继承
实现类的继承-简版 类的继承在几年前是重点内容#xff0c;有n种继承方式各有优劣#xff0c;es6普及后越来越不重要#xff0c;那么多种写法有点『回字有四样写法』的意思#xff0c;如果还想深入理解的去看红宝书即可#xff0c;我们目前只实现一种最理想…实现类的继承
实现类的继承-简版 类的继承在几年前是重点内容有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__ nullObject的基类(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.myEveryfunction(callback, context window){var lenthis.length,flagtrue,i 0;for(;i len; i){if(!callback.apply(context,[this[i], i , this])){flagfalse;break;} }return flag;}// var obj {num: 1}// var aaarr.myEvery(function(v,index,arr){// return v.num12;// },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 对象中拿出该条数据然后删除该条数据最后再重新插入该条数据确保将该条数据移动到最前面
// 测试// 存储数据 setlruCache.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.7then 必须返回一个新的 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保证 onFulfilledonRjected 异步执行// 所以用了 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.1x 不能和 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.4x 为基本类型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[keyAsync] 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()) 此时的ynew 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(fnfn())}}const reject reason {if(this.status EMUM.PENDING) {this.status EMUM.REJECTEDthis.reason reasonthis.onRejectedCallbacks.forEach(fnfn())}}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(vv).then(vv).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(dconsole.log(success,d)).catch(erconsole.log(faild,er))// Promise.reject(100).finally(()new Promise()).then(dconsole.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(dconsole.log(finally success,d))
// .catch(erconsole.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 langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /meta http-equivX-UA-Compatible contentieedge /titleMVVM/title/headbodydiv idapph3姓名/h3p{{name}}/ph3年龄/h3p{{age}}/p/div/body
/html
scriptdocument.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/javascriptscript.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计算鼠标移动的距离mousemoveCanvas 模拟画板功能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 100200300 找到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 i0, lenarr.length; ilen; i) {for (let ji1; jlen; 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 提供了新的数据结构 SetSet 结构的一个特性就是成员值都是唯一的没有重复的值。 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