• 轻松拿下 JS 浅拷贝、深拷贝


    Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

    本文将由浅入深地讲解浅拷贝和深拷贝,知识图谱如下:

    image.png

    深拷贝和浅拷贝的区别?

    答:

    浅拷贝和深拷贝都是创建一份数据的拷贝。

    JS 分为原始类型和引用类型,对于原始类型的拷贝,并没有深浅拷贝的区别,我们讨论的深浅拷贝都只针对引用类型。

    • 浅拷贝和深拷贝都复制了值和地址,都是为了解决引用类型赋值后互相影响的问题。

    • 但是浅拷贝只进行一层复制,深层次的引用类型还是共享内存地址,原对象和拷贝对象还是会互相影响。

    • 深拷贝就是无限层级拷贝,深拷贝后的原对象不会和拷贝对象互相影响。

    网络上的很多文章觉得引用类型赋值就是浅拷贝,误导了很多人,但 lodash 中的浅拷贝和深拷贝总不会错吧,这么多项目都在用。

    为了验证上述理论的正确性,我们就用 lodash 来测试一下,lodash 中浅拷贝方法为 clone,深拷贝方法为 cloneDeep。

    前置知识

    两个对象指向同一地址, 用 == 运算符作比较会返回 true。

    两个对象指向不同地址, 用 == 运算符作比较会返回 false。

    const obj = {}
    const newObj = obj
    
    console.log(obj == newObj) // true
    复制代码
    const obj = {}
    const newObj = {}
    
    console.log(obj == newObj) // false
    复制代码

    引用类型互相赋值

    直接赋值,两个对象指向同一地址,就会造成引用类型之间互相影响的问题:

    const obj = {
      name: 'lin'
    }
    
    const newObj = obj
    obj.name = 'xxx' // 改变原来的对象
    
    console.log('原来的对象', obj)
    console.log('新的对象', newObj)
    
    console.log('两者指向同一地址', obj == newObj) 
    复制代码

    image.png

    使用浅拷贝

    使用 lodash 浅拷贝 clone 方法,让他们俩指向不同地址,即可解决这个问题:

    import { clone } from 'lodash'
    
    const obj = {
      name: 'lin'
    }
    
    const newObj = clone(obj)
    obj.name = 'xxx'     // 改变原来的对象
    
    console.log('原来的对象', obj)
    console.log('新的对象', newObj)
    
    console.log('两者指向同一地址', obj == newObj)
    复制代码

    image.png

    但是浅拷贝只能解决一层,更深层的对象还是会指向同一地址,互相影响:

    import { clone } from 'lodash'
    
    const obj = {
      person: {
        name: 'lin'
      }
    }
    
    const newObj = clone(obj)
    obj.person.name = 'xxx'    // 改变原来的对象
    
    console.log('原来的对象', obj)
    console.log('新的对象', newObj)
    
    console.log('更深层的指向同一地址', obj.person == newObj.person)
    复制代码

    image.png

    使用深拷贝

    这个时候,就需要使用深拷贝来解决:

    import { cloneDeep } from 'lodash'
    
    const obj = {
      person: {
        name: 'lin'
      }
    }
    
    const newObj = cloneDeep(obj)
    obj.person.name = 'xxx' // 改变原来的对象
    
    console.log('原来的对象', obj)
    console.log('新的对象', newObj)
    
    console.log('更深层的对象指向同一地址', obj.person == newObj.person)
    复制代码

    image.png

    理论验证完了,接下来我们就来实现浅拷贝和深拷贝。

    实现浅拷贝

    Object.assign

    const obj = {
      name: 'lin'
    }
    
    const newObj = Object.assign({}, obj)
    
    obj.name = 'xxx' // 改变原来的对象
    
    console.log(newObj) // { name: 'lin' } 新对象不变
    
    console.log(obj == newObj) // false 两者指向不同地址
    复制代码

    数组的 slice 和 concat 方法

    const arr = ['lin', 'is', 'handsome']
    const newArr = arr.slice(0)
    
    arr[2] = 'rich' // 改变原来的数组
    
    console.log(newArr) // ['lin', 'is', 'handsome']
    
    console.log(arr == newArr) // false 两者指向不同地址
    复制代码
    const arr = ['lin', 'is', 'handsome']
    const newArr = [].concat(arr)
    
    arr[2] = 'rich' // 改变原来的数组
    
    console.log(newArr) // ['lin', 'is', 'handsome'] // 新数组不变
    
    console.log(arr == newArr) // false 两者指向不同地址
    复制代码

    数组静态方法 Array.from

    const arr = ['lin', 'is', 'handsome']
    const newArr = Array.from(arr)
    
    arr[2] = 'rich' // 改变原来的数组
    
    console.log(newArr) // ['lin', 'is', 'handsome']
    
    console.log(arr == newArr) // false 两者指向不同地址
    复制代码

    扩展运算符

    const arr = ['lin', 'is', 'handsome']
    const newArr = [...arr]
    
    arr[2] = 'rich' // 改变原来的数组
    
    console.log(newArr) // ['lin', 'is', 'handsome'] // 新数组不变
    
    console.log(arr == newArr) // false 两者指向不同地址
    复制代码
    const obj = {
      name: 'lin'
    }
    
    const newObj = { ...obj }
    
    obj.name = 'xxx' // 改变原来的对象
    
    console.log(newObj) // { name: 'lin' } // 新对象不变
    
    console.log(obj == newObj) // false 两者指向不同地址
    复制代码

    实现深拷贝

    要求:

    • 支持对象、数组、日期、正则的拷贝。
    • 处理原始类型(原始类型直接返回,只有引用类型才有深拷贝这个概念)。
    • 处理 Symbol 作为键名的情况。
    • 处理函数(函数直接返回,拷贝函数没有意义,两个对象使用内存中同一个地址的函数,问题不大)。
    • 处理 DOM 元素(DOM 元素直接返回,拷贝 DOM 元素没有意义,都是指向页面中同一个)。
    • 额外开辟一个储存空间 WeakMap,解决循环引用递归爆栈问题(引入 WeakMap 的另一个意义,配合垃圾回收机制,防止内存泄漏)。

    先把答案贴出来:

    function deepClone (target, hash = new WeakMap()) { // 额外开辟一个存储空间WeakMap来存储当前对象
      if (target === null) return target // 如果是 null 就不进行拷贝操作
      if (target instanceof Date) return new Date(target) // 处理日期
      if (target instanceof RegExp) return new RegExp(target) // 处理正则
      if (target instanceof HTMLElement) return target // 处理 DOM元素
    
      if (typeof target !== 'object') return target // 处理原始类型和函数 不需要深拷贝,直接返回
    
      // 是引用类型的话就要进行深拷贝
      if (hash.get(target)) return hash.get(target) // 当需要拷贝当前对象时,先去存储空间中找,如果有的话直接返回
      const cloneTarget = new target.constructor() // 创建一个新的克隆对象或克隆数组
      hash.set(target, cloneTarget) // 如果存储空间中没有就存进 hash 里
    
      Reflect.ownKeys(target).forEach(key => { // 引入 Reflect.ownKeys,处理 Symbol 作为键名的情况
        cloneTarget[key] = deepClone(target[key], hash) // 递归拷贝每一层
      })
      return cloneTarget // 返回克隆的对象
    }
    复制代码

    测试一下:

    const obj = {
      a: true,
      b: 100,
      c: 'str',
      d: undefined,
      e: null,
      f: Symbol('f'),
      g: {
        g1: {} // 深层对象
      },
      h: [], // 数组
      i: new Date(), // Date
      j: /abc/, // 正则
      k: function () {}, // 函数
      l: [document.getElementById('foo')] // 引入 WeakMap 的意义,处理可能被清除的 DOM 元素
    }
    
    obj.obj = obj // 循环引用
    
    const name = Symbol('name')
    obj[name] = 'lin'  // Symbol 作为键
    
    const newObj = deepClone(obj)
    
    console.log(newObj)
    复制代码

    image.png

    接下来,我们一步一步拆解,如何写出这个深拷贝。

    前置知识

    要手写出一个还算不错的深拷贝,下面这些知识都可以用到。

    typeof 与 instanceof 有什么区别?

    typeof null 的结果是什么?为什么?

    for in

    Object.prototype.constructor

    WeakMap

    Reflect.ownKeys()

    觉得概念多,没关系,阿林会带你一步一步慢慢熟悉的。

    一行代码版本

    首先是一行代码版本:

    JSON.parse(JSON.stringify(obj))
    复制代码
    const obj = {
      person: {
        name: 'lin'
      }
    }
    
    const newObj = JSON.parse(JSON.stringify(obj))
    obj.person.name = 'xxx' // 改变原来的深层对象
    
    console.log(newObj) // { person: { name: 'lin' } } 新的深层对象不变
    复制代码

    但是这种方式存在弊端,会忽略undefinedsymbol函数

    const obj = {
      a: undefined,
      b: Symbol('b'),
      c: function () {}
    }
    
    const newObj = JSON.parse(JSON.stringify(obj))
    
    console.log(newObj) // {} 
    复制代码

    NaNInfinity-Infinity 会被序列化为 null

    const obj = {
      a: NaN,
      b: Infinity,
      c: -Infinity
    }
    
    const newObj = JSON.parse(JSON.stringify(obj))
    
    console.log(newObj)
    复制代码

    image.png

    而且还不能解决循环引用的问题:

    const obj = {
      a: 1
    }
    
    obj.obj = obj
    
    const newObj = JSON.parse(JSON.stringify(obj)) // 报错
    复制代码

    image.png

    这种一行代码版本适用于日常开发中深拷贝一些简单的对象,接下来,我们试着一步步深入手写一个深拷贝,处理各种边界问题。

    先实现一个浅拷贝

    function clone (obj) {
      const cloneObj = {} // 创建一个新的对象
      for (const key in obj) { // 遍历需克隆的对象
        cloneObj[key] = obj[key] // 将需要克隆对象的属性依次添加到新对象上
      }
      return cloneObj
    }
    复制代码

    浅拷贝之后,原对象和克隆对象更深层的对象指向同一地址,会互相影响。

    测试一下,

    const obj = {
      person: {
        name: 'lin'
      }
    }
    
    const newObj = clone(obj)
    obj.person.name = 'xxx'    // 改变原来的对象
    
    console.log('原来的对象', obj)
    console.log('新的对象', newObj)
    
    console.log('更深层的指向同一地址', obj.person == newObj.person)
    复制代码

    image.png

    简单版本

    现在用递归来实现深拷贝,让原对象和克隆对象不互相影响。

    function deepClone (target) {
      if (typeof target !== 'object') { // 如果是原始类型,无需继续拷贝,直接返回
        return target
      }
      // 如果是引用类型,递归实现每一层的拷贝
      const cloneTarget = {} // 定义一个克隆对象
      for (const key in target) { // 遍历原对象
        cloneTarget[key] = deepClone(target[key]) // 递归拷贝每一层
      }
      return cloneTarget // 返回克隆对象
    }
    复制代码

    测试一下:

    const obj = {
      person: {
        name: 'lin'
      }
    }
    
    const newObj = deepClone(obj)
    obj.person.name = 'xxx' // 改变原来的对象
    
    console.log('原来的对象', obj)
    console.log('新的对象', newObj)
    
    console.log('更深层的指向同一地址', obj.person == newObj.person)
    复制代码

    image.png

    处理数组、日期、正则、null

    上文的方法实现了最简单版本的深拷贝,但是没有处理 null 这种原始类型,也没有处理数组、日期和正则这种比较常用的引用类型。

    测试一下,

    const obj = {
      a: [],
      b: new Date(),
      c: /abc/,
      d: null
    }
    
    const newObj = deepClone(obj)
    
    console.log('原来的对象', obj)
    console.log('新的对象', newObj)
    复制代码

    image.png

    现在来处理一下:

    function deepClone (target) {
      if (target === null) return target // 处理 null
      if (target instanceof Date) return new Date(target) // 处理日期
      if (target instanceof RegExp) return new RegExp(target) // 处理正则
      
      if (typeof target !== 'object') return target // 处理原始类型
      
      // 处理对象和数组
      const cloneTarget = new target.constructor() // 创建一个新的克隆对象或克隆数组
      for (const key in target) { // 递归拷贝每一层
        cloneTarget[key] = deepClone(target[key]) 
      }
      return cloneTarget
    }
    复制代码

    测试一下:

    const obj = {
      a: [1, 2, 3],
      b: new Date(),
      c: /abc/,
      d: null
    }
    
    const newObj = deepClone(obj)
    
    console.log('原来的对象', obj)
    console.log('新的对象', newObj)
    复制代码

    image.png

    new 实例.constructor()

    你可能注意到了这样一行代码,它是怎样处理数组的呢?

    const cloneTarget = new target.constructor() // 创建一个新的克隆对象或克隆数组
    复制代码

    实例的 constructor 其实就是构造函数,

    class Person {}
    
    const p1 = new Person()
    
    console.log(p1.constructor === Person) // true
    console.log([].constructor === Array)  // true
    console.log({}.constructor === Object) // true
    复制代码
    console.log(new {}.constructor())  // {}
    等价于
    console.log(new Object()) // {}
    复制代码
    console.log(new [].constructor())  // {}
    等价于
    console.log(new Array()) // []
    复制代码

    运用在我们的深拷贝函数里,就不用在拷贝时去判断数组类型了,原对象是对象,就创建一个新的克隆对象,原对象是数组,就创建一个新的克隆数组。

    处理 Symbol

    上面的方法无法处理 Symbol 作为键,测试一下。

    const obj = {}
    const name = Symbol('name')
    obj[name] = 'lin' // Symbol 作为键
    
    const newObj = deepClone(obj)
    
    console.log(newObj) // {}
    复制代码

    可以把 for in 换成 Reflect.ownKeys 来解决

    Reflect.ownKeys 方法返回一个由目标对象自身的属性键组成的数组。它的返回值等同于Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))。

    继续改造克隆函数,

    function deepClone (target) {
      if (target === null) return target 
      if (target instanceof Date) return new Date(target)
      if (target instanceof RegExp) return new RegExp(target) 
    
      if (typeof target !== 'object') return target 
    
      
      const cloneTarget = new target.constructor() 
      
      // 换成 Reflect.ownKeys
      Reflect.ownKeys(target).forEach(key => { 
        cloneTarget[key] = deepClone(target[key]) // 递归拷贝每一层
      })
      return cloneTarget
    }
    复制代码

    测试一下,

    const obj = {}
    const name = Symbol('name')
    obj[name] = 'lin'
    
    const newObj = deepClone(obj)
    
    console.log(newObj)
    复制代码

    image.png

    处理循环引用

    上面的方法无法处理循环引用,测试一下。

    const obj = {
      a: 1
    }
    obj.obj = obj
    
    const newObj = deepClone(obj)
    复制代码

    image.png

    原因是对象存在循环引用的情况,递归进入死循环导致栈内存溢出了。

    解决循环引用问题,可以额外开辟一个存储空间来存储当前对象和拷贝对象的对应关系。

    当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,就不会一直递归导致栈内存溢出了。

    function deepClone (target, hash = {}) { // 额外开辟一个存储空间来存储当前对象和拷贝对象的对应关系
      if (target === null) return target
      if (target instanceof Date) return new Date(target)
      if (target instanceof RegExp) return new RegExp(target)
    
      if (typeof target !== 'object') return target
    
      if (hash[target]) return hash[target] // 当需要拷贝当前对象时,先去存储空间中找,如果有的话直接返回
      const cloneTarget = new target.constructor()
      hash[target] = cloneTarget // 如果存储空间中没有就存进存储空间 hash 里
    
      Reflect.ownKeys(target).forEach(key => {
        cloneTarget[key] = deepClone(target[key], hash) // 递归拷贝每一层
      })
      return cloneTarget
    }
    复制代码

    测试一下,

    const obj = {
      a: 1
    }
    obj.obj = obj
    
    const newObj = deepClone(obj)
    
    console.log('原来的对象', obj)
    console.log('新的对象', newObj)
    复制代码

    image.png

    上面的方法我们使用的是对象来创建的存储空间,这个存储空间还可以用 MapWeakMap,这里优化一下,使用 WeakMap,配合垃圾回收机制,防止内存泄漏。

    WeakMap

    WeakMap结构与Map结构类似,用于生成键值对的集合,除了以下两点和 Map 不同,其他都和 Map 相同

    • WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。
    const map = new WeakMap()
    map.set(1, 2)        // TypeError: 1 is not an object!
    map.set(Symbol(), 2) // TypeError: Invalid value used as weak map key
    map.set(null, 2)     // TypeError: Invalid value used as weak map key
    复制代码
    • WeakMap的键名所指向的对象,不计入垃圾回收机制。

    WeakMap的设计目的在于,有时我们想在某个对象上面存放一些数据,但是这会形成对于这个对象的引用。请看下面的例子。

    const e1 = document.getElementById('foo')
    const e2 = document.getElementById('bar')
    const arr = [
      [e1, 'foo 元素'],
      [e2, 'bar 元素']
    ]
    复制代码

    上面代码中,e1e2是两个对象,我们通过arr数组对这两个对象添加一些文字说明。这就形成了arre1e2的引用。

    一旦不再需要这两个对象,我们就必须手动删除这个引用,否则垃圾回收机制就不会释放e1e2占用的内存。

    // 不需要 e1 和 e2 的时候
    // 必须手动删除引用
    arr[0] = null
    arr[1] = null
    复制代码

    上面这样的写法显然很不方便。一旦忘了写,就会造成内存泄露

    WeakMap 就是为了解决这个问题而诞生的,它的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。

    基本上,如果你要往对象上添加数据,又不想干扰垃圾回收机制,就可以使用 WeakMap。一个典型应用场景是,在网页的 DOM 元素上添加数据,就可以使用WeakMap结构。当该 DOM 元素被清除,其所对应的WeakMap记录就会自动被移除。

    const wm = new WeakMap()
    const element = document.getElementById('example')
    wm.set(element, 'some information')
    wm.get(element) // "some information"
    复制代码

    上面代码中,先新建一个 WeakMap 实例。然后,将一个 DOM 节点作为键名存入该实例,并将一些附加信息作为键值,一起存放在 WeakMap 里面。这时,WeakMap 里面对element的引用就是弱引用,不会被计入垃圾回收机制。

    也就是说,上面的 DOM 节点对象的引用计数是1,而不是2。这时,一旦消除对该节点的引用,它占用的内存就会被垃圾回收机制释放WeakMap 保存的这个键值对,也会自动消失。

    总之,WeakMap的专用场合就是,它的键所对应的对象,可能会在将来消失。WeakMap结构有助于防止内存泄漏。

    引入 WeakMap

    了解了 WeakMap 的作用,我们来继续优化深拷贝函数,

    存储空间把对象改成 WeakMap,WeakMap 主要是为了处理经常被删除的 DOM 元素,在深拷贝函数里也加入对 DOM 元素的处理。

    如果拷贝对象是 DOM 元素就直接返回,拷贝 DOM 元素没有意义,都是指向页面中同一个。

    function deepClone (target, hash = new WeakMap()) { // 额外开辟一个存储空间WeakMap来存储当前对象
      if (target === null) return target
      if (target instanceof Date) return new Date(target)
      if (target instanceof RegExp) return new RegExp(target)
      if (target instanceof HTMLElement) return target // 处理 DOM元素
    
      if (typeof target !== 'object') return target
    
      if (hash.get(target)) return hash.get(target) // 当需要拷贝当前对象时,先去存储空间中找,如果有的话直接返回
      const cloneTarget = new target.constructor()
      hash.set(target, cloneTarget) // 如果存储空间中没有就存进 hash 里
    
      Reflect.ownKeys(target).forEach(key => {
        cloneTarget[key] = deepClone(target[key], hash) // 递归拷贝每一层
      })
      return cloneTarget
    }
    复制代码

    测试一下,

    const obj = {
      domArr: [document.getElementById('foo')]
    }
    
    const newObj = deepClone(obj)
    
    console.log(newObj)
    复制代码

    image.png

    至此,在面试场景中手写一个深拷贝,差不多到天花板了,毕竟面试的环境10分钟写一段代码,能把上面的功能写出来,已经很厉害了。

    至于其他边界情况,就靠嘴皮子去说吧。

    更多边界情况

    其实上面的深拷贝方法还有很多缺陷,有很多类型对象都没有实现拷贝,毕竟 JS 的标准内置对象实在太多了,要考虑所有的边界情况,就会让深拷贝函数变得特别复杂。

    JavaScript 标准内置对象

    我们花了 14 行实现了一个还算不错的深拷贝,但lodash 里的拷贝函数光是定义数据类型就超过 14 行了。

    /** `Object#toString` result references. */
    const argsTag = '[object Arguments]'
    const arrayTag = '[object Array]'
    const boolTag = '[object Boolean]'
    const dateTag = '[object Date]'
    const errorTag = '[object Error]'
    const mapTag = '[object Map]'
    const numberTag = '[object Number]'
    const objectTag = '[object Object]'
    const regexpTag = '[object RegExp]'
    const setTag = '[object Set]'
    const stringTag = '[object String]'
    const symbolTag = '[object Symbol]'
    const weakMapTag = '[object WeakMap]'
    
    const arrayBufferTag = '[object ArrayBuffer]'
    const dataViewTag = '[object DataView]'
    const float32Tag = '[object Float32Array]'
    const float64Tag = '[object Float64Array]'
    const int8Tag = '[object Int8Array]'
    const int16Tag = '[object Int16Array]'
    const int32Tag = '[object Int32Array]'
    const uint8Tag = '[object Uint8Array]'
    const uint8ClampedTag = '[object Uint8ClampedArray]'
    const uint16Tag = '[object Uint16Array]'
    const uint32Tag = '[object Uint32Array]'
    复制代码

    工具函数方法,就封装了二十多个,一个深拷贝的函数代码加起来可能有接近千行了。

    import Stack from './Stack.js'
    import arrayEach from './arrayEach.js'
    import assignValue from './assignValue.js'
    import cloneBuffer from './cloneBuffer.js'
    import copyArray from './copyArray.js'
    import copyObject from './copyObject.js'
    import cloneArrayBuffer from './cloneArrayBuffer.js'
    import cloneDataView from './cloneDataView.js'
    import cloneRegExp from './cloneRegExp.js'
    import cloneSymbol from './cloneSymbol.js'
    import cloneTypedArray from './cloneTypedArray.js'
    import copySymbols from './copySymbols.js'
    import copySymbolsIn from './copySymbolsIn.js'
    import getAllKeys from './getAllKeys.js'
    import getAllKeysIn from './getAllKeysIn.js'
    import getTag from './getTag.js'
    import initCloneObject from './initCloneObject.js'
    import isBuffer from '../isBuffer.js'
    import isObject from '../isObject.js'
    import isTypedArray from '../isTypedArray.js'
    import keys from '../keys.js'
    import keysIn from '../keysIn.js'
    复制代码

    甚至为了提高性能,lodash内部不用 for in,也不用 Reflect.ownKeys来遍历,还专门重写了遍历的方法。

    // arrayEach.js
    function arrayEach(array, iteratee) {
      let index = -1
      const length = array.length
    
      while (++index < length) {
        if (iteratee(array[index], index, array) === false) {
          break
        }
      }
      return array
    }
    复制代码

    阿林也只是看得懂,但讲不好,就贴一些大佬们的文章吧。

    Lodash是如何实现深拷贝的

    如何写出一个惊艳面试官的深拷贝?

    日常开发中,如果要使用深拷贝,为了兼容各种边界情况,一般是使用三方库,推荐两个:

    未来的深拷贝

    其实,浏览器自己实现了深拷贝函数,想不到吧。

    这个 Web API 名称叫 structuredClone(),详情可访问 MDN 和最新的 HTML5 规范

    我们来尝尝鲜,试用一下:

    const obj = {
      person: {
        name: 'lin'
      }
    }
    
    const newObj = structuredClone(obj) // 
    obj.person.name = 'xxx' // 改变原来的对象
    
    console.log('原来的对象', obj)
    console.log('新的对象', newObj)
    
    console.log('更深层的对象指向同一地址', obj.person == newObj.person)
    复制代码

    image.png

    深拷贝生效了,那是不是说以后再也不用 lodash 的 cloneDeep 了呢?

    很显然,不能,毕竟这是一个新的 API,从兼容性来考虑,很多浏览器应该都不会支持。

    caniuse 一查,果然如此。

    image.png

    另外, MDN 也介绍了实现这个 API 用到的算法,阿林粗略地看了一下,似乎实现得没有 lodash 全面,链接贴在下面,感兴趣的可以去看看。

    Structured_clone_algorithm

    可能再过一段时间大家就都使用这个深拷贝了吧,谁知道呢?就跟几年前还在兼容 ie 浏览器,现在没有特殊需求,傻子才去做兼容

    总结

    关于浅拷贝和深拷贝的使用选择,保险的做法是所有的拷贝都用深拷贝,而且一般是直接引三方库,毕竟自己写深拷贝,各种边界情况有时候考虑不到。

    像手写深拷贝这种卷王行为也只会出现在面试场景中了,面试场景中能把本文的深拷贝手写出来,并且能说出 lodash 源码的实现思路,也差不多了。

    其实,如果只是拷贝一层对象,只要能解决引用类型赋值后相互影响的问题,用浅拷贝又怎么了?

    另外,如果 JSON.parse(JSON.stringify(object))能实现你的功能,你却非要去引入 lodashcloneDeep 方法,那不就徒增了项目的打包体积了吗?自己平时写着玩的项目,用 JSON.parse(JSON.stringify(object)) 又怎么了?

    当然,如果团队有规范,为了代码风格统一或者说为了避免潜在的风险,统一全部用三方库的方法,增加一些打包的体积也没关系,毕竟企业级的项目还是要严谨一点。

    黑猫白猫,能抓到耗子就是好猫,各取所长就行。

    如果我的文章对你有帮助,你的就是对我的最大支持^_^

    我是阿林,输出洞见技术,再会!

    来源:https://juejin.cn/post/7072528644739956773
  • 相关阅读:
    我所理解的执行力
    iOS移动开发周报-第20期
    iOS移动开发周报-第19期
    iOS开发如何提高
    iOS移动开发周报-第18期
    iOS移动开发周报-第17期
    一起入门python3之元组和数列
    提权笔记本
    sqlmap笔记本
    SQL注入自学[第一学:一个简单的注入环境的编写]
  • 原文地址:https://www.cnblogs.com/konglxblog/p/16756212.html
Copyright © 2020-2023  润新知