目录
十五、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)
})
}
}