JavaScript 变量有两种不同数据类型:基本类型和引用类型。

基本数据类型是按值访问,可以直接操作保存在变量中的实际值。引用类型的值是保存在堆内存中的对象,JavaScript 不能直接操作对象的内存空间,实际操作的是对象的引用。在传递的时候,基本类型是传值,引用类型是传址。所以,浅拷贝与深拷贝只针对于引用类型这种对象。

浅拷贝与深拷贝

浅拷贝:浅拷贝只拷贝对象的引用地址,并没有创建新的对象,并没有开辟新的堆内存。拷贝出来的变量是同一个引用地址,指向同一个对象。

深拷贝:深拷贝是对引用的对象全部进行拷贝。在内存中创建了新的对象,拷贝出来的变量指向不同的引用地址,指向不同的对象。

浅拷贝的示例

  • 赋值操作
var arr = [1, 2, 3, 4];
var obj = { a: 'a', b: 'b' };
var arrCopy = arr;
var objCopy = obj;

arrCopy[0] = 0;
objCopy.a = 'aa';

log(arr, arrCopy); //[0, 2, 3, 4], [0, 2, 3, 4]
log(obj, objCopy); //{ a: "aa", b: "b" }, { a: "aa", b: "b" }

特别的浅拷贝

当数据只有一层的时候,该方法可以当作深拷贝使用。当数据超过一层时候,只能用作浅拷贝。

  • Array.slice() –常用作数组
var arr = [1, 2, 3, 4];
var arrCopy = arr.slice();
arrCopy.push(5);
log(arr, arrCopy);
//[1, 2, 3, 4], [1, 2, 3, 4, 5]
var arr = [1, 2, 3, 4, [10, 11]];
var arrCopy = arr.slice();
arrCopy[4].push(12);
log(arr, arrCopy);
//[1, 2, 3, 4, [10, 11, 12]], [1, 2, 3, 4, [10, 11, 12]]
  • Ojbect.assign() –常用作对象
var obj = { a: 'a', b: 'b' };
var objCopy = Object.assign({}, obj);
objCopy.a = 'aaa';
log(obj, objCopy);
//{ a: "a", b: "b" }, { a: "aaa", b: "b" }
var obj = { a: 'a', b: 'b', c: { d: 'd' } };
var objCopy = Object.assign({}, obj);
objCopy.c.d = 'ddd';
log(obj, objCopy);
//{ a: 'a', b: 'b', c: {d: "ddd"} }, { a: 'a', b: 'b', c: {d: "ddd"} }
  • 扩展运算符… –数组,对象都常用
var arr = [1, 2, 3, 4];
var obj = { a: 'a', b: 'b' };
var arrCopy = [...arr];
var objCopy = { ...obj };
arr.push(5);
obj.c = 'c';
log(arr, arrCopy); //[1, 2, 3, 4, 5] [1, 2, 3, 4]
log(obj, objCopy); //{ a: "a", b: "b" }, { a: "a", b: "b", c: "c" }

深拷贝

  • JSON.parse 和 JSON.stringify
var obj = { a: { b: 'c' } };
var objCopy = JSON.parse(JSON.stringify(obj));
objCopy.a.b = 'd';
log(obj, objCopy); //{ a: { b: "c"} }, { a: { b: "d"} }

局限性:无法转换function,function会直接消失掉。

var obj = {
  fun: function () {
    console.log('hi');
  },
};
var objCopy = JSON.parse(JSON.stringify(obj));
log(typeof obj.fun); //"function"
log(typeof objCopy.fun); //"undefined"
  • Jquery 的 extend

  • Lodash 的 cloneDeep

题外话

数组的部分方法原本是不改变原数组的,如 map, filter, find…等等。但是如果数组项是对象的话,且遍历时修改原数组项会修改原数组。

const arr = [1, 2, 3];
const arr2 = [{ value: 1 }, { value: 2 }];

const copy = arr.map((i) => {
  i += 1;
  return i;
});
const copy2 = arr2.map((i) => {
  i.value += 1;
  return i;
});

console.log(arr, copy);
// [1, 2, 3] [2, 3, 4]
console.log(arr2, copy2);
// [{ value: 2 }, { value: 3 }] [{ value: 2 }, { value: 3 }]