js对象浅拷贝和深拷贝

263 阅读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、深拷贝

<!DOCTYPE html>
<html>

	<head>
		<meta charset="UTF-8">
		<script type="text/javascript" src=""></script>
		<script type="text/javascript">
			console.log("{}.toString.call('sss') == '[object String]:      " + ({}.toString.call('sss') == '[object String]'));
			var a = {
				name: "John",
				location: {
					city: "Boston",
					county: "USA"
				}
			};
			var b = {
				last: "Resig",
				location: {
					state: "MA",
					county: "China"
				}
			};
			console.log("潜复制:" + JSON.stringify($.extend({}, a, b)));
			console.log("深复制:" + JSON.stringify($.extend(true, {}, a, b)));
			console.log(JSON.stringify(a));
			console.log(JSON.stringify(b));

			console.log('-------------------------------------------------------');
			//深克隆
			function judgeType(arg) { //判断js数据类型
				return Object.prototype.toString.call(arg).slice(8, -1);
			}

			function deepCloneNick(obj) { //深克隆
				var result, oClass = judgeType(obj);
				//确定result的类型
				if(oClass === "Object") {
					result = {};
				} else if(oClass === "Array") {
					result = [];
				} else {
					return obj;
				}
				for(var key in obj) {
					var copy = obj[key];
					if(judgeType(copy) === "Object" || "Array") {
						//result[key]=deepCloneNick(copy);//递归调用 避免函数名改变 改成下面一句
						result[key] = arguments.callee(copy);   //返回正被执行的 Function 对象,也就是所指定的 Function 对象的正文。
					} else {
						result[key] = obj[key];
					}
				}
				return result;
			}
			console.log(deepCloneNick({
				a: [1, 2],
				b: 3
			}));
			console.log(deepCloneNick([{
				a: 'a'
			}, 2]));
		</script>
		<title></title>
	</head>

	<body>
	</body>

</html>
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;
			}
			console.log(deepCopy({
				a: [1, 2],
				b: 3
			}));
			console.log(deepCopy([{
				a: 'a'
			}, 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方法

拓展二:$.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"}