1. 暂时性死区
在变量声明前使用变量,如此一来便会造成变量的提前使用,这种情况下会出现暂时性死区。
- ES6中的const、let语法
console.log(i)
const i // let i
- typeof
typeof x
const x
- 函数参数
function(x = y, y = 2) {}
- 同变量名赋值
const x = x
2. Promise
2.1 Promise定义
Promise是异步调用的统一接口。每个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数。
Promise有三种状态:pending(等待)、fulfilled(完成)、rejected(已拒绝)。每个Promise必须实现then方法,也就是说then方法是Promise的核心。 then方法可传入两个回调函数,第一个即成功时的回调函数,状态由pending -> fulfilled;第二个为失败时的回调函数,状态由pending -> rejected;此外,then方法还可以传入Promise对象或者类then方法,即thenable方法。
2.2 Promise解决回调陷阱的链式写法
- 不使用Promise
// 假设有两个异步函数:func1 和 func2
// func1 接收一个回调函数作为参数
function func1(callback) {
// 异步操作
setTimeout(() => {
const result = "Func1"
callback(null, result) // 成功时调用回调函数,并传递结果作为参数
// callback(new Error('Error in func1')); // 失败时调用回调函数,并传递错误作为参数
}, 1000)
}
// func2 接收一个参数和一个回调函数作为参数
function func2(data, callback) {
// 异步操作
setTimeout(() => {
const result = data + " -> Func2"
callback(null, result) // 成功时调用回调函数,并传递结果作为参数
// callback(new Error('Error in func2')); // 失败时调用回调函数,并传递错误作为参数
}, 1000)
}
// 使用回调函数实现异步操作的串行执行
func1((err, result1) => {
if (err) {
console.error(err)
} else {
console.log(result1) // 输出:Func1
func2(result1, (err, result2) => {
if (err) {
console.error(err)
} else {
console.log(result2) // 输出:Func1 -> Func2
}
})
}
})
- 使用Promise
// 假设有两个异步函数:func1 和 func2
// func1 返回一个 Promise 对象
function func1() {
return new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
const result = "Func1"
resolve(result) // 成功时调用 resolve
// reject(new Error('Error in func1')); // 失败时调用 reject
}, 1000)
})
}
// func2 返回一个 Promise 对象
function func2(data) {
return new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
const result = data + " -> Func2"
resolve(result)
// reject(new Error('Error in func2'));
}, 1000)
})
}
// 使用 Promise 链式写法
func1()
.then(result => {
console.log(result) // 输出:Func1
return func2(result) // 返回一个 Promise 对象,作为下一个 then 的输入
})
.then(result => {
console.log(result) // 输出:Func1 -> Func2
})
.catch(error => {
console.error(error) // 捕捉链中的任何一个 Promise 的错误
})
2.3 resolve 不同值的区别
-
参数传入一个普通的值或对象
- 这个值回作为 then 回调的参数
new Promise((resolve, reject) => { resolve("normal resolve") }).then(res => { console.log("res: ", res) }) -
参数传入的是另一个 Promise
- 这个新 Promise 会决定原 Promise 的状态
new Promise((resolve, reject) => { resolve(new Promise((resolve, reject) => { setTimeout(() => { resolve("第二个 Promise 的 resolve") }) })) }).then(res => { console.log("res: ", res) }).catch(err => { console.log("err: ", err) }) -
参数传入一个对象,并且这个对象有实现 then 方法
- 会执行该 then 方法,并且根据 then 方法的结果来决定 Promise 的状态
new Promise((resolve, reject) => { resolve({ then: function(resolve, reject) { resolve("thenable value") } }) }).then(res => { console.log("res: ", res) })
2.4 await/async
await 与 async 是用在异步函数中的一对关键字,本质上是生成器的语法糖。
await 本质上就是 Promise.resolve()
3. js 内存泄漏
- 意外的全局变量
- 未清除的定时器
- 脱离DOM元素的引用
- 一个 dom 容器删除之后,变量未置为 null,则其内部的 dom 元素则不会释放
- 持续绑定的事件
- 函数中 addEventListener 绑定事件,函数多次执行,绑定便会产生多次,产生内存泄露
- 闭包引起的内存泄露
- 比如事件处理回调,导致 DOM 对象和脚本中对象双向引用
- console.log
- console.log 的对象是不能被垃圾回收的
4. js判断类型的几种方法
- typeof 对象。它返回一个字符串,表示对象的类型。typeof 123 === 'number',typeof undefined === 'undefined',typeof null === 'object'
- Object.prototype.toString.call(obj)。它返回一个字符串 '[object type]',其中,type 是对象的类型。
- instanceof 运算符。它判断一个对象是否是某个类的实例。'hello' instanceof String === false, new String('hello') instanceof String === true。
- constructor 属性。每个对象都有一个 constructor 属性,指向创建对象的构造函数。例如,'hello'.constructor 返回 String,(123).constructor 返回 Number,true.constructor 返回 Boolean。
5. Event Loop 与 宏任务微任务
Event Loop 至少有 6 个阶段,不同的操作系统上阶段数不同,不过至少是有这6个阶段的,分别如下:
最重要的是 timers、poll、check 阶段。
- timers:执行 setTimeout、setInterval的回调函数
- poll:检索 I/O 事件,执行 I/O 相关的回调。
- 计算这一阶段阻塞以及轮询I/O的时间(应该是计算 setTimeout 中阻塞的时间,如果时间到了,那么就会执行 timers 中的回调)
- 执行 poll 队列中的事件
当 event loop 进入到 poll 阶段并且没有其他 timers 回调执行,那么就会发生以下两件事:
- poll 队列不为空,event loop 将会一直同步执行队列中的回调,直到队列中的回调执行完,或者此阶段的时间用完
- 如果 poll 队列为空,又回执行以下两件事中的其中一件:
- 有 setImmediate 任务,event loop 将会结束 poll 轮询阶段执行 check 阶段中的回调
- 如果没有 setImmediate 任务,eventloop 将会等待添加到队列中的回调,然后立即执行。
一旦 poll 队列为空,event loop 将会一直检查 timers 的最小开始回调时间何时发生,如果有 timers 准备好,那么 event loop 将会返回到 timers 阶段执行回调。
- check:执行 setImmediate 的回调函数
5.1 宏任务与微任务
- 事件循环中并非只维护着一个队列,实际上有两个队列:
宏任务队列:Ajax、setTimeout、setInterval、DOM监听、UI Rendering等。微任务队列:Promise 的 then 回调、Mutation Observer API、queueMicrotask()等。
- 事件循环对于两个队列的优先级
-
main script 中的代码先执行(编写的顶层 script 代码)
-
- 在
执行任何一个宏任务之前(不是队列,是一个宏任务),都会先查看微任务队列中是否有任务需要执行
- 也就是宏任务执行之前,必须保证微任务队列是空的;
- 如果不为空,那么就要优先执行微任务队列中的任务(回调)
- 在
-
5.2 微任务宏任务思路:
-
每次在执行宏任务之前,需要先将微任务队列中的回调清空;
-
async/await
- 紧挨着 await 的回调立即执行,而后面的会放到 then 回调中。
async fn() { await foo() console.log(1) // 可以将上述改写: foo().then(() => {console.log(1)}) }
console.log('script start')
function requestData(url) {
console.log("requestData")
return new Promise((resolve) => {
setTimeout(() => {
console.log('setTimeout')
resolve(url)
}, 2000)
})
}
function getData() {
console.log('getData start')
requestData('why').then(res => {
console.log('then1-res: ', res)
})
console.log('getData end')
}
getData()
console.log('script end')
// 代码结果
// script start
// getData start
// requestData
// getData end
// script end
// setTimeout
// then1-res: why
- setTimeout() vs setImmediate()
-
这两个都是在主模块(main module)中被调用的,那么回调的执行顺序取决于当前进程的性能,这个性能受到其他程序进程(libuv、nodejs 启动顺序)的影响
// timeout_vs_immediate.js setTimeout(() => { console.log('timeout') }, 0) setImmediate(() => { console.log('immediate') }) $ node timeout_vs_immediate.js timeout immediate $ node timeout_vs_immediate.js immediate timeout
-
- 一个 return Promise.resolve() 等于两个 then。
6. 判断对象为空对象的几种方法
-
Object.keys()
Object.keys(obj).length === 0 ? '空对象' : '非空对象'
-
使用 for...in 遍历对象
const obj = {} let isEmpty = true for(let key in obj) { isEmpty = false break } -
JSON.stringify()
JSON.stringify(obj) === '{}' ? '空对象' : '非空对象'
-
使用第三方库
- lodash
- underscore
const isEmpty = _.isEmpty(obj)
-
Object.getOwnPropertyNames === 0 && Object.getOwnPropertySymbols === 0
-
Reflect.ownKeys(obj) === 0