重点:深浅拷贝

181 阅读3分钟

一、不同类型数据的比较、赋值和拷贝

1. 比较

  • 基本类型的比较是值大小的比较
  • 引用类型的比较是比较保存在栈内存的指针是否指向同一个对象

2. 赋值

  • 传值:基本类型的赋值是在内存中新开辟一段栈内存,然后再把值赋值到新的栈内存中,新旧变量之间没有影响。
  • 传址:引用类型的赋值只是改变了指针的指向,使两个变量指向同一个对象,新旧变量之间互相有影响。

3. 深拷贝、浅拷贝和赋值区别

浅拷贝:重新创建了一个新对象,如果原对象的属性是基本类型、那就会拷贝基本类型的值,如果属性是引用类型、拷贝的就是内存地址,互相有影响。

深拷贝:将对象和对象内部所有的属性都拷贝了,不论基本类型还是引用类型。

image.png

二、源码实现

1. 浅拷贝

法1:手写浅拷贝

for...in用来遍历一个对象的属性,但它不仅遍历对象自身的属性,还会遍历继承的属性。因此需要结合hasOwnProperty()判断一下某属性是否为对象自身的属性。

const shallowCopy = function(obj) {
  if (obj === null || typeof obj != 'object')  return obj;  // 基本类型则直接返回
  
  const res = Array.isArray(obj) ? [] : {};
  for (let key in obj) {   // 属性遍历 用 in
    if (obj.hasOwnProperty(key)) { // 如果是对象自身的属性
      res[key] = obj[key];
    }
  }
  return res;
}

法2:Object.assign():引用类型

ES6 新增了Object.assign()方法,通常用于对象的合并。它会将源对象 obj 的所有可枚举属性复制到目标对象 target 中,浅拷贝时只有引用类型是有效的

  • 第一个参数:目标对象
  • 其他参数:源对象
// Object.assign(target, source1, source2);

const target = Object.assign({}, obj);

法3:展开运算符: 引用类型

const target = {...obj};  // 注意,是花括号

法4:Array.prototype.concat():只适用数组

const obj = {a: 1};
const arr = [obj];

const newArr = arr.concat();

法5:Array.prototype.slice():只适用数组

2. 深拷贝

法1:JSON.parse(JSON.stringify())

JSON.stringify() 可以将 js对象 转为 json字符串。

JSON.parse() 又将 json字符串 转成 js对象。

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

不足

  • obj 中不能有函数、undefined,否则会丢失。
  • 不能存放NaN、Infinity、-Infinity,否则会变成null
  • 不能存放正则、时间对象等。

法2:递归法 + 循环引用

let obj1 = {};
let obj2 = { b: obj1 };
obj1.a = obj2;

如果存在循环引用的情况,也就是对象直接或间接地引用了自身,那么递归后会导致栈内存溢出。怎么解决呢?

可以额外开辟一个存储空间Map,存放已经被拷贝的对象。当需要拷贝当前对象时,先向Map查询该对象,如果存在直接取出返回即可!(实际上是改变了对象引用)

注意:不能使用WeakMap,因为WeakMap只接受对象作为键名,不接受其他类型的值作为键名。

const deepCopy = function(obj, map = new Map()) {
  if (obj === null || typeof obj != 'object')  return obj;  // 基本类型则直接返回
  if (map.has(obj)) return map.get(obj);  // 如果 map有存储,那么直接返回
  
  const res = Array.isArray(obj) ? [] : {};
  map.set(obj, res);
  for (let key in obj) {   // 属性遍历 用 in
    if (obj.hasOwnProperty(key)) {
      res[key] = deepCopy(obj[key], map);
    }
  }
  return res;
}