二一. 深拷贝_ 事件总线

41 阅读4分钟

二一. 深拷贝_ 事件总线

前面我们已经学习了对象相互赋值的一些关系,分别包括:

  • 引入的赋值:指向同一个对象,相互之间会影响;
  • 对象的浅拷贝:只是浅层的拷贝,内部引入对象时,依然会相互影响;
  • 对象的深拷贝:两个对象不再有任何关系,不会相互影响;

自定义深拷贝函数

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)