14 == 和 ===区别,什么情况用 ==
这里来解析一道题目 [] == ![] // -> true ,下面是这个表达式为何为 true 的步骤
// [] 转成 true,然后取反变成 false
[] == false
// 根据第 8 条得出
[] == ToNumber(false)
[] == 0
// 根据第 10 条得出
ToPrimitive([]) == 0
// [].toString() -> ''
'' == 0
// 根据第 6 条得出
0 == 0 // -> true
===用于判断两者类型和值是否相同。 在开发中,对于后端返回的 code,可以通过 ==去判断
#15 基本数据类型和引?类型在存储上的差别
前者存储在栈上,后者存储在堆上
#16 浏览器 Eventloop 和 Node 中的有什么区别
众所周知 JS 是门非阻塞单线程语言,因为在最初 JS 就是为了和浏览器交互而诞生的。如果 JS 是门多线程的语言话,我们在多个线程中处理 DOM 就可能会发生问题(一个线程中新加节点,另一个线程中删除节点),当然可以引入读写锁解决这个问题。
- JS 在执行的过程中会产生执行环境,这些执行环境会被顺序的加入到执行栈中。如果遇到异步的代码,会被挂起并加入到 Task(有多种 task) 队列中。一旦执行栈为空,Event Loop 就会从 Task 队列中拿出需要执行的代码并放入执行栈中执行,所以本质上来说 JS 中的异步还是同步行为
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
console.log('script end');
- 以上代码虽然 setTimeout 延时为 0,其实还是异步。这是因为 HTML5 标准规定这个函数第二个参数不得小于 4 毫秒,不足会自动增加。所以 setTimeout还是会在 script end 之后打印。
- 不同的任务源会被分配到不同的 Task队列中,任务源可以分为 微任务(microtask) 和 宏任务(macrotask)。在 ES6 规范中,microtask 称为 jobs,macrotask 称为 task。
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
new Promise((resolve) => {
console.log('Promise')
resolve()
}).then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
// script start => Promise => script end => promise1 => promise2 => setTimeout
- 以上代码虽然 setTimeout 写在 Promise 之前,但是因为 Promise 属于微任务而 setTimeout属于宏任务,所以会有以上的打印。
- 微任务包括 process.nextTick ,promise ,Object.observe,MutationObserver
- 宏任务包括 script , setTimeout ,setInterval,setImmediate ,I/O ,UI renderin
很多人有个误区,认为微任务快于宏任务,其实是错误的。因为宏任务中包括了 script ,浏览器会先执行一个宏任务,接下来有异步代码的话就先执行微任务
所以正确的一次 Event loop 顺序是这样的
- 执行同步代码,这属于宏任务
- 执行栈为空,查询是否有微任务需要执行
- 执行所有微任务
- 必要的话渲染 UI
- 然后开始下一轮 Event loop,执行宏任务中的异步代码
通过上述的 Event loop 顺序可知,如果宏任务中的异步代码有大量的计算并且需要操作 DOM 的话,为了更快的 界面响应,我们可以把操作 DOM 放入微任务中
#17 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, '代码执行时间:'+offset, '下次循环间隔'+currentInterval) // 打印 时 分 秒 代码执行时间 下次循环间隔
setTimeout(loop, currentInterval)
}
setTimeout(loop, currentInterval)
#18 数组降维
[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]])
#19 深拷贝
这个问题通常可以通过 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 的深拷贝函数
#20 typeof 于 instanceof 区别
typeof 对于基本类型,除了 null都可以显示正确的类型
typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
typeof b // b 没有声明,但是还会显示 undefined
typeof 对于对象,除了函数都会显示 object
typeof [] // 'object'
typeof {} // 'object'
typeof console.log // 'function'
对于 null 来说,虽然它是基本类型,但是会显示 object,这是一个存在很久了的 Bug
typeof null // 'object'
instanceof 可以正确的判断对象的类型,因为内部机制是通过判断对象的原型链中是不是能找到类型的 prototype
我们也可以试着实现一下 instanceof
function instanceof(left, right) {
// 获得类型的原型
let prototype = right.prototype
// 获得对象的原型
left = left.__proto__
// 判断对象的类型是否等于类型的原型
while (true) {
if (left === null)
return false
if (prototype === left)
return true
left = left.__proto__
}
}