在 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>