JavaScript数组浅拷贝与深拷贝实现详解

作者:木棉时间:2026-10-31 09:59:56浏览:230

本文详细解析JavaScript中数组的浅拷贝与深拷贝实现。浅拷贝可通过concat、slice或Object.assign完成,但仅复制引用;深拷贝涉及JSON.stringify、递归及处理循环引用的Map方案。文章提供通用代码示例,帮助开发者根据需求选择合适策略,避免修改嵌套对象时产生副作用。

浅拷贝的核心在于创建新对象以接收原对象的值。若属性为基本数据类型,则复制其具体数值;若属性为引用数据类型,则仅复制内存地址。此时新旧对象指向同一地址,修改任一对象的引用属性,另一个对象也会随之改变。

数组的浅拷贝

利用concat和slice方法返回新数组的特性,即可实现数组的浅拷贝。

var arr = ['old', 1, true, null, undefined];
var new_arr = arr.concat(); // 或者var new_arr = arr.slice()也是一样的效果;
new_arr[0] = 'new';
console.log(arr); // ["old", 1, true, null, undefined]
console.log(new_arr); // ["new", 1, true, null, undefined]

此外,还可以使用Object.assign()方法来实现数组的浅拷贝。

let target = {};
let source = { a: { b: 2 } };
Object.assign(target, source);
console.log(target); // { a: { b: 10 } }; 
source.a.b = 10; 
console.log(source); // { a: { b: 10 } }; 
console.log(target); // { a: { b: 10 } };

然而,当数组中包含嵌套的对象或数组时,使用concat或slice进行拷贝,一旦修改嵌套内容,新旧数组会同时发生变化。例如:

var arr = [{old: 'old'}, ['old']];
var new_arr = arr.concat();
arr[0].old = 'new';
new_arr[1][0] = 'new';
console.log(arr); // [{old: 'new'}, ['new']]
console.log(new_arr); // [{old: 'new'}, ['new']]

下面提供一个通用的浅拷贝方法,其实现逻辑为遍历原对象,将属性名及属性值逐一放入新对象中。

var shallowCopy = function (obj) {
  // 只拷贝对象
  if (typeof obj !== 'object') return;
  // 根据obj的类型判断是新建一个数组还是一个对象
  var newObj = Array.isArray(obj) ? [] : {};
  // 遍历obj,并且判断是obj的属性才拷贝
  for (var key in obj) {
    if (obj.hasOwnProperty(key)) {
      newObj[key] = obj[key];
    }
  }
  return newObj;
}

若数组元素为基本类型,拷贝后互不影响;若为对象或数组,则仅拷贝引用。因此,无论修改新旧数组中的哪一方,另一方都会随之变化,这即为浅拷贝的特征。深拷贝则要求完全独立,即使嵌套对象,修改一方也不会影响另一方。

数组的深拷贝

方法一:利用JSON.stringify()。该方法不仅能拷贝数组,也能拷贝对象,但无法处理函数,且不能解决循环引用问题。

var arr = ['old', 1, true, ['old1', 'old2'], {old: 1}]
var new_arr = JSON.parse(JSON.stringify(arr))
console.log(new_arr);

方法二:采用递归方式。以下是通用的深拷贝实现思路:在拷贝过程中判断属性值的类型,若为对象,则继续递归调用深拷贝函数。

var deepCopy = function(target) {
  // 只拷贝对象
  if (typeof target !== 'object') return target;
  // 根据obj的类型判断是新建一个数组还是一个对象
  var result = Array.isArray(obj) ? [] : {};
  for (var key in target) {
    // 遍历obj,并且判断是obj的属性才拷贝(不能拷贝原型对象的属性)
    if (target.hasOwnProperty(key)) {
      // 判断属性值的类型,如果是对象递归调用深拷贝
      result[key] = typeof target[key] === 'object' ? deepCopy(target[key]) : target[key];
    }
  }
  return result;
}

上述方法一和方法二均无法解决循环引用问题。方法三结合Map对象(将方法二的递归进行改造,以解决循环引用):

var deepCopy = function (target, map = new Map()) {
  // 只拷贝对象
  if (typeof target !== "object") return target;
  // 克隆数据之前,先判断之前是否有克隆过(解决循环引用的问题)
  let cache = map.get(target);
  if (cache) {
    return cache;
  }
  // 根据obj的类型判断是新建一个数组还是一个对象
  var result = Array.isArray(target) ? [] : {};
  // 将新的结果存到容器中
  map.set(target, result);
  for (var key in target) {
    // 遍历obj,并且判断是obj的属性才拷贝(不能拷贝原型对象的属性)
    if (target.hasOwnProperty(key)) {
      // 判断属性值的类型,如果是对象递归调用深拷贝
      result[key] =
        typeof target[key] === "object"
          ? deepCopy(target[key], map)
          : target[key];
    }
  }
  return result;
};
// 测试代码
let obj = { a: 1, b: [6, 8], c: { e: 3 } };
obj.b.push(obj.c);
obj.c.h = obj.b;
let obj1 = deepCopy(obj);
console.log(obj);
console.log(obj1);

关于JS实现数组浅拷贝和深拷贝的内容介绍至此,更多相关js浅拷贝和深拷贝内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

作者最新文章