JavaScript 之深浅拷贝

45 阅读2分钟

浅拷贝

拷贝基本数据类型时,不受任何影响,当拷贝引用类型时,只复制某个对象的引用,源对象也会被修改。

Object.assign

let obj = {
  name:'jack',
  age:20,
  child:{
    name:'mike',
    age:1,
  }
}

let target = {}

Object.assign(target,obj)

target.name = 'alice'
console.log(target.name); // alice
console.log(obj.name); // jack

target.child.name = 'haha'
console.log(target.child.name); // haha
console.log(obj.child.name); // haha

扩展运算符

let obj = {
  name:'jack',
  age:20,
  child:{
    name:'mike',
    age:1,
  }
}

let target = {...obj}
target.name = 'alice'
console.log(target.name); // alice
console.log(obj.name); // jack

target.child.name = 'haha'
console.log(target.child.name); // haha
console.log(obj.child.name); // haha

Array.prototype.slice()

slice()方法可以用来截取数组

let arr = [1,2,{name:'jack'}]

let newArr = arr.slice()


newArr[0] = 99
console.log(newArr[0]);  // 99
console.log(arr[0]);  // 1

newArr[2].name = 'mike'
console.log(newArr[2].name);  // mike
console.log(arr[2].name);  // mike

Array.prototype.concat()

concat() 方法用于合并两个或多个数组

let arr = [1,2,{name:'jack'}]
let newArr = arr.concat([3])

newArr[0] = 99
console.log(newArr[0]);  // 99
console.log(arr[0]);  // 1

newArr[2].name = 'mike'
console.log(newArr[2].name);  // mike
console.log(arr[2].name);  // mike

深拷贝

深拷贝会复制一个一模一样的对象,新对象和原对象不共享内存,修改新对象不会改变原对象。

JSON.stringify 和 JSON.parse

function Person(){}

let obj = {
  name:'jack',
  age:20,
  date:new Date(),
  reg:new RegExp('\\w+'),
  sayName:function(){
    console.log(this.name);
  },
  number:NaN,
  sex:undefined,
  child:{
    name:'mike',
    age:1,
  },
  p: new Person()
}

let target = JSON.parse(JSON.stringify(obj))
console.log(target);

target.name = 'alice'
console.log(target.name); // alice
console.log(obj.name); // jack

target.child.name = 'haha'
console.log(target.child.name); // haha
console.log(obj.child.name); // mike

image.png

缺点:

  • 拷贝的对象中如果有函数,undefined,symbol,当使用过JSON.stringify()进行处理之后,都会消失。
  • 无法拷贝不可枚举的属性;
  • 无法拷贝对象的原型链;
  • 拷贝 Date 引用类型会变成字符串;
  • 拷贝 RegExp 引用类型会变成空对象;
  • 对象中含有 NaN、Infinity 以及 -Infinity,JSON 序列化的结果会变成 null

lodash

import cloneDeep from 'lodash/cloneDeep'
let obj = {
  name: 'jack',
  age: 20,
  child: {
    name: 'mike',
    age: 1,
  },
}
var newObj = cloneDeep(obj)
console.log(obj.child === newObj.child) // false

手写实现

function deepClone(obj, hash = new WeakMap()) {
  // 如果不是对象或者null的直接返回
  if (obj === null || typeof obj != 'object') {
    return obj
  }
  // 日期对象直接返回一个新的日期对象
  if (obj instanceof Date) {
    return new Date(obj)
  }
  //正则对象直接返回一个新的正则对象
  if (obj instanceof RegExp) {
    return new RegExp(obj)
  }
  //如果循环引用,就用 weakMap 来解决
  if (hash.has(obj)) {
    return hash.get(obj)
  }
  // 获取对象所有属性的描述符
  let allDesc = Object.getOwnPropertyDescriptors(obj)
  // 创建新的对象,设置对象原型,对象属性和属性描述符
  let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)

  hash.set(obj, cloneObj)

  // 遍历对象属性进行属性值的复制
  for (let key of Reflect.ownKeys(obj)) {
    if (typeof obj[key] === 'object' && obj[key] !== null) {
      cloneObj[key] = deepClone(obj[key], hash)
    } else {
      cloneObj[key] = obj[key]
    }
  }
  return cloneObj
}
function Person() {}

let obj = {
  name: 'jack',
  age: 20,
  boolean: true,
  und: undefined,
  nul: null,
  arr: [1, 2],
  [Symbol('1')]: 1,
  date: new Date(),
  reg: new RegExp('\\w+'),
  sayName: function () {
    console.log(this.name)
  },
  number: NaN,
  child: {
    name: 'mike',
    age: 1,
  },
  p: new Person(),
}

Object.defineProperty(obj, 'innumerable', {
  enumerable: false,
  value: '不可枚举属性',
})

obj = Object.create(obj, Object.getOwnPropertyDescriptors(obj))
obj.loop = obj // 将loop设置成循环引用的属性
let cloneObj = deepClone(obj)

console.log(obj)
console.log(cloneObj)