Post

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

  1. Promise(exec).then(resolved, rejected) 描述了一个,当下或未来可能完成的任务exec, 以及该任务完成后的回调resolved, rejected.
  2. Promise.then是一种”发布/订阅模式”, Promise(exec)定义一个事件发布者, 不过 exec 只能发布 resolve 或 reject 一种事件, 且只能发布一次. then 则是添加订阅. 在同一个Promise对象上多次调用then,会添加多个订阅者,这样当Promise发布事件后,所有订阅者会依次执行其回调函数.
  3. then需要支持链式调用, 也就是说, then的返回值必须是一个新的 Promise. 注意链式调用和 2 中的多次调用then添加订阅者概念并不相同, 不要混淆.
  4. 注意使用微队列执行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.