javascript的深浅拷贝

注意:拷贝只针对 Object 和 Array 等引用数据类型,而 赋值 并不是一种拷贝操作。

赋值

把一个对象 赋值 给一个新的变量时,只是将栈空间中存储的对象地址复制了一份给新的变量,新的变量和对象依旧指向同一个堆中的存储空间。


把数组的内存地址赋值给newArr,这里不叫拷贝,修改时互相影响。
let arr = [1,2,3]
let newArr = arr  



 浅拷贝

把对象或数组,进行 浅拷贝 操作时,会在堆空间中生成一个新的对象,这个对象会精准复制原对象的所有属性值,并将新的变量指向这个堆中新产生的对象。

使用扩展运算符实现浅拷贝,还可以通过Object.assign(), Array.prototype.slice(), Array.prototype.concat()等方法实现对象和数组的浅拷贝。
let obj = {
    name: 'Jerry'
}
let arr = [1,2,3]

Object中的assign方法 将对象中所有可枚举的自有属性复制到目标对象,并返回修改后的目标对象
let obj_1 = Object.assign({},obj)

展开运算符实现浅拷贝
let arr_1 = [...arr]
let obj_1 = {...obj}

Array.prototype.concat() 用于合并两个或多个数组,并返回新的数组
let arr_2 = arr.concat([])

Array.prototype.slice() 回一个新的数组对象,这一对象是一个由 start 和 end 决定原数组的浅拷贝
let arr_3 = arr.slice()

浅拷贝缺点

针对基础类型 对象 或者 数组 浅拷贝复制出的新数据是完全独立于旧数据。

针对复杂类型 对象 的属性和 数组 的元素也是引用数据类型时,浅拷贝 仍然只会复制该引用数据类型堆中的地址。

let arr = [1,2,[3]]
let obj = {
    name: 'Jerry'
    info: {
        age: 18,
        height: 180
    }
}
展开运算符实现浅拷贝
let arr_1 = [...arr]
let obj_1 = {...obj}
修改新对象中的属性值
arr_1[2] = [3,4]
obj_1.info = 'null'
打印原对象数据   说明:复杂类型 新对象和原对象共享堆中的存储地址
console.log(arr[2])  [3,4]
console.log(obj.info) 'null'

深拷贝

深拷贝 在构造新的数据时,遇到引用所指向的引用数据类型会继续执行拷贝,直到所有的引用数据类型都被处理。

JSON.stringify()将一个 JavaScript 对象或值转换为 JSON 字符串, 而 JSON.parse() 则正好相反,它将JSON字符串解析为值或对象,我们以JSON字符串作为桥梁,来实现 深拷贝。

let obj = {
    name: 'Jerry',
    info: {
        age: 18,
        height: 180
    }
}
let obj_1 = JSON.parse(JSON.stringify(obj))

JSON.parse(JSON.stringify(obj))缺点

如果obj里面存在时间对象,JSON.parse(JSON.stringify(obj))之后,时间对象变成 字符串

如果obj里有RegExpError对象,则序列化的结果将只得到 空对象

如果obj里有函数undefined,则序列化的结果会把函数, undefined丢失

如果obj里有NaNInfinity-Infinity,则序列化的结果会变成 null

如果obj中的对象是由构造函数生成的, 则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的 constructor

如果对象中存在循环引用的情况也无法正确实现 深拷贝

手写深拷贝

function deepClone(obj, cache = new WeakMap()) { // weakmap做缓存处理循环引用,且不影响垃圾回收
  if (typeof obj === 'symbol') return Symbol(obj.description)  // 处理symbol
  if (obj === null || typeof obj !== 'object') return obj // 对于空对象,函数或者基本数据类型直接返回
  if (cache.get(obj)) return cache.get(obj) // 如果缓存中有该对象,说明有循环引用,直接返回该对象,不重复拷贝
  // 当为以下类型时直接new一个新对象
  const type = [Date, RegExp, Set, Map, WeakMap, WeakSet]
  if (type.includes(obj.constructor)) return new obj.constructor(obj)
  let cloneObj = new obj.constructor() // 让拷贝对象拥有原对象的构造函数类型
  cache.set(obj,cloneObj) // 拷贝对象写入缓存
  for(let key in obj) {
    if(obj.hasOwnProperty(key)){ // 如果该属性在对象上而不是其原型上
      cloneObj[key] = deepClone(obj[key],cache) // 递归调用
    }
  }
  return cloneObj
}

你可能感兴趣的:(javascript,开发语言,ecmascript)