• 第二章、javascript高级


    目录

    十五、Proxy-Reflect vue2-vue3响应式原理

    1、监听对象的操作
    const obj = {
        name: "黄婷婷",
        age: 18
    }
    /**
     * Object.defineProperty弊端:
     *     - 初衷不是为了监听对象
     *     - 对象属性添加和删除无法监听(vue2的$set()会调用Object.defineProperty(),
     *       以达到能监听添加的属性的目的)
     *     - 会改变对象的属性描述符(数据属性描述符->存取属性描述符)
     */
    Object.keys(obj).forEach(key => {
        let value = obj[key]
        Object.defineProperty(obj, key, {
            configurable: true,
            enumerable: true,
            get() {
                console.log(`访问了 ${key} 属性`)
                return value
            },
            set(val) {
                console.log(`设置了 ${key} 属性`)
                value = val
            }
        })
    })
    
    2、Proxy和Reflect基本使用
    const obj = {}
    
    function fun() {
    }
    
    // 一、常用的四个捕获器
    const oProxy1 = new Proxy(obj, {
        // 1、in 操作符的捕捉器
        has(target, p) {
            return Reflect.has(target, p)
        },
        // 2、属性读取操作的捕捉器
        get(target, p) {
            return Reflect.get(target, p)
        },
        // 3、属性设置操作的捕捉器
        set(target, p, value) {
            return Reflect.set(target, p, value)
        },
        // 4、delete 操作符的捕捉器
        deleteProperty(target, p) {
            return Reflect.deleteProperty(target, p)
        }
    });
    
    // 二、函数对象的两个捕获器
    const fProxy = new Proxy(fun, {
        // 1、函数调用操作的捕捉器
        apply(target, thisArg, argArray) {
            return Reflect.apply(target, thisArg, argArray)
        },
        // 2、new 操作符的捕捉器
        construct(target, argArray, newTarget) {
            return Reflect.construct(target, argArray, newTarget)
        }
    });
    
    // 三、其它的七个捕获器
    const oProxy2 = new Proxy(obj, {
        // 1、Object.getPrototypeOf 方法的捕捉器
        getPrototypeOf(target) {
            return Reflect.getPrototypeOf(target)
        },
        // 2、Object.setPrototypeOf 方法的捕捉器
        setPrototypeOf(target, v) {
            return Reflect.setPrototypeOf(target, v)
        },
        // 3、Object.isExtensible 方法的捕捉器
        isExtensible(target) {
            return Reflect.isExtensible(target)
        },
        // 4、Object.preventExtensions 方法的捕捉器
        preventExtensions(target) {
            return Reflect.preventExtensions(target)
        },
        // 5、Object.getOwnPropertyDescriptor 方法的捕捉器
        getOwnPropertyDescriptor(target, p) {
            return Reflect.getOwnPropertyDescriptor(target, p)
        },
        // 6、Object.defineProperty 方法的捕捉器
        defineProperty(target, p, attributes) {
            return Reflect.defineProperty(target, p, attributes)
        },
        // 7、Object.getOwnPropertyNames、Object.getOwnPropertySymbols 方法的捕捉器
        ownKeys(target) {
            return Reflect.ownKeys(target)
        }
    });
    
    3、receiver参数的作用
    const obj = {
        _name: "黄婷婷",
        get name() {
            return this._name
        },
        set name(val) {
            this._name = val
        }
    }
    /**
     * * 如果target对象中指定了getter/setter,
     *   receiver则为getter/setter调用时的this值
     */
    const oProxy = new Proxy(obj, {
        get(target, p, receiver) {
            return Reflect.get(target, p, receiver)
        },
        set(target, p, value, receiver) {
            return Reflect.set(target, p, value, receiver)
        }
    });
    
    4、Reflect.construct的作用
    function Student() {
    }
    
    function Teacher() {
    }
    
    // 执行Student函数中的内容,但是创建出来对象是Teacher对象
    const teacher = Reflect.construct(Student, [], Teacher);
    console.log(teacher.constructor.name)// Teacher
    // 相当于
    const teacher1 = new Teacher();
    Student.apply(teacher1, [])
    
    5、响应式原理
    let activeReactiveFn = null
    
    class Depend {
        constructor() {
            this.reactiveFns = new Set()
        }
    
        depend() {
            if (activeReactiveFn) {
                this.reactiveFns.add(activeReactiveFn)
            }
        }
    
        notify() {
            this.reactiveFns.forEach(fn => {
                fn()
            })
        }
    }
    
    function reactive(obj) {
        /*Object.keys(obj).forEach(p => {
            let value = obj[p]
            Object.defineProperty(obj, p, {
                get() {
                    const depend = getDepend(obj, p);
                    depend.depend()
                    return value
                },
                set(v) {
                    value = v
                    const depend = getDepend(obj, p);
                    depend.notify()
                }
            })
        })
        return obj*/
    
        return new Proxy(obj, {
            get(target, p, receiver) {
                const depend = getDepend(target, p);
                depend.depend()
                return Reflect.get(target, p, receiver)
            },
            set(target, p, value, receiver) {
                Reflect.set(target, p, value, receiver)
                const depend = getDepend(target, p);
                depend.notify()
            }
        });
    }
    
    function watchFn(fn) {
        activeReactiveFn = fn
        fn()
        activeReactiveFn = null
    }
    
    const targetMap = new WeakMap();
    
    function getDepend(target, p) {
        let map = targetMap.get(target);
        if (!map) {
            map = new Map()
            targetMap.set(target, map)
        }
        let depend = map.get(p);
        if (!depend) {
            depend = new Depend()
            map.set(p, depend)
        }
        return depend
    }
    
    const obj = {
        name: "黄婷婷",
        age: 18
    }
    const objProxy = reactive(obj)
    watchFn(() => {
        console.log(objProxy.name, "++++++++++")
        console.log(objProxy.name, "----------")
    })
    objProxy.name = "姜贞羽"
    
    const info = {
        address: "无锡市",
        gender: 0
    }
    const infoProxy = reactive(info)
    watchFn(() => {
        console.log(infoProxy.address, "++++++++++")
        console.log(infoProxy.address, "----------")
    })
    infoProxy.address = "上海市"
    

    十六、Promise

    1、Promise之前
    /**
     * 这种回调的方式有很多的弊端:
     *     - 如果是我们自己封装的requestData,那么我们在封装的时候必须要自己
     *       设计好callback名称,并且使用好
     *     - 如果我们使用的是别人封装的requestData或者一些第三方库,那么我们
     *       必须去看别人的源码或者文档,才知道它这个函数需要怎么去获取到结果
     *     - 回调地狱
     */
    function ajax(url, fnSucc, fnFaild) {
        setTimeout(() => {
            if (url) {
                fnSucc()
            } else {
                fnFaild()
            }
        }, 1000)
    }
    
    ajax("黄婷婷", () => {
        ajax("孟美岐", () => {
        }, () => {
        })
    }, () => {
    })
    
    2、Promise使用详解
    function ajax(url) {
        /**
         * Promise三个函数:
         *     - executor:new Promise传入的回调函数
         *     - resolve:函数类型的参数,executor执行成功则调用此函数
         *     - reject:函数类型的参数,executor执行失败则调用此函数
         * Promise三种状态:
         *     - 待定(pending):执行executor
         *     - 已兑现(fulfilled):执行resolve
         *     - 已拒绝(rejected):执行reject
         * 注意:Promise状态一旦确定下来,那么就是不可更改的(锁定)。
         *      resolve/reject之后的代码仍然可以执行,但是不会再执行resolve/reject
         */
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                if (url) {
                    /**
                     * 实现了thenable接口的对象:
                     *     {then(resolve, reject) {resolve("你好世界")}}
                     */
                    resolve()
                } else {
                    reject()
                }
            }, 1000)
        })
    }
    
    /**
     * then(onfulfilled,onrejected)传入两个回调函数:
     *     - onfulfilled:executor中执行resolve时回调
     *     - onrejected:executor中执行reject时回调
     * resolve(value):
     *     - value为普通的值或者对象:状态会由pending -> fulfilled
     *     - value为Promise对象:那么当前的Promise的状态会由传入的Promise
     *       来决定,相当于状态进行了移交
     *     - value为实现了thenable接口的对象:那么也会执行该then方法,并且
     *       由该then方法决定后续状态
     */
    ajax("黄婷婷").then(value => {
    }, reason => {
    })
    
    3、Promise.prototype.then方法
    const p = new Promise(resolve => {
        resolve("黄婷婷")
    })
    /**
     * 1、同一个Promise可以被多次调用then方法。当我们的resolve方法被回调时,
     *   所有的then方法传入的回调函数都会被调用
     */
    p.then(value => {
        console.log(value)// 黄婷婷
    })
    /**
     * 2、then方法传入的回调函数可以有返回值。then方法本身也是有返回值的,
     *   它的返回值是Promise
     *     - 如果我们返回的是一个普通值(数值/字符串/普通对象/undefined),
     *       那么这个普通的值被作为一个新的Promise的resolve值
     *     - 如果我们返回的是一个Promise
     *     - 如果返回的是一个对象,并且该对象实现了thenable
     */
    p.then(value => {
        console.log(value)// 黄婷婷
        return "孟美岐"
    }).then(value => {
        console.log(value)// 孟美岐
    })
    
    4、Promise.prototype.catch方法
    const p = new Promise((resolve, reject) => {
        // reject("黄婷婷")
        throw new Error("孟美岐")
    })
    // 1、当executor抛出异常时,也是会调用错误(拒绝)捕获的回调函数的
    p.then(undefined, reason => {
        console.dir(reason)
    })
    // 2、通过catch方法来传入错误(拒绝)捕获的回调函数,也可以被多次调用
    // Promises/A+规范
    p.catch(reason => {
        console.log(reason)
    })
    // 是语法糖。catch会优先捕获p的异常,p没有异常则捕获then回调函数的异常
    p.then(value => {
        console.log(value)
    }).catch(reason => {
        console.log(reason)
    })
    // 3、拒绝捕获的问题
    // 4、catch方法的返回值,是一个Promise
    
    5、Promise.prototype.finally方法
    const p = new Promise((resolve, reject) => {
        reject("黄婷婷")
    })
    // 不管是fulfilled还是rejected状态,最后都会执行finally回调函数
    p.then(value => {
    }).catch(reason => {
    }).finally(() => {
    })
    
    6、Promise.resolve方法
    /**
     * resolve参数的形态:
     *     - 参数是一个普通的值或者对象
     *     - 参数本身是Promise:直接返回Promise,微任务队列知识点需注意
     *     - 参数是一个thenable
     */
    const p = Promise.resolve("黄婷婷");
    // 相当于
    new Promise(resolve => resolve("黄婷婷"))
    
    7、Promise.reject方法
    // 注意:无论传入什么值都是一样的(参数不分三种情况)
    const p = Promise.reject("黄婷婷");
    // 相当于
    // new Promise((resolve, reject) => reject("黄婷婷"))
    p.catch(reason => {
        console.log(reason)
    })
    
    8、Promise.all方法
    const p1 = Promise.resolve("黄婷婷")
    const p2 = Promise.resolve("孟美岐")
    const p3 = Promise.resolve("姜贞羽")
    /**
     * 需求:所有的Promise都变成fulfilled时,再拿到结果
     * 意外:在拿到所有结果之前,有一个promise变成了rejected,
     *      那么整个promise是rejected
     */
    Promise.all([p1, p2, p3, "佟丽娅"]).then(value => {
        console.log(value)
    }).catch(reason => {
        console.log(reason)
    })
    
    9、Promise.allSettled方法
    const p1 = Promise.resolve("黄婷婷")
    const p2 = Promise.reject("孟美岐")
    const p3 = Promise.resolve("姜贞羽")
    // 所有Promise都不是pending状态则执行then回调函数
    Promise.allSettled([p1, p2, p3]).then(value => {
        console.log(value)// [{…}, {…}, {…}]
    })
    
    10、Promise.race方法
    const p1 = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("黄婷婷")
        }, 1000)
    })
    const p2 = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("孟美岐")
        }, 2000)
    })
    const p3 = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("姜贞羽")
        }, 3000)
    })
    /**
     * 只要有一个Promise变成fulfilled/rejected状态
     * 那么就执行then/catch回调函数
     */
    Promise.race([p1, p2, p3]).then(value => {
        console.log(value)
    }).catch(reason => {
        console.log(reason)
    })
    
    11、Promise.any方法
    const p1 = new Promise((resolve, reject) => {
        setTimeout(() => {
            reject("黄婷婷")
        }, 1000)
    })
    const p2 = new Promise((resolve, reject) => {
        setTimeout(() => {
            reject("孟美岐")
        }, 2000)
    })
    const p3 = new Promise((resolve, reject) => {
        setTimeout(() => {
            reject("姜贞羽")
        }, 3000)
    })
    /**
     * 只要有一个Promise变成fulfilled状态那么就执行then回调函数,
     * 所有Promise都变成rejected状态那么就执行catch回调函数
     */
    Promise.any([p1, p2, p3]).then(value => {
        console.log(value)
    }).catch(reason => {
        console.log(reason.errors)
    })
    
    12、手写Promise
    const PROMISE_STATUS_PENDING = "pending"
    const PROMISE_STATUS_FULFILLED = "fulfilled"
    const PROMISE_STATUS_REJECTED = "rejected"
    
    function execFunctionWithCatchError(execFn, value, resolve, reject) {
        try {
            const result = execFn(value);
            resolve(result)
        } catch (err) {
            reject(err)
        }
    }
    
    class MyPromise {
        constructor(executor) {
            this.status = PROMISE_STATUS_PENDING
            this.value = undefined
            this.reason = undefined
            this.onFulfilledFns = []
            this.onRejectedFns = []
            const resolve = (value) => {
                if (this.status === PROMISE_STATUS_PENDING) {
                    queueMicrotask(() => {
                        if (this.status !== PROMISE_STATUS_PENDING) return
                        this.status = PROMISE_STATUS_FULFILLED
                        this.value = value
                        this.onFulfilledFns.forEach(fn => {
                            fn(this.value)
                        })
                    })
                }
            }
            const reject = (reason) => {
                if (this.status === PROMISE_STATUS_PENDING) {
                    queueMicrotask(() => {
                        if (this.status !== PROMISE_STATUS_PENDING) return
                        this.status = PROMISE_STATUS_REJECTED
                        this.reason = reason
                        this.onRejectedFns.forEach(fn => {
                            fn(this.reason)
                        })
                    })
                }
            }
            try {
                executor(resolve, reject)
            } catch (err) {
                reject(err)
            }
        }
    
        then(onfulfilled, onrejected) {
            onfulfilled = onfulfilled || (value => {
                return value
            })
            onrejected = onrejected || (err => {
                throw err
            })
            return new MyPromise((resolve, reject) => {
                if (this.status === PROMISE_STATUS_FULFILLED && onfulfilled) {
                    execFunctionWithCatchError(onfulfilled, this.value, resolve, reject)
                }
                if (this.status === PROMISE_STATUS_REJECTED && onrejected) {
                    execFunctionWithCatchError(onrejected, this.reason, resolve, reject)
                }
                if (this.status === PROMISE_STATUS_PENDING) {
                    if (onfulfilled) this.onFulfilledFns.push(() => {
                        execFunctionWithCatchError(onfulfilled, this.value, resolve, reject)
                    })
                    if (onrejected) this.onRejectedFns.push(() => {
                        execFunctionWithCatchError(onrejected, this.reason, resolve, reject)
                    })
                }
            })
        }
    
        catch(onrejected) {
            return this.then(undefined, onrejected)
        }
    
        finally(onfinally) {
            this.then(() => {
                onfinally()
            }, () => {
                onfinally()
            })
        }
    
        static resolve(value) {
            return new MyPromise(resolve => {
                resolve(value)
            })
        }
    
        static reject(reason) {
            return new MyPromise((resolve, reject) => {
                reject(reason)
            })
        }
    
        static all(promises) {
            return new MyPromise((resolve, reject) => {
                const values = []
                promises.forEach(promise => {
                    promise.then(value => {
                        values.push(value)
                        if (values.length === promise.length) {
                            resolve(values)
                        }
                    }, reason => {
                        reject(reason)
                    })
                })
            })
        }
    
        static allSettled(promises) {
            return new MyPromise(resolve => {
                const results = []
                promises.forEach(promise => {
                    promise.then(value => {
                        results.push({status: PROMISE_STATUS_FULFILLED, value: value})
                        if (results.length === promises.length) {
                            resolve(results)
                        }
                    }, reason => {
                        results.push({status: PROMISE_STATUS_REJECTED, reason: reason})
                        if (results.length === promises.length) {
                            resolve(results)
                        }
                    })
                })
            })
        }
    
        static race(promises) {
            return new MyPromise((resolve, reject) => {
                promises.forEach(promise => {
                    promise.then(resolve, reject)
                })
            })
        }
    
        static any(promises) {
            const reasons = []
            return new MyPromise((resolve, reject) => {
                promises.forEach(promise => {
                    promise.then(resolve, reason => {
                        reasons.push(reason)
                        if (reasons.length === promises.length) {
                            reject(new AggregateError(reasons))
                        }
                    })
                })
            })
        }
    }
    

    十七、Iterator-Generator

    1、迭代器
    // 迭代器
    const iterator = {
        next() {
            // done为true时value可省略,表示迭代完毕
            return {done: false, value: ""}
        }
    }
    // 无限的迭代器:done属性永远不为true
    
    // 生成迭代器的函数
    function createIterator(arr) {
        let index = 0
        return {
            next() {
                if (index < arr.length) {
                    return {done: false, value: arr[index++]}
                } else {
                    return {done: true, value: undefined}
                }
            }
        }
    }
    
    2、可迭代对象
    // 可迭代对象
    const iterableObj = {
        array: ["黄婷婷", "孟美岐", "姜贞羽"],
        [Symbol.iterator]: function () {
            let index = 0
            return {
                next: () => {
                    if (index < this.array.length) {
                        return {done: false, value: this.array[index++]}
                    } else {
                        return {done: true, value: undefined}
                    }
                }
            }
        }
    }
    // 可迭代对象:可通过[Symbol.iterator]()函数返回一个迭代器
    const iterator = iterableObj[Symbol.iterator]();
    
    // 原生可迭代对象:String、Array、Map、Set、arguments对象、NodeList集合
    
    3、可迭代对象的应用场景
    * for of场景
    * 展开语法(spread syntax):[...arr]和{...obj}性质不一样,对象展开不是迭代器
    * 解构语法:const [item1,item2]=arr和const {name,age}=obj性质不一样,对象解构不是迭代器
    * 创建一些对象:
          - new Set(iterableObj)
          - Array.from(iterableObj)
          - Promise.all(iterableObj)
    
    4、自定义类对象可迭代性
    class Classroom {
        constructor() {
            this.classmates = ["黄婷婷", "孟美岐", "姜贞羽", "张婧仪", "周洁琼"]
        }
    
        [Symbol.iterator]() {
            let index = 0
            return {
                next: () => {
                    if (index < this.classmates.length) {
                        return {done: false, value: this.classmates[index++]}
                    } else {
                        return {done: true, value: undefined}
                    }
                },
                return: () => {
                    console.log("迭代器中断监听器")
                    return {done: true, value: undefined}
                }
            }
        }
    }
    
    const classroom = new Classroom();
    for (const classmate of classroom) {
        console.log(classmate)
        if (classmate === "姜贞羽") {
            break
        }
    }
    
    5、生成器
    /**
     * 生成器:生成器是ES6中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数
     *       什么时候继续执行、暂停执行等。
     * 生成器函数:
     *     - 生成器函数需要在function的后面加一个符号:*
     *     - 生成器函数可以通过yield关键字来控制函数的执行流程
     *     - 生成器函数的返回值是一个生成器(Generator)
     */
    function* foo() {
        console.log("黄婷婷")
        yield
        console.log("孟美岐")
        yield "张婧仪"
        console.log("周洁琼")
        yield
        return "鞠婧祎"
    }
    
    /**
     * 返回值是一个生成器(一种特殊的迭代器)
     *     - 当遇到yield时候是暂停函数的执行
     *     - 当遇到return时候生成器就停止执行
     */
    const generator = foo();
    generator.next()// 黄婷婷
    const yieldResult = generator.next()// 孟美岐
    console.log(yieldResult)// {value: '张婧仪', done: false}
    generator.next()// 周洁琼
    console.log(generator.next())// {value: '鞠婧祎', done: true}
    
    6、生成器next方法
    function* foo(arg) {
        console.log(arg)
        const res = yield
        console.log(res)
    }
    
    /**
     * 1、一般情况下,next方法不传入参数的时候,yield表达式的返回值是undefined。
     *   当next传入参数的时候,该参数会作为上一步yield的返回值。
     */
    const generator = foo("黄婷婷");
    generator.next()// 黄婷婷
    generator.next("孟美岐")// 孟美岐
    
    7、生成器return方法
    function* foo() {
        yield "黄婷婷"
    }
    
    const generator = foo();
    // 相当于:return "孟美岐",直接终止后面的不会执行
    console.log(generator.return("孟美岐"))// {value: '孟美岐', done: true}
    console.log(generator.next())// {value: undefined, done: true}
    
    8、生成器throw方法
    function* foo() {
        try {
            yield "黄婷婷"
        } catch (e) {
            console.log(e)
        }
        yield "孟美岐"
    }
    
    const generator = foo();
    console.log(generator.next())// {value: '黄婷婷', done: false}
    // 相当于上一个yield抛出了异常,不捕获后面的代码不会执行,捕获则执行到当前yield
    const yieldResult = generator.throw("异常信息");// 异常信息
    console.log(yieldResult)// {value: '孟美岐', done: false}
    console.log(generator.next())// {value: undefined, done: true}
    
    9、生成器替代迭代器
    // 案例一
    const arr = ["黄婷婷", "孟美岐", "姜贞羽"]
    
    function* foo(iterableObj) {
        // yield* iterableObj
        // 相当于
        for (const item of iterableObj) {
            yield item
        }
    }
    
    const generator = foo(arr);
    console.log(generator.next())// {value: '黄婷婷', done: false}
    console.log(generator.next())// {value: '孟美岐', done: false}
    console.log(generator.next())// {value: '姜贞羽', done: false}
    console.log(generator.next())// {value: undefined, done: true}
    
    // 案例二
    class Classroom {
        constructor() {
            this.classmates = ["黄婷婷", "孟美岐", "姜贞羽", "张婧仪", "周洁琼"]
        }
    
        // * [Symbol.iterator]() {
        //     yield* this.classmates
        // }
        // 等价于
        [Symbol.iterator] = function* () {
            yield* this.classmates
        }
    }
    
    const classroom = new Classroom();
    for (const classmate of classroom) {
        console.log(classmate)
    }
    

    十八、async-await

    1、async-await
    // 1、没有Promise之前使用的是回调函数方式
    function requestData(url) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve(url)
            }, 1000)
        })
    }
    
    /*// 2、没有Generator之前
    requestData(["黄婷婷"]).then(res => {
        console.log(res)// ['黄婷婷']
        res.push("孟美岐")
        return requestData(res)
    }).then(res => {
        console.log(res)// ['黄婷婷', '孟美岐']
        res.push("姜贞羽")
        return requestData(res)
    }).then(res => {
        console.log(res)// ['黄婷婷', '孟美岐', '姜贞羽']
    })*/
    
    /*// 3、Promise + Generator
    function* getData() {
        let res = yield requestData(["黄婷婷"])
        console.log(res)
        res.push("孟美岐")
        res = yield requestData(res)
        console.log(res)
        res.push("姜贞羽")
        res = yield requestData(res)
        console.log(res)
    }
    
    /!*const generator = getData();
    generator.next().value.then(res => {
        generator.next(res).value.then(res => {
            generator.next(res).value.then(res => {
                generator.next(res)
            })
        })
    })*!/
    // 优化
    function execGenerator(genFn) {
        const generator = genFn();
        /!**
         * 其它立即执行函数写法总结:都是将函数声明转换成函数表达式,推荐()其它会参与函数返回值的运算
         *     - (function(){}())
         *     - !function(){}()
         *     - +function(){}()
         *     - -function(){}()
         *     - var fun=function(){}()
         *!/
        (function exec(res) {
            const result = generator.next(res);
            if (result.done) {
                return result.value
            } else {
                result.value.then(res => {
                    exec(res)
                })
            }
        })(undefined)
    }
    
    execGenerator(getData)*/
    
    // 4、async-await
    async function getData() {
        let res = await requestData(["黄婷婷"])
        console.log(res)
        res.push("孟美岐")
        res = await requestData(res)
        console.log(res)
        res.push("姜贞羽")
        res = await requestData(res)
        console.log(res)
    }
    
    getData()
    
    2、async函数和普通函数区别
    /**
     * 1、异步函数返回值一定是Promise(返回值会有三种情况)
     * 2、抛出异常相当于return Promise.reject(err)
     */
    async function foo() {
        // throw new Error("异常")
        return "黄婷婷"
    }
    
    foo().then(value => {
        console.log(value)
    }).catch(reason => {
        console.log(reason)
    })
    
    3、await关键字
    /**
     * 1、await必须在async函数中使用
     * 2、await后面的值有三种情况(普通值、Promise、thenable)
     * 3、await后的Promise是rejected状态相当于async函数reject()
     */
    async function foo() {
        await new Promise((resolve, reject) => {
            reject("黄婷婷")
        })
    }
    
    foo().catch(reason => {
        console.log(reason)// 黄婷婷
    })
    

    十九、事件循环-微任务-宏任务

    1、进程和线程
    * 进程(process):计算机已经运行的程序,是操作系统管理程序的一种方式
    * 线程(thread):操作系统能够运行运算调度的最小单位,通常情况下它被包含在进程中
    
    2、浏览器和JavaScript线程
    * 多数的浏览器其实都是多进程,打开一个tab页面时就会开启一个新的进程
    * 每个进程中又有很多的线程,包括执行JavaScript代码的线程(JavaScript是单线程)
    * 真正耗时的操作(网络请求、定时器),实际上并不是由JavaScript线程在执行的
    
    3、浏览器的事件循环
    * js线程发起耗时操作 -> 其它线程处理耗时操作 -> 事件队列添加回调函数 -> js线程执行
    
    4、微任务和宏任务
    * main script:顶层script代码优先执行
    * 微任务队列(macrotask queue):
          - Promise.prototype.then()
          - Mutation Observer API
          - queueMicrotask()
    * 宏任务队列(microtask queue):
          - ajax
          - setTimeout
          - setInterval
          - DOM监听
          - UI Rendering等
    * 优先级:宏任务执行之前,必须保证微任务队列是空的,如果不为空,那么就优先执行微任务队列中的任务
    
    5、微任务和宏任务面试题注意点
    * await之后的代码,会调用一次then()
    * then()回调函数返回值和resolve()参数的三种情况,
      thenable会推迟一步添加到微任务队列,Promise会推迟两步
    * Promise.resolve(Promise.resolve("黄婷婷")) 等价于 Promise.resolve("黄婷婷")
    
    6、node的事件循环
    * 与浏览器的事件循环架构相似。node中的事件循环是由libuv来实现并维护的
    * 完整的事件循环分成很多个阶段(1Tick)
          - 定时器(Timers):本阶段执行已经被setTimeout()和setInerval()的调度回调函数
          - 待定回调(Pending Callback):对某些系统操作(如TCP错误类型)执行回调,比如TCP连接
            时接收到ECONNREFUSED
          - idle,prepare:仅系统内部使用
          - 轮询(Poll):检索新的I/O事件;执行与I/O相关的回调;
          - 检测(check):setImmediate()回调函数在这里执行
          - 关闭的回调函数:一些关闭的回调函数,如:socket.on('close',...)
    * 事件循环中的队列:
          - 微任务队列
                next tick queue:process.nextTick
                other queue:Promise的then回调、queueMicrotask
          - 宏任务队列
                timer queue:setTimeout、setInterval
                poll queue:IO事件
                check queue:setImmediate
                close queue:close事件
    

    二十、错误处理方案

    1、函数出现错误处理
    * 当传入的参数的类型不正确时,应该告知调用者一个错误
    * 调用者(如果没有对错误进行处理,那么程序会直接终止)
    
    2、抛出异常的其他补充
    * 1、抛出一个字符串类型(基本的数据类型)
    * 2、比较常见的是抛出一个对象类型
    * 3、创建类,并且创建这个类对应的对象
    * 4、提供了一个Error,Error对象具有的属性{messgae,name,stack}
    * 5、Error的子类
          - RangeError:下标志越界时使用的错误类型
          - SyntaxError:解析语法错误时使用的错误类型
          - TypeError:出现类型错误时,使用的错误类型
    * 强调:如果函数中已经抛出了异常,那么后续的代码都不会继续执行了
    
    3、对抛出的异常进行处理
    /**
     * 两种处理方法:
     *     - 第一种是不处理,那么异常会进一步的抛出,直到最顶层的调用。
     *       如果在最顶层也没有对这个异常进行处理,那么我们的程序就会终止执行,并且报错
     *     - 使用try catch来捕获异常
     */
    try {
        throw new Error("黄婷婷")
    } catch (err) {
        console.log(err.message)
        console.log(err.name)
        console.log(err.stack)
    } finally {
        console.log("孟美岐")
    }
    

    二十一、JS模块化解析

    1、CommonJS(CJS)基本使用
    // 导出方案一:module.exports
    module.exports = {}
    
    // 导出方案二:exports(容易修改导出的引用,所以弃用)
    /**
     * 源码逻辑:module.exports = {};exports = module.exports;
     * 再给exports添加属性,最终能导出的一定是module.exports
     */
    exports.name = "黄婷婷"
    exports.age = 18
    
    // 使用另外一个模块导出的对象,那么就要进行导入require
    /**
     * require(X)细节
     * 1、X是一个Node核心模块
     *     - require("path")
     *     - require("fs")
     * 2、X是路径
     *     - 有后缀名,按照后缀名的格式查找对应的文件
     *     - 没有后缀名,查找顺序:
     *         - 直接查找文件X
     *         - X.js
     *         - X.json
     *         - X.node
     *     - 以上都未找到,X将作为目录查找,查找顺序:
     *         - X/index.js
     *         - X/index.json
     *         - X/index.node
     * 3、X既不是核心模块,也不是路径(查找顺序可通过module.paths查看)
     *     - 当前目录下node_modules/X/index.js
     *     - 上级目录的node_modules,直到根目录
     */
    const obj = require("")
    
    2、CommonJS模块的加载过程
    * 模块在被第一次引入时,模块中的js代码会被运行一次
    * 模块被多次引入时,会缓存,最终只加载(运行)一次。(module.loaded为true表示已经加载)
    * 如果有循环引入,那么加载顺序是一种数据结构(图结构)。图结构遍历方式:
        - 深度优先搜索(DFS,depth first search),Node采用此种方式
        - 广度优先搜索(BFS,breadth first search)
    
    3、CommonJS规范缺点
    * CommonJS加载模块是同步的,同步意味着会产生阻塞。所以在浏览器中,我们通常不使用CommonJS规范。
      webpack中使用CommonJS是另外一回事,webpack会将我们的代码转成浏览器可以直接执行的代码
    
    4、AMD规范
    /**
     * AMD是Asynchronous Module Definition(异步模块定义)的缩写
     * 使用步骤:
     *     1、下载:https://github.com/requirejs/requirejs
     *     2、<script src="./lib/require.js" data-main="./index.js"></ script>
     *       data-main属性的作用是在加载完src的文件后会加载执行该文件
     */
    // 配置
    require.config({
        baseUrl: "./src",
        paths: {
            foo: "./foo"
        }
    })
    
    // 导入
    require(["foo"], function (foo) {
        console.log(foo)
    })
    
    // 导出/导入
    define(["foo"], function (foo) {
        return {}
    })
    
    // 导出
    define(function () {
        return {}
    })
    
    5、CMD规范
    /**
     * CMD是Common Module Definition(通用模块定义)的缩写,也是异步加载
     * 引入sea.js:
     *     1、<script src="./lib/sea.js"></ script>
     *     2、<script>seajs.use("./src/main.js")</ script>
     */
    // 导入
    define(function (require, exports, module) {
        const foo = require("./foo")
        console.log(foo)
    })
    
    // 导出
    define(function (require, exports, module) {
        exports.name = "黄婷婷"
        exports.age = 18
        // 或者
        // module.exports = {}
    })
    
    6、ES Module基本使用
    // 了解:采用了ES Module将自动采用严格模式:use strict
    /**
     * 模块加载:<script src="./main.js" type="module"></ script>
     * 文件不能通过本地方式加载(url scheme不能是file://)
     * 浏览器下模块后缀名不能省,webpack下模块后缀名可以省
     */
    // 导入
    // 1、普通的导入
    // import {name} from "./foo.js"
    
    // 2、起别名
    // import {name as fName} from "./foo.js"
    
    // 3、将导出的所有内容放到一个标识符中
    /*import * as foo from "./foo.js"
    console.log(foo.name)*/
    
    // 4、导入默认的导出
    import foo from "./foo.js"
    
    // 导出
    // 1、export声明语句
    /*export const age = 18
    export function foo() {
    }
    export class Person {
    }*/
    
    // 2、export导出和声明分开(named export)
    /*const age = 18
    function foo() {
    }
    export {
        age,
        foo
    }*/
    
    // 3、导出时起别名
    /*export {
        age as fAge,
        foo as fFoo
    }*/
    
    // 4、默认导出(default export)
    // 注意:默认导出只能有一个
    // export {foo as default}
    export default foo//常用
    
    // 导入/导出
    // 1、多个文件统一导出
    /*export {add} from "./math.js"
    export {timeFormat} from "./format.js"*/
    export * from "./math.js"
    export * from "./format.js"
    
    7、import函数
    // 是同步的,会阻塞后面代码的运行
    // import {name} from "./foo.js"
    
    // import函数返回的结果是一个Promise。异步的,不会阻塞后面代码的运行
    import("./foo.js").then(res => {
        console.log(res)
    })
    
    // ES11新增的特性
    // meta属性本身也是一个对象:{url:"当前模块所在的路径"}
    console.log(import.meta)
    
    8、ES Module的解析流程
    /**
     * 1、ES Module的解析过程可以划分为三个阶段
     *     - 构件(Construction),根据地址查找js文件,并且下载,
     *       将其解析成模块记录(Module Record)
     *     - 实例化(Instantiation),对模块记录进行实例化,并且分配内存空间,
     *       解析模块的导入和导出语句,把模块指向对应的内存地址(Module
     *       environment Record)
     *     - 运行(Evaluation),运行代码,计算值,并且将值填充到内存地址中
     * 2、ES Module是基于静态分析的
     * 3、所有的模块记录会有一个对应的映射关系(MODULE MAP)
     * 4、导出的模块可以更新变量的值,但是导入的模块不可以更新变量的值
     */
    
    9、ES Module和CommonJS相互调用
    * 在浏览器中(不能)
    * node环境中(与版本有关系)
    * 平时开发中使用webpack打包工具(可以)
    

    二十二、包管理工具详解npm、yarn、cnpm、npx

    1、包管理工具npm
    * npm(Node Package Manager),也就是Node包管理器
    * package.json常见属性
        - name:是项目的名称(必填)
        - version:是当前项目的版本号(必填)
        - description:是描述信息,很多时候是作为项目的基本描述
        - author:是作者相关信息(发布时用到)
        - license:是开源协议(发布时用到)
        - private:为true则npm是不能发布它的(npm publish)
        - main:使用node_modules中的模块时,是通过main
          属性查找对应的文件的(发布时用到)
        - scripts:用于配置一些脚本命令,以键值对的形式存在
          键为start、test、stop、restart时可以省略掉run
        - dependencies:开发和生产环境需要依赖的包(vue、axios、vuex)
        - devDependencies:生产环境不需要依赖的包(webpack、babel)
        - peerDependencies:依赖包所必须的宿主包(element-plus需要vue3)
        - engines:用于指定Node和NPM的版本号,
          也可以指定操作系统:"os":["darwin","linux"]
    * browserslist、eslintConfig等属性,可在package.json中配置,也可独立文件配置
    
    2、依赖的版本管理
    * npm的包通常需要遵从semver版本规范,semver版本规范是X.Y.Z
        - X主版本号(major):当你做了不兼容的API修改(可能不兼容之前的版本)
        - Y次版本号(minor):当你做了向下兼容的功能性新增(新功能增加,但是兼容之前的版本)
        - Z修订号(patch):当你做了向下兼容的问题修正(没有新功能,修复了之前版本的bug)
    * ^和~的区别
        - ^X.Y.Z:表示X是保持不变的,Y和Z永远安装最新的版本
        - ~X.Y.Z:表示X和Y保持不变的,Z永远安装最新的版本
    
    3、npm install原理
    // 1、npm install原理
    /*
    * 没有lock文件
        - 分析依赖关系,这是因为我们可能包会依赖其他的包,并且多个包之间会产生相同依赖的情况
        - 从registry仓库中下载压缩包(如果我们设置了镜像,那么会从镜像服务器下载压缩包)
        - 获取到压缩包后会对压缩包进行缓存(从npm5开始有的)
        - 将压缩包解压到项目的node_modules文件夹中(前面我们讲过,require的查找顺序会在该包下面查找)
    * 有lock文件
        - 监测lock中包的版本是否和package.json中一致(会按照semver版本规范检测)
          不一致,那么会重新构建依赖关系,直接会走顶层的流程
        - 一致的情况下,会去优先查找缓存
          没有找到,会从registry仓库下载,直接走顶层流程
        - 查找到,会获取缓存中的压缩文件,并且将压缩文件解压到node_modules文件夹中*/
    
    // 2、package-lock.json
    /*name:项目的名称
    version:项目的版本
    lockfileVersion:lock文件的版本
    requires:使用requires来跟踪模块的依赖关系
    dependencies:项目的依赖
        axios:依赖的模块名
            version:表示实际安装的axios版本
            resolved:用来记录下载的地址,registry仓库中的位置
            requires:记录当前模块的依赖
            integrity:用来从缓存中获取索引,再通过索引去获取压缩包文件*/
    
    // 3、其他命令
    /*npm rebuild:强制重新build
    npm get cache:获取缓存目录
    npm cache clean:清除缓存*/
    
    4、yarn工具
    npm yarn
    npm install yarn install
    npm install [package] yarn add [package]
    npm rebuild yarn install --force
    npm uninstall [package] yarn remove [package]
    5、cnpm工具
    npm config get registry
    npm config set registry https://registry.npm.taobao.org/
    npm config set registry https://registry.npm.org/
    
    6、npx工具
    * npx常用来调用项目中的某个模块的指令
        - webpack --version:环境变量
        - npx webpack --version:项目模块
    
    7、发布自己的包
    * 登录:npm login
    * "homepage": "https://github.com/coderwhy/coderwhy",
      "repository": {
        "type": "git",
        "url": "https://github.com/coderwhy/coderwhy"
      },
      "keywords":["coder","why"]
    * 发布:npm publish
    * 修改版本号重新发布
    * 删除发布的包:npm unpublish
    * 让发布的包过期:npm deprecate
    

    二十三、JSON-数据存储

    1、JSON的序列化
    /**
     * 1、JSON的全称是JavaScript Object Notation(JavaScript对象符号)
     * 2、其他的传输格式:JSON、XML、Protobuf
     * 3、JSON的顶层支持三种类型的值(函数不转化)
     *     - 简单值(不支持单引号、不能有undefined)
     *     - 对象值(key必须是字符串)
     *     - 数组值
     */
    const obj = {
        name: "黄婷婷",
        age: 18,
        friend: {
            name: "孟美岐"
        },
        hobbies: ["篮球", "足球"]
    }
    // 将obj转成JSON格式字符串
    const obj2str = JSON.stringify(obj);
    // 将对象数据存储localStorage
    localStorage.setItem("obj", obj2str)
    const info = localStorage.getItem("obj");
    // 将JSON格式的字符串转回对象
    const str2obj = JSON.parse(info);
    console.log(str2obj)
    
    2、JSON.stringify
    const obj = {
        name: "黄婷婷",
        age: 18,
        friend: {
            name: "孟美岐"
        },
        hobbies: ["篮球", "足球"],
        // toJSON: function () {
        //     return "鞠婧祎"
        // }
    }
    // 1、直接转
    const str1 = JSON.stringify(obj);
    console.log(str1)
    // 2、参数2:replacer传数组
    const str2 = JSON.stringify(obj, ["name", "friend"]);
    console.log(str2)
    // 3、参数2:replacer传回调函数
    const str3 = JSON.stringify(obj, (key, value) => {
        return value
    });
    console.log(str3)
    // 4、参数3:space(数值、字符串)
    const str4 = JSON.stringify(obj, null, 2);
    console.log(str4)
    // 5、如果对象中有toJSON方法
    const str5 = JSON.stringify(obj);
    console.log(str5)
    
    3、JSON.parse
    const str = '{"name":"黄婷婷","friend":{"name":"孟美岐"},"hobbies":["篮球","足球"]}'
    // 1、参数2:reviver
    const obj = JSON.parse(str, (key, value) => {
        return value
    });
    console.log(obj)
    
    4、浅拷贝和深拷贝
    const obj = {
        name: "黄婷婷",
        age: 18,
        friend: {
            name: "孟美岐"
        },
        hobbies: ["篮球", "足球"]
    }
    // 1、浅拷贝:不同引用指向同一内存
    const objCopy1 = {...obj}
    const objCopy2 = Object.assign({}, obj)
    // 2、深拷贝:源对象与拷贝对象互相独立
    const objCopy3 = JSON.parse(JSON.stringify(obj));
    

    二十四、浏览器存储方案

    1、认识Storage
    /**
     * 1、localStorage和sessionStorage的区别
     *     - 关闭网页后重新打开,localStorage会保留,而sessionStorage会被删除
     *     - 在页面内实现跳转,localStorage会保留,sessionStorage也会保留
     *     - 在页面外实现跳转(打开新的网页),localStorage会保留,sessionStorage不会被保留
     */
    localStorage.setItem("name", "黄婷婷")
    sessionStorage.setItem("name", "孟美岐")
    
    // 2、Storage常见的属性和方法
    // 2.1、setItem
    localStorage.setItem("person", "张婧仪")
    // 2.2、length
    console.log(localStorage.length)
    // 2.3、key
    console.log(localStorage.key(0))
    // 2.4、getItem
    console.log(localStorage.getItem("person"))
    // 2.5、removeItem
    // localStorage.removeItem("person")
    // 2.6、clear
    // localStorage.clear()
    
    2、认识IndexedDB
    // 创建数据库/打开数据库
    // 参数1:数据库名;参数2:数据库版本
    const dbRequest = indexedDB.open("coder", 1)
    dbRequest.onerror = function (err) {
        console.log("打开数据库失败~")
    }
    let db = null
    dbRequest.onsuccess = function (event) {
        db = event.target.result
    }
    // 创建数据库或更新数据库版本会调用此函数
    dbRequest.onupgradeneeded = function (event) {
        const db = event.target.result
        // 创建一些存储对象(相当于创建数据库表)
        db.createObjectStore("person", {keyPath: "id"})
    }
    
    // 新增操作
    function insertIntoHandler() {
        const transaction = db.transaction(["person"], "readwrite");
        const store = transaction.objectStore("person");
    
        let id = new Date().getTime()
        const arr = [
            {id: ++id, name: "黄婷婷", age: 18},
            {id: ++id, name: "孟美岐", age: 19},
            {id: ++id, name: "姜贞羽", age: 20},
        ]
        arr.forEach(li => {
            const request = store.add(li);
            request.onsuccess = function () {
                console.log(`${li.name}添加成功`)
            }
        })
        transaction.oncomplete = function () {
            console.log("添加操作全部完成")
        }
    }
    
    // 删除操作
    function deleteHandler() {
        const transaction = db.transaction(["person"], "readwrite");
        const store = transaction.objectStore("person");
    
        const request = store.openCursor();
        request.onsuccess = function (event) {
            const cursor = event.target.result
            if (cursor) {
                const value = cursor.value
                if (value.name === "姜贞羽") {
                    cursor.delete()
                }
                cursor.continue()
            }
        }
    }
    
    // 更新操作
    function updateHandler() {
        const transaction = db.transaction(["person"], "readwrite");
        const store = transaction.objectStore("person");
    
        const request = store.openCursor();
        request.onsuccess = function (event) {
            const cursor = event.target.result
            if (cursor) {
                const value = cursor.value
                if (value.name === "黄婷婷") {
                    value.age = 16
                    cursor.update(value)
                }
                cursor.continue()
            }
        }
    }
    
    // 查询操作
    function selectHandler() {
        const transaction = db.transaction(["person"], "readwrite");
        const store = transaction.objectStore("person");
    
        // 1、通过主键查询
        /*const request = store.get(1647919664300);
        request.onsuccess = function (event) {
            console.log(event.target.result)
        }*/
    
        // 2、通过游标查询
        const request = store.openCursor();
        request.onsuccess = function (event) {
            const cursor = event.target.result
            if (cursor) {
                console.log(cursor.key, cursor.value)
                cursor.continue()
            }
        }
    }
    

    二十五、Cookie-BOM-DOM

    1、Cookie
    /**
     * 1、认识Cookie
     *     - Cookie由服务端维护
     *     - 服务端在响应头里设置Cookie(Set-Cookie)
     *     - 客户端每次请求都会在请求头里带上Cookie(Cookie)
     * 2、Cookie常见属性
     *     - expires:设置过期时间(Date)
     *     - max-age:设置过期时间(秒)
     *     - domain:设置可接收的域名
     *     - path:设置可接收的路径
     *     - httpOnly:为true则表示只能通过http/https方式操作cookie
     */
    // 设置
    document.cookie = "name=黄婷婷"
    setTimeout(() => {
        // 删除
        document.cookie = "name=;max-age=0"
    }, 3000)
    
    2、BOM
    /**
     * 1、BOM:Browser Object Model
     * 2、BOM主要包括以下的对象模型
     *     - window:包括全局属性、方法,控制浏览器窗口相关的属性方法
     *     - location:浏览器连接到的对象的位置(URL)
     *     - history:操作浏览器的历史
     *     - document:当前窗口操作文档的对象
     * 3、window对象在浏览器中有两个身份
     *     - 全局对象:
     *         全局声明的变量
     *         全局默认提供的函数和类:setTimeout、Math、Date、Object
     *     - 浏览器窗口对象:
     *         60+的属性:localStorage、console、location、history、screenX、scrollX
     *         40+的方法:alert、close、scrollTo、open
     *         30+的事件:focus、blur、load、hashchange
     *         EventTarget继承的方法:addEventListener、removeEventListener、dispatchEventListener
     * 4、MDN文档不同符号意思:
     *     - 删除符号:已废弃
     *     - 点踩符号:浏览器提供的API,W3C未规范
     *     - 实验符号:API尚在试验阶段
     */
    // 一、window
    // 1、常见的属性
    console.log(window.screenX, window.screenY)
    console.log(window.scrollX, window.scrollY)
    console.log(window.innerWidth, window.innerHeight)
    console.log(window.outerWidth, window.outerHeight)
    
    // 2、常见的方法
    // window.scrollTo({top: 2000})
    // window.close()
    // window.open("")
    
    // 3、常见的事件
    window.onload = function () {
        console.log("window窗口加载完毕~")
    }
    window.onfocus = function () {
        console.log("window窗口获取焦点~")
    }
    window.onblur = function () {
        console.log("window窗口失去焦点~")
    }
    window.onhashchange = function () {
        console.log("hash发生了改变~")
    }
    
    // 4、EventTarget继承的方法
    // 4.1addEventListener和removeEventListener
    const clickHandler = () => {
        console.log("window发生了点击")
    }
    window.addEventListener("click", clickHandler)
    // window.removeEventListener("click", clickHandler)
    
    // 4.2dispatchEvent
    window.addEventListener("coder", () => {
        console.log("监听到了coder事件")
    })
    window.dispatchEvent(new Event("coder"))
    
    // 二、localtion
    /**
     * 1、常见属性
     *     - href:当前window对应的超链接URL,整个URL
     *     - protocol:当前的协议
     *     - host:主机地址
     *     - hostname:主机地址(不带端口)
     *     - port:端口
     *     - pathname:路径
     *     - search:查询字符串
     *     - hash:哈希值
     *     - username:URL中的username(很多浏览器已经禁用)
     *     - password:URL中的password(很多浏览器已经禁用)
     *       协议://username:password@主机:端口
     */
    // 2、常见方法
    // location.assign("")// location.href=""
    // location.replace("")
    // location.reload(true)// true表示强制重新加载,false表示从缓存加载
    
    // 三、history
    /**
     * 1、属性
     *     - length:会话中的记录条数
     *     - state:当前保留的状态值
     * 2、方法
     *     - back():返回上一页,等价于history.go(-1)
     *     - forward():前进下一页,等价于history.go(1)
     *     - go():加载历史中的某一页
     *     - pushState():打开一个指定的地址
     *     - replaceState():打开一个新的地址,并且使用replace
     */
    history.pushState({name: "coder"}, "", "/coder")
    
    3、DOM
    // 一、认识DOM
    // 1、DOM:Document Object Model
    // 2、继承关系:
    /*
    EventTarget:{
        Node:{
            Document:{
                HTMLDocument,
                XMLDocument
            },
            Element:{
                HTMLElement:{
                    HTMLDivElement,
                    HTMLImageElement
                }
            },
            CharacterData:{
                Text,
                Comment
            },
            Attr
        }
    }
    */
    
    // 二、EventTarget
    /*window.onload = function () {
        document.addEventListener("click", () => {
            console.log("点击document")
        })
        const btn = document.querySelector("button");
        btn.addEventListener("click", () => {
            console.log("点击button")
        })
    }*/
    
    // 三、Node
    /*window.onload = function () {
        // 1、属性
        const spanEl = document.querySelector("span");
        console.log(spanEl.nodeName)
        console.log(spanEl.nodeType)
        const spanChildNodes = spanEl.childNodes
        console.log(spanChildNodes)
        const textNode = spanChildNodes[0]
        console.log(textNode.nodeValue)
        // 2、方法
        const strongEl = document.createElement("strong");
        strongEl.textContent = "孟美岐"
        spanEl.appendChild(strongEl)
        // 3、注意:document.appendChild()会报错
        document.body.appendChild(strongEl)
    }*/
    
    // 四、Document
    /*window.onload = function () {
        // 1、属性
        console.log(document.body)
        console.log(document.title)
        document.title = "黄婷婷"
        console.log(document.head)
        console.log(document.children[0])
        console.log(window.location)
        console.log(document.location)
        console.log(window.location === document.location)// true
        // 2、方法
        // 2.1、创建元素
        const imageEl1 = document.createElement("img");
        const imageEl2 = new Image()// HTMLImageElement的构造函数
        // 2.2、获取元素
        // <button id="btn" name="btn">按钮</button>
        const btn1 = document.getElementById("btn");
        const btn2 = document.getElementsByTagName("button");
        const btn3 = document.getElementsByName("btn");
        const btn4 = document.querySelector("#btn");
        const btn5 = document.querySelectorAll("button");
    }*/
    
    // 五、Element
    window.onload = function () {
        // 1、属性
        // <span id="sp" class="sp" gender="女">黄婷婷</span>
        const spanEl = document.querySelector("span");
        console.log(spanEl.id)
        console.log(spanEl.tagName)
        console.log(spanEl.children)
        console.log(spanEl.className)
        console.log(spanEl.classList)
        console.log(spanEl.clientWidth)
        console.log(spanEl.clientHeight)
        console.log(spanEl.offsetLeft)
        console.log(spanEl.offsetTop)
        // 2、方法
        console.log(spanEl.getAttribute("gender"));
        spanEl.setAttribute("friend", "孟美岐")
    }
    
    4、事件冒泡和事件捕获
    /**
     * 1、事件监听方式
     *     - 在script中直接监听
     *     - 通过元素的on来监听事件
     *     - 通过EventTarget中的addEventListener来监听
     * 2、事件捕获和事件冒泡
     *     - addEventListener()参数3:为true事件句柄在捕获阶段执行,
     *       为false(默认)事件句柄在冒泡阶段执行
     *     - 元素关系:<body><div><span>黄婷婷</span></div></body>
     *       事件流:先捕获body -> div -> span,再冒泡span -> div -> body
     */
    window.onload = function () {
        document.body.addEventListener("click", event => {
            // 3、事件对象
            // 3.1、属性
            console.log(event.type)
            console.log(event.target)// 事件触发的元素
            console.log(event.currentTarget)// 事件绑定的元素
            console.log(event.offsetX, event.offsetY)
            console.log("捕获:body")
            // 3.2、方法
            // 阻止事件流传播,阻止之后与此相同元素且相同事件类型的监听器被调用
            // event.stopImmediatePropagation()
            event.stopPropagation()// 阻止事件流传播
            event.preventDefault()// 阻止默认事件
        }, true)
        document.querySelector("div").addEventListener("click", event => {
            console.log("捕获:div")
        }, true)
        document.querySelector("span").addEventListener("click", event => {
            console.log("捕获:span")
        }, true)
        document.querySelector("span").addEventListener("click", event => {
            console.log("冒泡:span")
        })
        document.querySelector("div").addEventListener("click", event => {
            console.log("冒泡:div")
        })
        document.body.addEventListener("click", event => {
            console.log("冒泡:body")
        })
    }
    

    二十六、防抖-节流-深拷贝-事件总线

    1、防抖
    /**
     * 1、认识防抖(debounce):事件函数会等待一段时间被调用,
     *   当事件再次触发的时间小于等待时间,则会重置等待时间
     * 2、防抖函数应用场景
     *     - 输入框input事件
     *     - 按钮频繁click事件
     *     - 滚轮scroll事件
     *     - 元素resize事件
     */
    // <input type="text" id="ipt"><button id="btn">取消</button>
    window.onload = function () {
        const ipt = document.getElementById("ipt");
        const btn = document.getElementById("btn");
    
        let count = 0
    
        function iptChange() {
            console.log(`发送了${count++}次请求`)
        }
    
        function debounce(fn, delay, immediate = false, resultCallback) {
            let timer = null
            // 为了不改传进来的参数
            let isInvoke = false
    
            const _debounce = function (...args) {
                // 返回函数返回值
                return new Promise((resolve, reject) => {
                    if (timer) clearInterval(timer)
                    // 是否立即执行一次
                    if (immediate && !isInvoke) {
                        const result = fn.apply(this, args);
                        if (resultCallback) resultCallback(result)
                        resolve(result)
                        isInvoke = true
                    } else {
                        timer = setTimeout(() => {
                            console.log(this)
                            const result = fn.apply(this, args)
                            if (resultCallback) resultCallback(result)
                            resolve(result)
                            isInvoke = false
                            timer = null
                        }, delay)
                    }
                })
            }
    
            // 取消将要执行的函数
            _debounce.cancel = function () {
                if (timer) clearTimeout(timer)
                timer = null
                isInvoke = false
            }
    
            return _debounce
        }
    
        // 为了测试返回值
        const debounceResult = debounce(iptChange, 3000);
        ipt.oninput = function () {
            debounceResult.apply(this).then(res => {
                console.log(res)
            })
        }
        // 测试取消
        btn.onclick = debounceResult.cancel
    }
    
    2、节流
    /**
     * 1、认识节流(throttle):设定的周期时间内,多次触发的事件,只调用一次
     * 2、节流函数应用场景
     *     - 滚轮scroll事件
     *     - 鼠标move事件
     *     - 按钮频繁click事件
     */
    // <input type="text" id="ipt"><button id="btn">取消</button>
    window.onload = function () {
        const ipt = document.getElementById("ipt");
        const btn = document.getElementById("btn");
    
        let count = 0
    
        function iptChange() {
            console.log(`发送了${count++}次请求`)
            return '孟美岐'
        }
    
        function throttle(fn, interval, options = {leading: true, trailing: false}) {
            const {leading, trailing, resultCallback} = options
            let lastTime = 0
            let timer = null
            const _throttle = function (...args) {
                return new Promise((resolve, reject) => {
                    const nowTime = new Date().getTime()
                    if (!lastTime && !leading) lastTime = nowTime
                    const remainTime = interval - (nowTime - lastTime)
                    if (remainTime <= 0) {
                        if (timer) {
                            clearTimeout(timer)
                            timer = null
                        }
                        const result = fn.apply(this, args)
                        if (resultCallback) resultCallback(result)
                        resolve(result)
                        lastTime = nowTime
                        return
                    }
                    if (trailing && !timer) {
                        timer = setTimeout(() => {
                            timer = null
                            lastTime = !leading ? 0 : new Date().getTime()
                            const result = fn.apply(this, args)
                            if (resultCallback) resultCallback(result)
                            resolve(result)
                        }, remainTime)
                    }
                })
            }
            _throttle.cancel = function () {
                if (timer) clearTimeout(timer)
                timer = null
                lastTime = 0
            }
            return _throttle
        }
    
        const throttleResult = throttle(iptChange, 3000, {leading: false, trailing: true});
        ipt.oninput = function () {
            throttleResult.apply(this).then(res => {
                console.log(res)
            })
        }
        btn.onclick = throttleResult.cancel
    }
    
    3、深拷贝
    function isObject(value) {
        const valueType = typeof value
        return (value != null) && (valueType === "object" || valueType === "function")
    }
    
    function deepClone(originValue, map = new WeakMap()) {
        // 判断是否是一个Set类型
        if (originValue instanceof Set) {
            return new Set([...originValue])
        }
        // 判断是否是一个Map类型
        if (originValue instanceof Map) {
            return new Map([...originValue])
        }
        // 判断如果是Symbol的value,那么创建一个新的Symbol
        if (typeof originValue === "symbol") {
            return Symbol(originValue.description)
        }
        // 判断如果是函数类型,那么直接使用同一个函数
        if (typeof originValue === "function") {
            return originValue
        }
        // 判断传入的originValue是否是一个对象类型
        if (!isObject(originValue)) {
            return originValue
        }
        if (map.has(originValue)) {
            return map.get(originValue)
        }
        // 判断传入的对象是数组,还是对象
        const newObject = Array.isArray(originValue) ? [] : {}
        map.set(originValue, newObject)
        for (const key in originValue) {
            newObject[key] = deepClone(originValue[key], map)
        }
        // 对Symbol的key进行特殊的处理
        const symbolKeys = Object.getOwnPropertySymbols(originValue)
        for (const sKey of symbolKeys) {
            newObject[sKey] = deepClone(originValue[sKey], map)
        }
        return newObject
    }
    
    4、自定义事件总线
    class HYEventBus {
        constructor() {
            this.eventBus = {}
        }
    
        on(eventName, eventCallback, thisArg) {
            let handlers = this.eventBus[eventName]
            if (!handlers) {
                handlers = []
                this.eventBus[eventName] = handlers
            }
            handlers.push({
                eventCallback,
                thisArg
            })
        }
    
        off(eventName, eventCallback) {
            const handlers = this.eventBus[eventName]
            if (!handlers) return
            const newHandlers = [...handlers]
            for (let i = 0; i < newHandlers.length; i++) {
                const handler = newHandlers[i]
                if (handler.eventCallback === eventCallback) {
                    const index = handlers.indexOf(handler)
                    handlers.splice(index, 1)
                }
            }
        }
    
        emit(eventName, ...payload) {
            const handlers = this.eventBus[eventName]
            if (!handlers) return
            handlers.forEach(handler => {
                handler.eventCallback.apply(handler.thisArg, payload)
            })
        }
    }
    
  • 相关阅读:
    XenServer 7 上Linux单用户模式下修改密码
    python pysnmp使用
    处理一则MySQL Slave环境出现ERROR 1201 (HY000): Could not initialize master info structure的案例。
    H3C交换机端口聚合
    H3C交换机密码策略
    使用Chrome远程调试GenyMotion上的WebView程序
    CefGlue在WinXP下闪退的排查方法
    Visual Studio 2015 开发Android Cordova出现unsupported major minor version 52.0错误的解决方法
    股票涨跌预测器
    javascript模块化编程-详解立即执行函数表达式IIFE
  • 原文地址:https://www.cnblogs.com/linding/p/15991019.html
Copyright © 2020-2023  润新知