【问题标题】:Using iterative style to clone an object in JavaScript在 JavaScript 中使用迭代样式克隆对象
【发布时间】:2012-01-06 00:46:02
【问题描述】:

是否可以重写以下 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

【问题讨论】:

  • 你可以重写一个递归算法来使用一个迭代算法,如果递归太深,这有时是必要的,但是你有理由想要专门转向继续传递吗?我认为现有的递归算法会更容易理解……
  • 我也希望看到一个迭代版本。
  • 我改了问题。唯一的目标是让它更快。
  • 你使用的是什么继承实现?如果不是extension(我现在不知道实际名称),那么clone_recursive 的两个实现都可能不正确。
  • outis,让我们假设输入对象是 Object 的直接后代,并且它的所有属性都是自己的属性。

标签: javascript algorithm recursion tree iteration


【解决方案1】:

值得怀疑的是,迭代版本是否真的会更快,因为您将递归调用替换为对排队函数的多次调用。对迭代的转换有助于防止堆栈溢出(因为调用堆栈往往比解释语言中的堆小),并且在没有尾调用优化的语言中使用尾递归。

【讨论】:

  • 在 FF3.6 上,我回答的版本在经验上比递归版本快。使用队列中的对象来保存 2 个值是主要瓶颈。
  • @LastCoder:在 jsFiddle 上使用您的基准脚本,运行 10 次试验,每次试验运行 10 次,每次运行调用每个函数 100 次,我最终的平均执行时间为 19.331 毫秒(标准dev: 0.2424) 用于递归版本,23.49 ms (std dev: 0.1749) 用于 FF 3.6 下的迭代。在 Chrome 15 下,递归版本的平均时间为 6.268 毫秒(标准开发 0.2291),迭代为 6.409 毫秒(标准开发 0.2771)。根据我的研究,递归函数在经验上比迭代更快。如果 -1 是你,我现在就把它拿回来 :-)。
  • “如果 -1 是你”,那不是……我测试了对测试对象的一些修改,看看在每一层中嵌入更多递归对象是否有任何不同。无论被复制的测试对象的方差如何,递归确实快了 5% 到 15%。我最初的回答是我在虚拟机中运行 FF 的前三轮测试代码的观察偏差。
【解决方案2】:

您在迭代版本中存储(使用队列)的方式是导致速度下降的原因。 使用数组堆栈并为每个项目创建一个条目,而不是一个包含两个项目(基数和值)的对象。

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。在某些运行中,迭代版本比递归更快,而在其他时候递归胜出。

【讨论】:

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

我尝试使用队列的链表实现来看看会发生什么。我认为您的问题可能是函数调用开销和 shift() 不一定是 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;
}

【讨论】:

  • jsperf.com/clone-an-object/3 带有链表的迭代版本仍然比递归版本慢。虽然,它比 Safari 中的数组快,在 Firefox 中慢,在 Chrome 中完全一样。
  • 你的版本不做深拷贝:clone_iterative_linked_list({a:{b:1}}) // {a:{}}
  • 哦该死,quehead.next 发生在我有机会将项目添加到列表之前。现在我想知道我在发布之前运行的测试用例到底是如何工作的:D
【解决方案4】:

好吧,这尝试使用 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[""];
}

【讨论】:

    【解决方案5】:

    迭代。 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

    【讨论】:

      猜你喜欢
      • 2011-07-18
      • 2013-01-03
      • 1970-01-01
      • 1970-01-01
      • 2018-03-07
      • 2014-01-11
      • 2015-02-08
      • 2014-11-20
      • 2014-07-08
      相关资源
      最近更新 更多