JS的事件循环与异步编程
块级作用域和暂时性死区
- 暂时性死区(TDZ):let 和 const 声明的变量在声明之前处于 TDZ,在 TDZ 内访问这些变量会导致 ReferenceError 错误。
- 块级作用域:let 和 const 声明的变量具有块级作用域,在声明所在的块 {} 内可见和有效,而 var 声明的变量不具备块级作用域,具有函数作用域或全局作用域。
- var 声明的变量不具备块级作用域,而是函数作用域或全局作用域。这意味着 var 声明的变量会在函数内或全局范围内可见,即使它们是在块内部声明的。
引用复制
- js中对象的赋值是引用复制,即两个变量指向同一个内存地址,改变其中一个变量的值,另一个变量的值也会改变。
- 可以使用 Object.assign() 或者展开运算符 … 来实现对象的浅拷贝。
JS的事件循环与异步机制
- JS是单线程的,但是通过事件循环机制实现异步操作。
- JS的异步操作是依靠事件循环机制实现的.
事件循环
(浏览器环境) 可以将JS的执行顺序看做三个队列: 同步队列 宏任务队列 微任务队列. 同步队列是JS主线程最优先执行的代码
当同步队列的代码执行完成, 主线程处于空闲状态, JS会依次执行微队列中的所有回调(此时新增的微任务也会排在队列末尾并在本次循环中执行). 然后执行宏队列中的所有回调. 然后再次检查微任务队列, 再次检查宏队列 … 如此循环.
需要注意的是, 如果在一个循环中, 添加了新的微任务或宏任务, 那么, 微任务会尽可能在本轮循环中执行(微任务中创建微任务, 那么新的任务也会在本轮循环中执行), 但宏任务则需要等待到下一个循环.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
console.log('script start!')
Promise.resolve().then(()=>{
console.log("Promise")
setTimeout(() => {
console.log('setTimeout in Promise')
}, 0);
})
setTimeout(() => {
console.log('setTimeout')
Promise.resolve().then(()=>{
console.log('Promise in setTimeout')
})
}, 0);
console.log('script end!')
/// script start!
/// script end!
/// Promise
/// setTimeout
/// Promise in setTimeout
/// setTimeout in Promise
手写一个 Promise
Promise(exec).then(resolved, rejected)
描述了一个,当下或未来可能完成的任务exec, 以及该任务完成后的回调resolved, rejected.Promise.then
是一种”发布/订阅模式”, Promise(exec)定义一个事件发布者, 不过 exec 只能发布 resolve 或 reject 一种事件, 且只能发布一次. then 则是添加订阅. 在同一个Promise对象上多次调用then,会添加多个订阅者,这样当Promise发布事件后,所有订阅者会依次执行其回调函数.then
需要支持链式调用, 也就是说, then的返回值必须是一个新的Promise
. 注意链式调用和 2 中的多次调用then添加订阅者概念并不相同, 不要混淆.- 注意使用微队列执行Promise回调.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
// MyPromise.js
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
#state = PENDING
#reason = undefined
#value = undefined
#resolvedCallbacks = []
#rejectedCallbacks = []
#isPromiseLike(value){
if(value != null && (typeof value === 'object' || typeof value === 'function'))
return typeof value.then === 'function'
return false
}
#runMicroTask(cb){
// node
if(typeof process === 'object' && typeof process.nextTick === 'function'){
process.nextTick(cb)
}
// browser
else if(typeof MutationObserver === 'function'){
const ob = new MutationObserver(()=>{
cb()
ob.disconnect()
})
const textNode = document.createTextNode('')
ob.observer(textNode)
textNode.data = 'trigger'
}
// Macro
else{
setTimeout(cb, 0)
}
}
constructor(executor){
const resolve = (value)=>{
if(this.#state === PENDING){
this.#state = FULFILLED
this.value = value
this.#resolvedCallback.forEach(fn => fn())
}
}
const reject = (reason)=>{
if(this.#state === PENDING){
this.#state = REJECTED
this.reason = reason
this.#rejectedCallback.forEach(fn => fn())
}
}
try{
executor(resolve, reject)
}catch(error){
reject(error)
}
}
then(onResolved, onRejected){
onResolved = typeof onResolved === 'function' ? onResolved : (value)=>value
onRejected = typeof onRejected === 'function' ? onRejected : (reason)=> throw reason
let thenPromise = new MyPromise((resolve, reject) => {
const resolvePromise = (cb)=>{
this.#runMicroTask(()=>{
try{
const x = cb(this.#value)
if(x === thenPromise)
throw new TypeError("")
else if(this.#isLikePromise(x)){
x.then(resolve, reject)
}
else
resolve(x)
}catch(e){
reject(e)
}
})
}
if(this.#state === FULFILLED){
resolvePromise(onResolved)
}
else if(this.#state === REJECTED){
resolvePromise(onRejected)
}
else{
this.#resolvedCallbacks.push(()=>resolvePromise(onResolved))
this.#rejectedCallbacks.push(()=>resolvePromise(onRejected))
}
})
}
}
This post is licensed under CC BY 4.0 by the author.