赋值,浅拷贝,深拷贝的区别,以及各自实现

312 阅读4分钟

赋值

  1. 当我们把一个对象赋值给一个新的对象时,赋的是该对象在栈中的地址,而不是堆中的数据 也就是赋值之后,两个对象指向同一个存储空间。无论哪个对象发生改变,其实都是改变的堆中的数据
let a = {
name: 'a',
like: ['吃饭']
}
let b = a

b.name = 'b'
b.like[0] = '睡觉'
console.log('a', a)
console.log('b', b)

image.png 当我们把一个变量赋值给另一个变量时,是在栈内存中新增一个变量,并且赋值。二者互不影响

 let c = 1
 let d = c
 d = 2
 console.log(c) //c1
 console.log(d) //2

浅拷贝

浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

利用{...}实现拷贝。注意这里是拷贝,先不区分是深还是浅

如果是引用类型,且只有一层对象,则这一层就是深拷贝。拷贝前后,对象的基本类型互不影响, 如果是引用类型,且有两层以上嵌套对象,则这嵌套的,就是浅拷贝。嵌套的对象,拷贝的嵌套对象的引用。拷贝前后,嵌套对象数据相互影响,一层对象,互不影响

let obj = {
    name: 1,
    address: {
        x: 1
    }
}

let d = { ...obj }
d.name = 4 // 一层 深拷贝,互不影响
d.address.x = 'x' // 两层,拷贝的事地址,相互影响
console.log('浅拷贝', obj)// 浅拷贝 { name: 1, address: { x: 'x' } }
console.log('浅拷贝', d)//浅拷贝 { name: 4, address: { x: 'x' } }
  • 修改一层对象的d.name。你会发现互不影响。因为深拷贝
  • 修改嵌套对象的d.address.x.你会发现相互影响,因为是浅拷贝 浅拷贝实现
function shallowCopy (obj) {
  let target = {}
  for (let k in obj) {
    if (obj.hasOwnProperty(i)) { // 不遍历其原型链上的属性
      target[i] = obj[i] 
    }
  }
  return target
}

for-in 循环有一个问题,不仅会遍历对象的实例属性,还会遍历从原型链继承来的属性,用hasOwnProperty 过滤出对象的实例属性

深拷贝

在堆内存,开辟一块新的区域存放对象,对对象中的子对象进行递归拷贝,拷贝前后,两个对象互不影响

function deepClone(obj, hash = new Map()) {
    if (obj == null) return obj // 如果是null和undefined 就不进行操作
    if (obj instanceof Date) return new Date(obj)
    if (obj instanceof RegExp) return RegExp(obj)
    // 如果是普通值,不需要拷贝
    if (typeof obj !== 'object') return obj
    // 是对象的话,就要进行深拷贝
    if (hash.get(obj)) return hash.get(obj)
    let cloneObj = new obj.constructor
    hash.set(obj, cloneObj)
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            cloneObj[key] = deepClone(obj[key], hash)
        }
    }
    return cloneObj
}
let obj = {
    name: 1,
    address: {
        x: 1
    }
}

let d = deepClone(obj)
d.address.x = 4
console.log(obj, d)//{ name: 1, address: { x: 1 } } { name: 1, address: { x: 4 } }
  • 如果出现循环引用,则需要使用WeakMap

快捷方式进行浅拷贝

  1. Object.assign({},) // 一层是深拷贝,两层以上是浅拷贝
  2. {...} // 一层是深拷贝,两层以上是浅拷贝
  3. loadsh

深拷贝

  1. JSON.parse(JSON.stringify())
  2. lodash

JSON.parse(JSON.stringify())

  1. 将js对象序列化为json字符串,然后再反序列化为js原对象
  2. 如果obj里有函数,undefined,则序列化的结果会把函数或 undefined丢失;
  3. 如果obj里有RegExp、Error对象 ,序列化之后会得到空对象
  4. 如果obj里有时间对象 ,序列化之后会得到字符串 5.只能序列化对象的可枚举的自由属性,,如果obj中的对象由构造函数生成,拷贝后,会丢失对象constroter

总结

  • 赋值的实现,只是一个 =,而 浅拷贝是 创建一个新的{} ,那么就可以得知,赋值时与原数据指向同一对象,而浅拷贝则指向了不同对象。
  • 拷贝是拷贝对象的(引用数据类型),所以你要是基本数据类型,用赋值就行了
  • 手写深拷贝有几个点,很不错,一个是对,null和undefined的判断,因为null == nudefined,所以这两个判断一个就可以了
  • 判断是数组还是对象,巧妙用到了new obj.constructor。他会返回你要拷贝的数据,并且可以得到传入的类型。这就比这行代码省事 Array.isArray(obj) ? [] : {}
  • 如果出现循环引用,用 new WeakMap 。