一路繁花似锦绣前程
失败的越多,成功才越有价值

导航

 

目录

十五、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+规范(es6前可引入bluebird或q这些第三方库来作为Promise使用)
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)
        })
    }
}
posted on 2022-03-10 20:00  一路繁花似锦绣前程  阅读(41)  评论(0编辑  收藏  举报