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