二一. 深拷贝_ 事件总线
前面我们已经学习了对象相互赋值的一些关系,分别包括:
- 引入的赋值:指向同一个对象,相互之间会影响;
- 对象的浅拷贝:只是浅层的拷贝,内部引入对象时,依然会相互影响;
- 对象的深拷贝:两个对象不再有任何关系,不会相互影响;
自定义深拷贝函数
v1. 乞丐版 简单拷贝
缺点:不能处理 函数 和 Symbol , 存在对象的循环引用,也会报错的
const s1=Symbol()
const s2=Symbol()
const obj={
name:"why",
friend:{
name:"coder"
},
foo:function(){},
[s1]:"abc",
s2:s2
}
const info= JSON.parse(JSON.stringify(obj))
console.log(info===obj); // false
v2. 基本实现 基本类型和对象
function isObject(value) {
const valueType = typeof value
return (value !== null) && (valueType === "object" || valueType === "function")
}
function deepClone(originValue) {
// 传入的originValue必须是对象类型
if (!isObject(originValue)) return originValue
const newObj= {}
for (const key in originValue) {
newObj[key] = deepClone(originValue[key])
}
return newObj
}
const obj = {
name: "why",
friend: {
name: "coder",
address: { city: "上海市" }
},
}
const newObj = deepClone(obj)
console.log(newObj === obj) // false
obj.friend.name = "kobe"
obj.friend.address.city = "成都"
console.log(newObj)
v3. 其他类型 数组、函数、Symbol、set、map
function isObject(value) {
const valueType = typeof value
return (value !== null) && (valueType === "object" || valueType === "function")
}
function deepClone(originValue) {
// 如果传入的值,不是对象和函数类型,那么就返回该值
if (!isObject(originValue)) return originValue
// 如果传入的值是数组/对象,则创建一个数组/对象
const newObj = Array.isArray(originValue) ? [] : {}
for (const key in originValue) {
console.log(key);
newObj[key] = deepClone(originValue[key])
}
// 如果value是map类型,则创建一个Map
if(originValue instanceof Map) return new Map([...originValue])
// 如果value是set类型,则创建一个Set
if(originValue instanceof Set) return new Set([...originValue])
// 如果key是symbol类型,进行特殊处理(for in只能遍历可迭代对象)
const symbolKeys = Object.getOwnPropertySymbols(originValue)
for (const symbolKey of symbolKeys) {
newObj[symbolKey] = deepClone(originValue[symbolKey])
}
// 如果value是symbol类型,则创建一个新的symbol
if (typeof originValue === "symbol") return Symbol(originValue.description)
// 如果value是函数类型,则使用同一个函数
if (typeof originValue === "function") return originValue
return newObj
}
let s1 = Symbol("aaa")
let s2 = Symbol("bbb")
const obj = {
name: "why",
friend: {
name: "coder",
address: { city: "上海市" }
},
foo: function () { },
arr: ["a", "b", "c"],
[s1]: "aaa",
s2: s2,
set: new Set(["aa", "bb", "cc"]),// map set 这里其实是做的浅拷贝
map: new Map([["aa","aa"],["bb","bb"]])
}
const newObj = deepClone(obj)
obj.friend.name = "kobe"
obj.friend.address.city = "成都"
console.log(newObj)
v4. 循环引用
function isObject(value) {
const valueType = typeof value
return (value !== null) && (valueType === "object" || valueType === "function")
}
// 注:new Map()放到全局是可以实现功能,但缺点是不会销毁,调用deepClone()的次数越多,Map越大
// 如果new Map()放到局部,那么每次调用都会生成一个新的Map,递归调用时取不到第一次生成的Map
// 最佳做法是放到参数,第一次会创建Map,递归调用时传入一个值就不会创建Map了
function deepClone(originValue, map= new WeakMap()) {
// 如果传入的值,不是对象和函数类型,那么就返回该值
if (!isObject(originValue)) return originValue
// 处理循环引用;注:位置必须在创建newObj前面
if(map.has(originValue)) return map.get(originValue)
// 如果传入的值是数组/对象,则创建一个数组/对象
const newObj = Array.isArray(originValue) ? [] : {}
map.set(originValue, newObj)
for (const key in originValue) {
console.log(key);
newObj[key] = deepClone(originValue[key], map)
}
// 如果value是map类型,则创建一个Map
if(originValue instanceof Map) return new Map([...originValue])
// 如果value是set类型,则创建一个Set
if(originValue instanceof Set) return new Set([...originValue])
// 如果key是symbol类型,进行特殊处理(for in只能遍历可迭代对象)
const symbolKeys = Object.getOwnPropertySymbols(originValue)
for (const symbolKey of symbolKeys) {
newObj[symbolKey] = deepClone(originValue[symbolKey], map)
}
// 如果value是symbol类型,则创建一个新的symbol
if (typeof originValue === "symbol") return Symbol(originValue.description)
// 如果value是函数类型,则使用同一个函数
if (typeof originValue === "function") return originValue
return newObj
}
let s1 = Symbol("aaa")
let s2 = Symbol("bbb")
const obj = {
name: "why",
friend: {
name: "coder",
address: { city: "上海市" }
},
foo: function () { },
arr: ["a", "b", "c"],
[s1]: "aaa",
s2: s2,
set: new Set(["aa", "bb", "cc"]),// map set 这里其实是做的浅拷贝
map: new Map([["aa","aa"],["bb","bb"]])
}
obj.info= obj
const newObj = deepClone(obj)
obj.friend.name = "kobe"
obj.friend.address.city = "成都"
console.log(newObj)
console.log(newObj.info.info.info);
自定义事件总线
自定义事件总线属于一种观察者模式,其中包括三个角色:
- 发布者(Publisher):发出事件(Event);
- 订阅者(Subscriber):订阅事件(Event),并且会进行响应(Handler);
- 事件总线(EventBus):无论是发布者还是订阅者都是通过事件总线作为中台的;
当然我们可以选择一些第三方的库:
- Vue2默认是带有事件总线的功能;
- Vue3中推荐一些第三方库,比如mitt;
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)
});
}
}
const eventBus = new HYEventBus()
// main.js
eventBus.on("abc", function () {
console.log(0, this);
}, { name: "111" })
const fn= function () {
console.log(1, this);
}
eventBus.on("abc",fn, { name: "111" })
// util.js
eventBus.emit("abc", 123)
//移除监听
eventBus.off("abc",fn)
eventBus.emit("abc", 123)