浅拷贝实现方法精讲:从原生 API 到手写(10 分钟讲透)

29 阅读3分钟

首先明确:浅拷贝是针对「引用类型(对象 / 数组)」的操作 —— 创建一个新对象 / 数组 拷贝原数据的「表层属性」

很多人会把「直接赋值」当成拷贝,其实完全不一样:

const obj1 = { name: '张三', hobby: ['打球', '看书'] };

// 直接赋值:没有创建新对象,只是多了一个引用
const obj2 = obj1;
obj2.name = '李四';
obj2.hobby.push('听歌');
console.log(obj1); // { name: "李四", hobby: ["打球", "看书", "听歌"] }(原对象被改)

// 浅拷贝:创建了新对象,表层属性独立
const obj3 = 浅拷贝函数(obj1);
obj3.name = '王五';
obj3.hobby.push('跑步');
console.log(obj1); // { name: "李四", hobby: ["打球", "看书", "听歌", "跑步"] }(子数组被改,表层name不变)

浅拷贝后,表层的 name 改了不影响原对象,但子数组 hobby 改了会影响 —— 这就是浅拷贝的核心特征。

看下实现浅拷贝的方法有哪些

方法1:Object.assign ()(ES6 对象浅拷贝)

这是 ES6 专门的对象拷贝 API,功能强,支持多源对象合并,核心是「浅拷贝 + 属性覆盖」。

let target = { a: 1 }; // 目标对象(接收拷贝结果)
let object2 = { b: 2 };
let object3 = { c: 3 };

// 语法:Object.assign(目标对象, 源对象1, 源对象2, ...)
Object.assign(target, object2, object3);  
console.log(target); // {a: 1, b: 2, c: 3}(目标对象被修改,合并了所有源对象属性)

浅拷贝本质:子对象只拷地址

let target = { a: 1 };
let source = { b: { c: 2 } }; // 源对象有子对象
Object.assign(target, source);

target.b.c = 3; // 修改子对象属性
console.log(source.b.c); // 3(原源对象的子对象也被改了,浅拷贝特征)

方法 2:扩展运算符(...)(最简洁的浅拷贝)

// 语法:let Obj = { ...obj };
let obj1 = {a:1,b:{c:1}}
let obj2 = {...obj1};
obj1.a = 2;
console.log(obj1); //{a:2,b:{c:1}}
console.log(obj2); //{a:1,b:{c:1}}
obj1.b.c = 2;
console.log(obj1); //{a:2,b:{c:2}}
console.log(obj2); //{a:1,b:{c:2}}

方法 3:数组专用浅拷贝(slice/concat)

//语法:array.slice(start, end),两个参数都省略时,返回原数组的浅拷贝
let arr = [1, 2, 3, 4];
let cloneArr = arr.slice(); // 无参数=浅拷贝

console.log(cloneArr); // [1,2,3,4](新数组)
console.log(cloneArr === arr); // false(不是同一个数组)

// 浅拷贝特征:子数组共享
let arr3 = [1, [2,3]];
let cloneArr3 = arr3.slice();
arr3[1].push(4);
console.log(cloneArr3); // [1, [2,3,4]](子数组被改)
//语法:array.concat(数组1, 数组2, ...),无参数时,返回原数组的浅拷贝
let arr = [1, 2, 3, 4];
let cloneArr = arr.concat(); // 无参数=浅拷贝

console.log(cloneArr); // [1,2,3,4]
console.log(cloneArr === arr); // false

// 浅拷贝特征:子数组共享
let arr4 = [1, [2,3]];
let cloneArr4 = arr4.concat();
arr4[1].push(4);
console.log(cloneArr4); // [1, [2,3,4]]

核心逻辑是「判断类型→创建新容器→遍历拷贝表层属性」

方法4:手写浅拷贝函数

function shallowCopy(object) {
  // 1. 边界判断:非对象/数组(null、基本类型)直接返回原数据
  if (!object || typeof object !== "object") return;

  // 2. 创建新容器:根据原数据类型,新建数组或对象
  let newObject = Array.isArray(object) ? [] : {};

  // 3. 遍历原数据,只拷贝自身属性(排除原型链属性)
  for (let key in object) {
    // hasOwnProperty:确保只拷贝对象自己的属性,不拷贝原型上的
    //因为 for...in 会遍历对象原型链上的可枚举属性,
    //加 hasOwnProperty 能确保只拷贝对象自身的属性,
    //避免拷贝原型上的冗余属性(比如 Person.prototype.age)
    if (object.hasOwnProperty(key)) {
      newObject[key] = object[key]; // 表层拷贝:基本类型拷值,引用类型拷地址
    }
  }

  return newObject;
}
// 测试对象
let obj = { a: 1, b: { c: 2 } };
let cloneObj = shallowCopy(obj);
obj.b.c = 3;
console.log(cloneObj.b.c); // 3(浅拷贝特征,正确)

// 测试数组
let arr = [1, [2,3]];
let cloneArr = shallowCopy(arr);
arr[1].push(4);
console.log(cloneArr); // [1, [2,3,4]](正确)

// 测试基本类型
console.log(shallowCopy(123)); // undefined(边界判断生效)
console.log(shallowCopy(null)); // undefined(边界判断生效)