1.generator 原理
Generator 是 ES6 中新增的语法,和 Promise ⼀样,都可以⽤来异步编 程
// 使⽤ * 表示这是⼀个 Generator 函数
// 内部可以通过 yield 暂停代码
// 通过调⽤ next 恢复执⾏
function* test() {
let a = 1 + 2;
yield 2;
yield 3;
}
let b = test();
console.log(b.next()); // > { value: 2, done: false }
console.log(b.next()); // > { value: 3, done: false }
console.log(b.next()); // > { value: undefined, done: true }
从以上代码可以发现,加上 * 的函数执⾏后拥有了 next 函数,也就是说 函数执⾏后返回了⼀个对象。每次调⽤ next 函数可以继续执⾏被暂停的代 码。以下是 Generator 函数的简单实现
// cb 也就是编译过的 test 函数
function generator(cb) {
return (function() {
var object = {
next: 0,
stop: function() {
}
};
return {
next: function() {
var ret = cb(object);
if (ret === undefined) return {
value: undefined, done: true };
return {
value: ret,
done: false
};
}
};
})();
}
// 如果你使⽤ babel 编译后可以发现 test 函数变成了这样
function test() {
var a;
return generator(function(_context) {
while (1) {
switch ((_context.prev = _context.next)) {
// 可以发现通过 yield 将代码分割成⼏块
// 每次执⾏ next 函数就执⾏⼀块代码
// 并且表明下次需要执⾏哪块代码
case 0:
a = 1 + 2;
_context.next = 4;
return 2;
case 4:
_context.next = 6;
return 3;
// 执⾏完毕
case 6:
case "end":
return _context.stop();
}
}
});
}
2. Promise
Promise
是ES6
新增的语法,解决了回调地狱的问题- 可以把
Promise
看成⼀个状态机。初始是pending
状态,可以通过函数resolve
和reject
,将状态转变为resolved
或者rejected
状态,状态⼀旦改变就不能再次变 化。 then
函数会返回⼀个Promise
实例,并且该返回值是⼀个新的实例⽽不是之前的实 例。因为Promise
规范规定除了pending
状态,其他状态是不可以改变的,如果返回 的是⼀个相同实例的话多个then
调⽤就失去意义了。 对于then
来说,本质上可以 把它看成是flatMap
3.== 和 ===区别,什么情况⽤ ==
这⾥来解析⼀道题⽬ [] == ![] // -> true ,下⾯是这个表达式为何为true 的步骤
// [] 转成 true,然后取反变成 false
[] == false
// 根据第 8 条得出 [] == ToNumber(false) [] == 0
// 根据第 10 条得出
ToPrimitive([]) == 0
// [].toString() -> ''
'' == 0
// 根据第 6 条得出 0 == 0 // -> true
=== ⽤于判断两者类型和值是否相同。 在开发中,对于后端返回的 code , 可以通过 == 去判断
4. 基本数据类型和引⽤类型在存储上的差别
前者存储在栈上,后者存储在堆上
5. setTimeout 倒计时误差
JS
是单线程的,所以setTimeout
的误差其实是⽆法被完全解决的,原因 有很多,可能是回调的,有可能是浏览器中的各种事件导致。这也是为什么⻚⾯开久了,定时器会不准的原因,当然我们可以通过⼀定的办法去减少这个 误差。
// 以下是⼀个相对准备的倒计时实现
var period = 60 * 1000 * 60 * 2
var startTime = new Date().getTime();
var count = 0
var end = new Date().getTime() + period
var interval = 1000
var currentInterval = interval
function loop() {
count++
var offset = new Date().getTime() - (startTime + count * interval); // 代码
var diff = end - new Date().getTime()
var h = Math.floor(diff / (60 * 1000 * 60))
var hdiff = diff % (60 * 1000 * 60)
var m = Math.floor(hdiff / (60 * 1000))
var mdiff = hdiff % (60 * 1000)
var s = mdiff / (1000)
var sCeil = Math.ceil(s)
var sFloor = Math.floor(s)
currentInterval = interval - offset // 得到下⼀次循环所消耗的时间
console.log('时:'+h, '分:'+m, '毫秒:'+s, '秒向上取整:'+sCeil, '代码执⾏时间:
setTimeout(loop, currentInterval)
}
setTimeout(loop, currentInterval)
6.数组降维
[1, [2], 3].flatMap(v => v)
// -> [1, 2, 3]
如果想将⼀个多维数组彻底的降维,可以这样实现
const flattenDeep = (arr) => Array.isArray(arr)
? arr.reduce( (a, b) => [...a, ...flattenDeep(b)] , [])
: [arr]
flattenDeep([1, [[2], [3, [4]], 5]])
7.深拷⻉
这个问题通常可以通过
JSON.parse(JSON.stringify(object))
来解决
let a = {
age: 1,
jobs: {
first: 'FE'
} }
let b = JSON.parse(JSON.stringify(a)) a.jobs.first = 'native'
console.log(b.jobs.first) // FE
但是该⽅法也是有局限性的:
- 会忽略
undefined
- 会忽略
symbol
- 不能序列化函数
- 不能解决循环引⽤的对象
let obj = {
a: 1,
b: {
c: 2,
d: 3,
},
}
obj.c = obj.b
obj.e = obj.a
obj.b.c = obj.c
obj.b.d = obj.b
obj.b.e = obj.b.c
let newObj = JSON.parse(JSON.stringify(obj))
console.log(newObj)
在遇到函数、 undefined 或者 symbol 的时候,该对象也不能正常的序列 化
let a = {
age: undefined,
sex: Symbol('male'),
jobs: function() {
},
name: 'yck'
}
let b = JSON.parse(JSON.stringify(a))
console.log(b) // {name: "yck"}
但是在通常情况下,复杂数据都是可以序列化的,所以这个函数可以解决⼤部 分问题,并且该函数是内置函数中处理深拷⻉性能最快的。当然如果你的数据中含有以上三种情况下,可以使⽤
lodash
的深拷⻉函数