JavaScript中手写深拷贝函数-事件总线

导读:本篇文章讲解 JavaScript中手写深拷贝函数-事件总线,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

1.手写深拷贝函数

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

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

JavaScript并没有给我们提供深拷贝的方法, 因为深拷贝是非常消耗内存的

前面我们已经可以通过JSON的方法来实现深拷贝了:JSON.parse

  • 这种深拷贝的方式其实对于函数、Symbol等是无法处理的;
  • 并且如果存在对象的循环引用,也会报错的;

那么, 如果实际开发中真的需要深拷贝, 当然这种情况是非常少的, 但是如果真的需要, 我们就需要自定义深拷贝函数了

手写深拷贝我们按照以下步骤:

  1. 自定义深拷贝的基本功能;
  2. 其他数据类型的值进程处理:数组、函数、Symbol、Set、Map;
  3. 对Symbol的key进行处理;

1.1 基本功能实现

首先按照我们实现一个工具函数的步骤:

  1. 需要接收什么参数
  2. 返回值是什么
  3. 内部实现

1.需要接收的参数

  • 接收一个对象作为参数, 对这个传入的对象进行深拷贝

    function deepCopy(obj) {
      
    }
    

2.返回值是什么

  • 返回一个已经完成深拷贝的新对象

    function deepCopy(obj) {
      const newObj = {}
      return newObj
    }
    

3.内部实现

  • 我们思考一下, 如何判断是否是一个对象呢, 大家应该都想到了typeof, 但是我们每个地方都使用typeof是有点麻烦的, 而且如果传入的对象为null(null也是一个对象), 我们想要返回false

  • 一般遇到这种情况, 我们会单独封装一个小工具函数用来判断是否是一个对象, 这也是很多库和框架的做法

    function isObject(value) {
      const valueType = typeof value
      return (valueType !== null) && (valueType === "function" || valueType === "object")
    }
    
  • 然后我们再实现内部代码

    // 判断是否是对象
    function isObject(value) {
      const valueType = typeof value
      return (value !== null) && (valueType === "function" || valueType === "object")
    }
    
    // 深拷贝函数
    function deepCopy(obj) {
      // 1.如果是原始类型, 直接返回即可
      if (!isObject(obj)) return obj
    
      // 2.如果是对象类型,需要创建对象
      const newObj = {}
      // 3.对传入的对象进行遍历
      for (const key in obj) {
        // 4.对传入对象的属性值进行递归调用
        newObj[key] = deepCopy(obj[key])
      }
      return newObj
    }
    
  • 这样深拷贝的基本功能已经实现, 我们测试一下

    const info = {
      name: "kaisa",
      age: 18,
      friend: {
        name: "chen",
        address: {
          province: "四川省",
          city: "成都市"
        }
      }
    }
    
    // 进行深拷贝
    const newInfo = deepCopy(info)
    
    // 测试
    console.log(newInfo)
    
    // 修改原对象, 不会影响新对象
    info.friend.address.province = "重庆市"
    console.log(info.friend.address.province) // 重庆市
    console.log(newInfo.friend.address.province) // 四川省
    

1.2 其他数据类型值处理

  • 特殊类型数组: 我们在传入值的时候, 有可能传入一个数组对象, 传入数组的话我们只需添加一行代码就可以解除这个问题

    function isObject(value) {
      const valueType = typeof value
      return (value !== null) && (valueType === "function" || valueType === "object")
    }
    
    // 深拷贝函数
    function deepCopy(obj) {
      if (!isObject(obj)) return obj
    
      // 判断是否是一个数组, 如果是创建数组, 不是创建对象
      const newObj = Array.isArray(obj) ? [] : {}
      for (const key in obj) {
        newObj[key] = deepCopy(obj[key])
      }
      return newObj
    }
    
  • 特殊类型Set, Map: 如果是Set, Map类型我们也需要做对应处理(这里只写一个set)

    function isObject(value) {
      const valueType = typeof value
      return (value !== null) && (valueType === "function" || valueType === "object")
    }
    
    // 深拷贝函数
    function deepCopy(obj) {
      if (!isObject(obj)) return obj
    
      // 2.判断是否是set类型
      if (obj instanceof Set) {
        const newSet = new Set()
        for (setItem of obj) {
          newSet.add(deepCopy(setItem))
        }
        return newSet
      }
    
      // 1.判断是否是一个数组, 如果是创建数组, 不是创建对象
      const newObj = Array.isArray(obj) ? [] : {}
      for (const key in obj) {
        newObj[key] = deepCopy(obj[key])
      }
      return newObj
    }
    
  • 特殊类型函数: 函数在开发中是用来执行的, 所以我们函数我们不需要进行深拷贝, 我们对函数也需处理一下

    function isObject(value) {
      const valueType = typeof value
      return (value !== null) && (valueType === "function" || valueType === "object")
    }
    
    // 深拷贝函数
    function deepCopy(obj) {
      if (!isObject(obj)) return obj
    
      // 2.判断是否是set类型
      if (obj instanceof Set) {
        const newSet = new Set()
        for (setItem of obj) {
          newSet.add(deepCopy(setItem))
        }
        return newSet
      }
    
      // 3.判断是否是函数, 是函数直接返回
      if (typeof obj === "function") {
        return obj
      }
    
      // 1.判断是否是一个数组, 如果是创建数组, 不是创建对象
      const newObj = Array.isArray(obj) ? [] : {}
      for (const key in obj) {
        newObj[key] = deepCopy(obj[key])
      }
      return newObj
    }
    
  • 特殊类型值为Symbol: 值为Symbol我们也需要进行单独处理

    function isObject(value) {
      const valueType = typeof value
      return (value !== null) && (valueType === "function" || valueType === "object")
    }
    
    // 深拷贝函数
    function deepCopy(obj) {
      // 4.判断是否是Symbol类型
      if (typeof obj === "symbol") {
        return Symbol(obj.description)
      }
    
      if (!isObject(obj)) return obj
    
      // 2.判断是否是set类型
      if (obj instanceof Set) {
        const newSet = new Set()
        for (setItem of obj) {
          newSet.add(deepCopy(setItem))
        }
        return newSet
      }
    
      // 3.判断是否是函数, 是函数直接返回
      if (typeof obj === "function") {
        return obj
      }
    
      // 1.判断是否是一个数组, 如果是创建数组, 不是创建对象
      const newObj = Array.isArray(obj) ? [] : {}
      for (const key in obj) {
        newObj[key] = deepCopy(obj[key])
      }
      return newObj
    }
    
  • 特殊类型key为Symbol:key为Symbol时, for…in不会遍历Symbol, 我们需要对Symbol单独进行遍历

    function isObject(value) {
      const valueType = typeof value
      return (value !== null) && (valueType === "function" || valueType === "object")
    }
    
    // 深拷贝函数
    function deepCopy(obj) {
      // 4.判断是否是Symbol类型
      if (typeof obj === "symbol") {
        return Symbol(obj.description)
      }
    
      if (!isObject(obj)) return obj
    
      // 2.判断是否是set类型
      if (obj instanceof Set) {
        const newSet = new Set()
        for (setItem of obj) {
          newSet.add(deepCopy(setItem))
        }
        return newSet
      }
    
      // 3.判断是否是函数, 是函数直接返回
      if (typeof obj === "function") {
        return obj
      }
    
      // 1.判断是否是一个数组, 如果是创建数组, 不是创建对象
      const newObj = Array.isArray(obj) ? [] : {}
      for (const key in obj) {
        newObj[key] = deepCopy(obj[key])
      }
    
      // 5.单独遍历Symbol
      const symbolKeys = Object.getOwnPropertySymbols(obj)
      for (const symbolKey of symbolKeys) {
        newObj[Symbol(symbolKey.description)] = deepCopy(obj[symbolKey])
      }
      return newObj
    }
    

如果还有其他特殊类型需要处理, 按照上面的逻辑追加即可

2.手写事件总线

自定义事件总线属于一种观察者模式,其中包括三个角色:

  • 发布者(Publisher):发出事件(Event);
  • 订阅者(Subscriber):订阅事件(Event),并且会进行响应(Handler);
  • 事件总线(EventBus):无论是发布者还是订阅者都是通过事件总线作为中台的;

当然我们可以选择一些第三方的库:

  • Vue2默认是带有事件总线的功能;
  • Vue3中推荐一些第三方库,比如mitt;

我们也可以实现自己的事件总线, 按照以下步骤:

  • 事件的监听方法on;
  • 事件的发射方法emit;
  • 事件的取消监听off;

我们创建一个类作为事件总线对象

// 类EventBus -> 事件总线对象
class HYEventBus {
  constructor() {
    this.eventMap = {}
  }

  on(eventName, eventFn) {
    let eventFns = this.eventMap[eventName]
    if (!eventFns) {
      eventFns = []
      this.eventMap[eventName] = eventFns
    }
    eventFns.push(eventFn)
  }
  
  off(eventName, eventFn) {
    let eventFns = this.eventMap[eventName]
    if (!eventFns) return
    for (let i = 0; i < eventFns.length; i++) {
      const fn = eventFns[i]
      if (fn === eventFn) {
        eventFns.splice(i, 1)
        break
      }
    }

    // 如果eventFns已经清空了
    if (eventFns.length === 0) {
      delete this.eventMap[eventName]
    }
  }

  emit(eventName, ...args) {
    let eventFns = this.eventMap[eventName]
    if (!eventFns) return
    eventFns.forEach(fn => {
      fn(...args)
    })
  }
}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/120109.html

(0)
seven_的头像seven_bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!