反復スタイルを使用して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

12
私は反復バージョンも見たいと思います。
追加された 著者 NVI,
私はその質問を変えた。唯一の目標は、それをより速くすることです。
追加された 著者 NVI,
outisでは、入力オブジェクトがObjectの直系子孫であり、そのすべてのプロパティが独自のプロパティであるとします。
追加された 著者 NVI,
繰り返しアルゴリズムを使用するように再帰アルゴリズムを書き直すことができます。これは、再帰が深すぎる場合には時には必要ですが、具体的に継続継続に移行する理由がありますか?私は、既存の再帰アルゴリズムは、従うのが簡単になるだろうと思う...
追加された 著者 nnnnnn,
どのような継承の実装を使用していますか? 拡張子(現時点では実際の名前が私から逃げ出します)以外のものがあれば、 clone_recursive の両方の実装が間違っている可能性があります。
追加された 著者 outis,
JavaScriptで完璧なクローンメソッドを書くことはできませんが、少なくとも配列を考慮する必要があります。これはかなり近いです: 追加された 著者 user123444555621,

5 答え

反復呼び出しをキューイング関数への複数の呼び出しで置き換えると、反復的なバージョンが本当に速くなることは疑いありません。反復処理への変換がスタックオーバーフロー(インタープリター言語のヒープよりもコールスタックの方が小さくなる傾向がある)を防ぎ、テールコール最適化のない言語でテール再帰を使用すると、

9
追加された
@LastCoder:jsFiddleのベンチマークスクリプトを使用して、10回の試行を10回実行します。各試行は1回の実行につき100回と呼ばれ、再帰バージョンの平均実行時間は19.331ms(std dev:0.2424)ですFF 3.6の反復の場合は23.49 ms(標準偏差:0.1749)です。 Chrome 15では、再帰バージョンの平均時間は6.268 ms(標準偏差0.2291)、繰り返しは6.409 ms(標準偏差0.2771)です。私の研究から、再帰関数は反復関数よりも経験的に高速です。 -1があなただったら、私は今それを取り戻すでしょう:-)。
追加された 著者 outis,
FF3.6では、私が答えたバージョンは再帰バージョンより経験的に速かった。キュー内のオブジェクトを使用して2つの値を保持することは、主要なボトルネックでした。
追加された 著者 Louis Ricci,
"-1があなただったら"、それは...私はテストオブジェクトのいくつかの変更をテストし、より多くの再帰的なオブジェクトが各レイヤーに埋め込まれている場合、何か違いがあるかどうかを確認します。再帰は実際にコピーされるテストオブジェクトの分散に関係なく5〜15%高速です。最初の返信は、仮想マシンでFFを実行しているテストコードの最初の3回の実行からの観測偏見でした。
追加された 著者 Louis Ricci,

The way you are storing (using your queue) in the iterative version is what's causing the slowdown. Use an array-stack and have an entry for each item instead of an object that holds both items (base & value).

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のクローン機能ベンチマークスイートをご覧ください。いくつかの実行では、反復的なバージョンは再帰的なものより速く、他の回帰は再帰的に勝ちます。

3
追加された
元の反復バージョンよりも改善されています。これはChromeのclone_recursiveとペアで実行されますが、他のすべてのブラウザではさらに遅くなります。 jsperf.com/clone-an-object/5
追加された 著者 NVI,

私はキューのリンクリストの実装を使用して何が起こるかを見てみました。私はあなたの問題は、関数呼び出しのオーバーヘッドとシフト()が必要ではないと思いますO(1)

Jsperfは、これが最も速いと言った(FF7でテストした): http:// jsperf .com/clone-an-object/4 しかし、私はjsperfのウェブサイトにあまり慣れていないので、ベンチマークを台無しにしていないかどうかはわかりません。

Edit: I had a retarded bug in my code. It was actually just shallow copying

以下は私が使用したコードの固定バージョンです。そのより速い他の必須のバージョンが、まだ再帰的なコードを失う:

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;
}
2
追加された
ああ、リストに項目を追加する前に quehead.next が起こります。今私はこれがうまくいった:D
追加された 著者 hugomg,
リンクリスト付きの反復バージョンの jsperf.com/clone-an-object/3 再帰的なものよりもまだ遅い。 Safariの配列よりも速いですが、Firefoxでは遅く、Chromeではまったく同じです。
追加された 著者 NVI,
あなたのバージョンは深いコピーをしません:clone_iterative_linked_list({a:{b:1}})// {a:{}}
追加された 著者 NVI,

まあ、これは、JSONトラバーサルを1つだけ持たせるために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[""];
}
1
追加された

Iterative. 2 arrays, don't use 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

0
追加された
JavaScript - 日本のコミュニティ
JavaScript - 日本のコミュニティ
2 参加者の

日本人コミュニティのjavascript