js对象浅拷贝和深拷贝

150 阅读4分钟

1、浅拷贝

拷贝就是把父对象的属性,全部拷贝给子对象。
接下来,我们看一个拷贝的例子:

function extendCopy(b) {
  var a = {};
  for (var i in b) {
    a[i] = b[i];
  }
  return a;
}

调用的时候,这样写:

// 调用
var copyA = {
  titleA: '标题A'
};
var copyB = extendCopy(copyA);
console.log(copyB.titleA); // 标题A

但是,这样的拷贝有一个问题。那就是,如果父对象的属性等于数组或另一个对象,那么实际上,子对象获得的只是一个内存地址,而不是真正拷贝,因此存在父对象被篡改的可能。

接下来,我们看一个篡改的示例:

function extendCopy(b) {
  var a = {};
  for (var i in b) {
    a[i] = b[i];
  }
  return a;
}
// 调用
var copyA = {
  arrayA: [1, 2, 3, 4]
};
var copyB = extendCopy(copyA);
copyB.arrayA.push(5);
console.log(copyA.arrayA); // [1, 2, 3, 4, 5]

结果是增加了一个5。
所以,extendCopy() 只是拷贝了基本类型的数据,我们把这种拷贝叫做“浅拷贝”。

2、深拷贝

因为浅深拷有如此弊端所以我们接下来看一下深拷贝
所谓”深拷贝”,就是能够实现真正意义上的数组和对象的拷贝。它的实现并不难,只要递归调用”浅拷贝”就行了。

function deepCopy(p, c) {
  var c = c || {};
  for (var i in p) {
    if (typeof p[i] === 'object') {
      c[i] = (p[i].constructor === Array) ? [] : {};
      deepCopy(p[i], c[i]);
    } else {
      c[i] = p[i];
    }
  }
  return c;
}
// 调用
var copyA = {
    arrayA: [1, 2, 3, 4]
};
var copyB = deepCopy(copyA);
copyB.arrayA.push(5);
console.log(copyA.arrayA); // [1, 2, 3, 4]

这样就完成了拷贝;

拓展

拓展一、数组的深浅拷贝
在使用JavaScript对数组进行操作的时候,我们经常需要将数组进行备份,事实证明如果只是简单的将它赋予其他变量,那么我们只要更改其中的任何一个,然后其他的也会跟着改变,这就导致了问题的发生。

 var arr = [1, 2, 3];
var copyarr = arr;
copyarr.push(4);
console.log(arr); // [1, 2, 3, 4]
console.log(copyarr); // [1, 2, 3, 4]

像上面的这种直接赋值的方式就是浅拷贝,很多时候,这样并不是我们想要得到的结果,其实我们想要的是arr的值不变,不是吗?
方法一:js的slice函数

对于array对象的slice函数,
返回一个数组的一段。(仍为数组)
arrayObj.slice(start, [end])
参数
arrayObj
必选项。一个 Array 对象。
start
必选项。arrayObj 中所指定的部分的开始元素是从零开始计算的下标。
end
可选项。arrayObj 中所指定的部分的结束元素是从零开始计算的下标。
说明
slice 方法返回一个 Array 对象,其中包含了 arrayObj 的指定部分。
slice 方法一直复制到 end 所指定的元素,但是不包括该元素。如果 start 为负,将它作为 length + start处理,此处 length 为数组的长度。如果 end 为负,就将它作为 length + end 处理,此处 length 为数组的长度。如果省略 end ,那么 slice 方法将一直复制到 arrayObj 的结尾。如果 end 出现在 start 之前,不复制任何元素到新数组中。

方法二:js的concat方法

var a=[1,2,3];  
var b=a;  
var c=[].concat(a);  
a.push(4);  
console.log(b);  
console.log(c);  


拓展二:$.extend()
用过jquery的朋友都知道jquery中有$.extend()
$.extend( [deep ], target, object1 [, objectN ] )

deep 类型: Boolean 如果是true,合并成为递归(又叫做深拷贝)。
target 类型: Object 对象扩展。这将接收新的属性。 object1 类型: Object 一个对象,它包含额外的属性合并到第一个参数.
objectN 类型: Object 包含额外的属性合并到第一个参数
当我们提供两个或多个对象给$.extend(),对象的所有属性都添加到目标对象(target参数)。
如果只有一个参数提供给$.extend(),这意味着目标参数被省略。在这种情况下,jQuery对象本身被默认为目标对象。这样,我们可以在jQuery的命名空间下添加新的功能。这对于插件开发者希望向 jQuery 中添加新函数时是很有用的。
请记住,目标对象(第一个参数)将被修改,并且将通过$.extend()返回。然而,如果我们想保留原对象,我们可以通过传递一个空对象作为目标对象:
var object = $.extend({}, object1, object2);
在默认情况下,通过$.extend()合并操作不是递归的;如果第一个对象的属性本身是一个对象或数组,那么它将完全用第二个对象相同的key重写一个属性。这些值不会被合并。可以通过检查下面例子中 banana 的值,就可以了解这一点。然而,如果将 true 作为该函数的第一个参数,那么会在对象上进行递归的合并。
警告:不支持第一个参数传递 false 。
1、合并两个对象,并修改第一个对象。

var obj1 = {
  name: 'name1',
  addr: {
    p: '浙江',
    c: '杭州'
   },
   age: 20
};
var obj2 = {
   addr: {
    d: '西湖'
   },
   sex: 1
};
$.extend(obj1, obj2);
console.log(JSON.stringify(obj1));
// {"name":"name1","addr":{"d":"西湖"},"age":20,"sex":1}

2、采用递归方式合并两个对象,并修改第一个对象

var obj1 = {
  name: 'name1',
  addr: {
    p: '浙江',
    c: '杭州'
  },
  age: 20
};
var obj2 = {
  name: 'name2',
  addr: {
    d: '西湖'
  },
  sex: 1
};
$.extend(true, obj1, obj2);
console.log(JSON.stringify(obj1));
// {"name":"name2","addr":{"p":"浙江","c":"杭州","d":"西湖"},"age":20,"sex":1}

3、合并 defaults 和 options 对象,并且不修改 defaults 对象。这也是常用的插件开发模式。

var defaults = { isAuto: false, limit: 5, name: "foo" };
var options = { isAuto: true, name: "bar" };
var settings = $.extend( {}, defaults, options );
console.log(JSON.stringify( settings ));
//settings -- {"isAuto":true,"limit":5,"name":"bar"}