关于深度 clone 的后续讨论

原创
2012/11/23 17:00
阅读数 628

在 zTree v3.5 发布之前看到了 [愚人码头] 的一篇文章《JavaScript深度克隆(深度拷贝)一个对象》,觉得 zTree 中的 clone 方法是应该适当优化一下,看着优化后的代码的确很简洁,大概测试无误,简单修正一下后,就直接拿了过来。

后来想了想,其实有时候代码也不能绝对为了优雅而忽略了性能,所以今天做了个简单的性能测试。

在测试过程中发现新代码虽然优雅,但效率却要略低于原先的代码,这让我很诧异,经过分析原来是

if(obj.hasOwnProperty(i)){...}
这个判断在作怪,每个属性在复制前都要判断一下,自然影响了效率。而且这句话还会产生另一个隐患——对于继承出来的对象在 clone 时会导致继承的属性全部被丢掉了。(对比了一下 jQuery 的extend 方法,clone 对象后是不会丢掉继承的属性的) 所以 果断删除这一句,删除后,效率与原先代码没有什么差异了。( 这个修正会在 v3.5.01 中一起发布的,对于 zTree 的节点数据来说应该是不会有这方面影响的

同时,对于有些朋友推崇的最精简的方法做了性能比较(这里暂不讨论这个方法有其他一些隐患以及兼容方面的问题)

JSON.parse(JSON.stringify(obj))
真的很简练的方法,但是他的效率只能达到咱们自己写的clone 方法的 一半;其实原因也很明显,先要遍历一遍 JSON 对象,把它转为 string;然后再把 string 转为 JSON 对象,同样的工作做了两遍,自然效率减半了。

我并不想强调你一定要用哪种方法,具体问题还是需要具体分析,只是想说明大家再决定采用什么方法的时候,还是要多考虑一些因素:代码的优雅、效率、兼容性 等等,选择适合自己的方法就可以了。

完整代码如下:

<!DOCTYPE HTML>
<html>
  <head>
    <title>CLONE TEST</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
	<script type="text/javascript">
		//zTree 原先的 clone 方法
		function cloneOld (jsonObj) {
			var buf;
			if (jsonObj instanceof Array) {
				buf = [];
				var i = jsonObj.length;
				while (i--) {
					buf[i] = arguments.callee(jsonObj[i]);
				}
				return buf;
			}else if (typeof jsonObj == "function"){
				return jsonObj;
			}else if (jsonObj instanceof Object){
				buf = {};
				for (var k in jsonObj) {
					buf[k] = arguments.callee(jsonObj[k]);
				}
				return buf;
			}else{
				return jsonObj;
			}
		}
		//zTree 使用更简洁的 clone 方法
		function cloneNew (obj) {
			if (obj === null) return null;
			var o = obj.constructor === Array ? [] : {};
			for(var i in obj){
				//测试中可以打开 if 语句,看看效率的变化
//				if(obj.hasOwnProperty(i)){
					o[i] = (obj[i] instanceof Date) ? new Date(obj[i].getTime()) : (typeof obj[i] === "object" ? arguments.callee(obj[i]) : obj[i]);
//				}
			}
			return o;
		}
		//最精简的 clone 方法
		function cloneSimple (obj) {
			return JSON.parse(JSON.stringify(obj));
		}
		//测试方法
		function testClone(clone) {
			var i, times = 1000, start = new Date();
			for (i=0; i<times; i++) {
				clone(testCase);
			}
			var end = new Date();
			console.log(clone.name + " use " + (end.getTime() - start.getTime()));
		}

		var testCase = [], curId=1, maxLevel=3, num=4;
		//初始化测试数据
		function makeCase (list, level) {
			var i;
			for (i=0; i<num; i++) {
				var obj = {
					id : curId,
					level : level,
					name : "testItem_" + curId,
					title : "test Title " + curId
				};
				curId++;
				if (level < maxLevel) {
					obj.children = [];
					makeCase(obj.children, level+1);
				}
				list.push(obj);
			}
		}
		makeCase(testCase, 1);
	</script>
  </head>
  <body>
	  <button type="button" onclick="testClone(cloneOld)">Test Clone Old</button>
	  <button type="button" onclick="testClone(cloneNew)">Test Clone New</button>
	  <button type="button" onclick="testClone(cloneSimple)">Test Clone Simple</button>
  </body>
</html>

展开阅读全文
打赏
0
2 收藏
分享
加载中
写的不错,支持。
2012/11/23 17:18
回复
举报
更多评论
打赏
1 评论
2 收藏
0
分享
返回顶部
顶部