JavaScript数组的浅拷贝&深拷贝,以及JSON.parse(JSON.stringify())深拷贝的局限性

数组直接赋值、浅拷贝、深拷贝

在开发过程中,对于一个后端返回结果,我们可能需要存成两个变量,用于做一些修改和复原,其中可能遇到的问题是:

将数组list 直接赋值给list1和list2.修改了list2导致list1也变了。

造成错误的原因是:

数组是引用类型(对象),使用type of 判断类型返回的是object。因此直接将一个数组赋值给多个变量时,这些变量会共享同一个数组引用。修改其中一个变量会导致其他变量也受到影响,因为它们指向同一个数组。

示例代码


let list = [1, 2, 3];
let list1 = list;
let list2 = list;

list2.push(4);

console.log(list1); // 输出: [1, 2, 3, 4]
console.log(list2); // 输出: [1, 2, 3, 4]

在这个例子中,list1 和 list2 都指向同一个数组 list,所以修改 list2 会导致 list1 也发生变化。

解决方法:
如果你希望 list1 和 list2 是两个独立的数组,可以使用以下方法:

1. 使用 slice() 方法
slice() 方法会返回数组的一个浅拷贝。

let list = [1, 2, 3];
let list1 = list.slice();
let list2 = list.slice();

list2.push(4);

console.log(list1); // 输出: [1, 2, 3]
console.log(list2); // 输出: [1, 2, 3, 4]

2. 使用扩展运算符 (...)
扩展运算符可以快速创建一个数组的浅拷贝。

let list = [1, 2, 3];
let list1 = [...list];
let list2 = [...list];

list2.push(4);

console.log(list1); // 输出: [1, 2, 3]
console.log(list2); // 输出: [1, 2, 3, 4]

3. 使用 Array.from()
Array.from() 也可以创建一个数组的浅拷贝。

let list = [1, 2, 3];
let list1 = Array.from(list);
let list2 = Array.from(list);

list2.push(4);

console.log(list1); // 输出: [1, 2, 3]
console.log(list2); // 输出: [1, 2, 3, 4]

4. 使用 JSON.parse(JSON.stringify())(深拷贝)
如果数组包含嵌套对象或数组,可以使用 JSON.parse(JSON.stringify()) 进行深拷贝。

let list = [1, 2, { a: 3 }];
let list1 = JSON.parse(JSON.stringify(list));
let list2 = JSON.parse(JSON.stringify(list));

list2[2].a = 4;

console.log(list1); // 输出: [1, 2, { a: 3 }]
console.log(list2); // 输出: [1, 2, { a: 4 }]

注意事项

浅拷贝:slice()、扩展运算符 (…) 和 Array.from()
都是浅拷贝,只能复制一层。如果数组包含嵌套对象或数组,嵌套部分仍然是引用。

深拷贝:JSON.parse(JSON.stringify()) 可以实现深拷贝,但它无法处理函数、undefined 和循环引用的情况。

JSON.parse(JSON.stringify()) 深拷贝方法的局限性

无法正确处理以下情况:

1. 函数(Function)
JavaScript 中的函数无法被 JSON.stringify() 序列化。如果对象中包含函数,JSON.stringify() 会直接忽略它们。

示例

let obj = {
  name: "Alice",
  greet: function() {
    console.log("Hello!");
  }
};

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

console.log(copy); // 输出: { name: "Alice" }

原对象中的 greet 函数在拷贝后的对象中丢失了。

2. undefined
JSON.stringify() 会忽略值为 undefined 的属性。

示例

let obj = {
  name: "Alice",
  age: undefined
};

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

console.log(copy); // 输出: { name: "Alice" }

原对象中的 age 属性(值为 undefined)在拷贝后的对象中丢失了。

3. 循环引用
循环引用是指对象属性直接或间接地引用自身。JSON.stringify() 无法处理循环引用,会直接抛出错误。

示例

let obj = {
  name: "Alice"
};
obj.self = obj; // 循环引用

try {
  let copy = JSON.parse(JSON.stringify(obj));
} catch (error) {
  console.error(error); // 报错: TypeError: Converting circular structure to JSON
}

由于 obj 引用了自身,JSON.stringify() 无法将其转换为字符串,导致报错。

4. 其他特殊情况

NaN 和 Infinity:JSON.stringify() 会将 NaN 和 Infinity 转换为 null。

Date 对象:JSON.stringify() 会将 Date 对象转换为字符串,反序列化后不会恢复为 Date 对象。

RegExp 对象:JSON.stringify() 会将其转换为空对象 {}。

示例

let obj = {
  date: new Date(),
  regex: /abc/g,
  nan: NaN,
  infinity: Infinity
};

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

console.log(copy);
// 输出:
// {
//   date: "2023-10-10T12:00:00.000Z", // Date 对象被转换为字符串
//   regex: {},                        // RegExp 对象被转换为空对象
//   nan: null,                        // NaN 被转换为 null
//   infinity: null                    // Infinity 被转换为 null
// }

如何解决这些问题?
如果需要处理函数、undefined、循环引用等特殊情况,可以使用以下方法:

1. 使用深拷贝库
一些第三方库(如 Lodash 的 _.cloneDeep)可以正确处理这些特殊情况。

let _ = require('lodash');

let obj = {
  name: "Alice",
  greet: function() {
    console.log("Hello!");
  },
  self: null
};
obj.self = obj; // 循环引用

let copy = _.cloneDeep(obj);

console.log(copy); // 正确拷贝,包括函数和循环引用

2. 手动实现深拷贝
可以编写递归函数来实现深拷贝,处理函数、undefined 和循环引用等特殊情况。

function deepClone(obj, cache = new WeakMap()) {
  if (obj === null || typeof obj !== 'object') {
    return obj; // 基本类型直接返回
  }

  if (cache.has(obj)) {
    return cache.get(obj); // 解决循环引用
  }

  let result = Array.isArray(obj) ? [] : {};
  cache.set(obj, result);

  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      result[key] = deepClone(obj[key], cache);
    }
  }

  return result;
}

let obj = {
  name: "Alice",
  greet: function() {
    console.log("Hello!");
  },
  self: null
};
obj.self = obj; // 循环引用

let copy = deepClone(obj);
console.log(copy); // 正确拷贝,包括函数和循环引用

总结
JSON.parse(JSON.stringify()) 是一种简单但有限的深拷贝方法。

如果需要处理函数、undefined、循环引用等特殊情况,可以使用第三方库(如 Lodash)或手动实现深拷贝。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值