2011-11-25 61 views
12

是否可以重写下面的JavaScript递归函数以使其更快?使用迭代风格在JavaScript中克隆对象

function clone_recursive(object) { 
    var result = {}; 
    for (var key in object) { 
     var value = object[key]; 
     if (typeof value === 'object') { 
      result[key] = clone_recursive(value); 
     } else { 
      result[key] = value; 
     } 
    } 
    return result; 
} 

我重写了它在迭代风格,但它并没有获得任何性能,实际上速度≈20%下降。

function clone_iterative(object) { 
    var result = {}; 
    var queue = [{base: result, value: object}]; 
    var item; 
    while (item = queue.shift()) { 
     var current = item.value; 
     var base = item.base; 
     for (var key in current) { 
      var value = current[key]; 
      if (typeof value === 'object') { 
       var resultValue = base[key] = {}; 
       queue.push({base: resultValue, value: value}); 
      } else { 
       base[key] = value; 
      } 
     } 
    } 
    return result; 
} 

http://jsperf.com/clone-an-object/13

+0

那么你可以重写一个递归算法使用迭代算法,这有时是必要的,如果递归会太深,但你有一个理由要移动到延续专门传递?我认为现有的递归算法会更容易遵循... – nnnnnn

+0

我希望看到一个迭代版本。 – NVI

+0

我改变了问题。唯一的目标是加快速度。 – NVI

回答

9

这是值得怀疑的是一个迭代版本将真正成为速度更快,因为您要更换多次调用排队功能的递归调用。迭代转换有助于防止堆栈溢出(因为调用堆栈往往小于解释型语言中的堆栈),并且在没有尾部调用优化的语言中使用尾部递归。

+0

在FF3.6上,我回答的版本在经验上比递归版本更快。使用队列中的对象来保存2个值是主要瓶颈。 –

+0

@LastCoder:在jsFiddle上使用您的基准测试脚本,每次运行10次,每次运行10次,每次运行调用100次函数,结束时的递归平均执行时间为19.331 ms(std dev:0.2424)版本和23.49毫秒(标准开发:0.1749)的FF 3.6下的迭代。在Chrome 15下,递归版本的平均时间为6.268 ms(std dev 0.2291),迭代时间为6.409 ms(std dev 0.2771)。从我的研究中,递归函数在经验上比迭代更快。如果-1是你,我现在就把它拿回来:-)。 – outis

+0

“如果-1是你”,那不是......我测试了对测试对象的一些修改,看看是否有更多嵌入在每个图层中的递归对象有所不同。无论测试对象被复制的方差如何,递归确实会更快5%到15%。我的最初答复是观察偏见,我在虚拟机中运行FF的前三个测试代码运行。 –

2

我尝试使用队列的链表实现来查看会发生什么。我觉得你的问题可能已经在函数调用的开销和shift()不necessaroly是O(1)

Jsperf说,这是最快的这种方式(我在FF7测试):http://jsperf.com/clone-an-object/4 后来我不是当然,如果我没有搞错基准,我不太习惯jsperf网站。

编辑︰我在我的代码中有一个延迟的错误。它实际上只是浅拷贝

以下是我使用的代码的固定版本。它的速度更快那么其他必要的版本,但还是输给了递归代码:

function clone_iterative_linked_list(object) { 
    var result = {}; 
    var queueHead = {base: result, value: object, next: null}; 
    var queueTail = queueHead; 

    for(;queueHead; queueHead = queueHead.next){ 
     var item = queueHead; 

     var current = item.value; 
     var base = item.base; 
     for (var key in current) { 
      var value = current[key]; 
      if (typeof value === 'object') { 
       var resultValue = base[key] = {}; 
       queueTail.next = {base: resultValue, value: value, next:null}; 
       queueTail = queueTail.next; 
      } else { 
       base[key] = value; 
      } 
     } 
    } 
    return result; 
} 
+0

http://jsperf.com/clone-an-object/3带链表的迭代版本仍然比递归版本慢。虽然它比Safari中的数组快,但在Firefox中速度较慢,而在Chrome中则完全相同。 – NVI

+0

您的版本不会执行深层复制:clone_iterative_linked_list({a:{b:1}})// {a:{}} – NVI

+0

Oh shit,'quehead.next'发生在我有机会将项目添加到名单。现在我想知道在发布这个测试用例之前我是如何运行的:D – hugomg

3

你(使用队列)存储在迭代版本的方法是什么导致增长放缓。 使用数组堆栈并为每个项目设置一个条目,而不是包含这两个项目的对象(基本值为&值)。

function clone_iterative(object) { 
    var result = {}; 
    var stack = [result, object]; 
    var curr, base; 
    while (curr = stack.pop()) { 
     var base = stack.pop(); 
     for (var key in curr) { 
      var value = curr[key]; 
      if (typeof value === 'object') { 
       stack.push(base[key] = {}); 
       stack.push(value) 
      } else { 
       base[key] = value; 
      } 
     } 
    } 
    return result; 
} 

查看关于JS Fiddle的clone function benchmark suite。在一些运行中,迭代版本比递归更快,其他时候递归胜出。

+0

这是对原始迭代版本的改进。它与Chrome中的clone_recursive配对运行,但在所有其他浏览器中仍然较慢。 http://jsperf.com/clone-an-object/5 – NVI

1

那么,这个试图用JSON替代品来只有一个JSON遍历,也不能更快(见http://jsperf.com/clone-an-object/6):

function clone(x) { 
var r = {}, lastSrc, lastDst = r, stack = [], v; 
function replacer(key, value) { 
    while (this !== lastSrc && stack.length) { 
     lastDst = stack.pop(); 
     lastSrc = stack.pop(); 
    } 
    if (typeof value === "object") { 
     stack.push(lastSrc, lastDst); 
     lastDst[key] = v = new value.constructor; 
     lastDst = v; 
     lastSrc = value; 
     return value; 
    } else { 
     lastDst[key] = value; 
     return undefined; 
    } 
} 
JSON.stringify(x, replacer); 
return r[""]; 
} 
0

迭代。 2个阵列,不要使用pop()

function clone_iterative2(object) { 
    var result = {}; 
    var bases = [result]; 
    var objects = [object]; 
    for (var i = 0, length = 1; i < length; ++i) { 
     var current = objects[i]; 
     var base = bases[i]; 
     for (var key in current) { 
      var value = current[key]; 
      if (typeof value === 'object') { 
       bases.push(base[key] = {}); 
       objects.push(value); 
       ++length; 
      } else { 
       base[key] = value; 
      } 
     } 
    } 
    return result; 
} 

这是迄今为止最快的迭代。在Chrome Canary(17.0.959.0)中,它是整体速度最快的。在所有其他浏览器中,仍然比递归慢。

http://jsperf.com/clone-an-object/13