JavaScriptがどのように深さコピーを実現するかを深く検討します.

9717 ワード

コード多重モードには「コピー属性モード」というものがあります.コード多重というと、コードの継承性が考えられるかもしれませんが、重要なのはその最終的な目標を覚えておくことです.コードを多重化したいです.継承性は、唯一の方法ではなく、コード多重化を実現する手段にすぎない.コピー属性も多重モードであり、継承性とは異なる.このようなモードでは、オブジェクトは別のオブジェクトからメンバを取得し、その方法はコピーだけでよい.jQueryを使ったことがあるのはすべて知っていて、それは1つの$exted()の方法があって、その用途は第三者のプラグインを拡張する以外、属性のを複製することに用いることができます.次はexted()関数の実装コードを見てみます.(ここのはjQueryのソースコードではなく、単純な例です.)
function extend(parent, child) {
    var i;

    //         child
    //           
    child = child || {}; 

    //  parent       
    //          
    //          child   
    for(i in parent) {
        if(parent.hasOwnProperty(i)) {
            child[i] = parent[i];
        }
    }

    //      child
    return child;
}
上のコードは、親オブジェクトのメンバーだけを巡回して、サブオブジェクトにコピーする簡単な実装です.上のexted()方法でテストします.
var dad = {name: "Adam"};
var kid = extend(dad);
console.log(kid.name); //Adam
exted()法は正常に動作することができることを発見した.しかし、上にはいわゆるレプリカがあります.浅いコピーを使う場合、サブオブジェクトの属性が変更され、またオブジェクトの属性がぴったりとなると、このような操作も親オブジェクトを修正します.これは私たちが望んだ結果ではない場合が多いです.次の状況を考える:
var dad = {
    counts: [1, 2, 3],
    reads: {paper: true}
};

var kid = extend(dad) //  extend()   dad      kid  
kid.counts.push(4); // 4   kid.counts    
console.log(dad.counts); //[1, 2, 3, 4]
上記の例を通して、kid.com unts属性を修正した後(元素4を追加しました)、dad.com untsも影響を受けます.これは、レプリカを使用する場合、オブジェクトは参照によって伝達されるので、kid.com untsとdad.com untsは同じ配列を指しています.
 
次に、exted()関数を修正して、深さコピーを実現します.私たちが必要なことは、親オブジェクトの属性を確認し、該当属性がオブジェクトである場合には、そのオブジェクトの属性を再帰的にコピーすることです.また、このオブジェクトが配列であるかどうかを検出する必要があります.これは配列の字面量作成方式とオブジェクトの字面量作成方式が違っています.前者は[],後者は{}です.検出配列はObject.prototype.toString()法を用いて検出でき、配列であれば「Object Aray」に戻ります.深さコピーバージョンのexted()関数を見てみましょう.
function extendDeep(parent, child) {
    child = child || {};
    for(var i in parent) {
        if(parent.hasOwnProperty(i)) {
            //           
            if(typeof parent[i] === "object") {
                //
                //                       
                //   [],    {}
                child[i] = (Object.prototype.toString.call(parent[i]) === "[object Array]") ? [] : {};

                //    extend
                extendDeep(parent[i], child[i]);
            } else {
                child[i] = parent[i];
            }

        }
    }

    return child;
}
はい、深さコピーの関数はもう書きました.これからテストしてみます.そのような作業が期待できるかどうか、すなわち深さコピーが可能かどうかを確認します.
var dad = {
    counts: [1, 2, 3],
    reads: {paper: true}
};

var kid = extendDeep(dad);

//  kid counts   reads  
kid.counts.push(4);
kid.reads.paper = false;


console.log(kid.counts); //[1, 2, 3, 4]
console.log(kid.reads.paper); //false

console.log(dad.counts); //[1, 2, 3]
console.log(dad.reads.paper); //true
上記の例を通して、子供のオブジェクトのkid.com untsとkid.readsを修正しても、父の対象となるdad.com untsとkid.readsは変わっていないことが分かりました.したがって、私達の目的は実現されました.
深くコピーした基本的な考え方をまとめます.
1.現在の属性がオブジェクトかどうかを検出する
2.配列は特殊なオブジェクトであるため、属性がオブジェクトであることを前提として配列かどうかを検査する必要があります.
3.配列であれば、空の配列を作成します.そうでないと、{}空のオブジェクトを作成し、サブオブジェクトの現在の属性に値を付けます.その後、再帰的にextendDeep関数を呼び出す.
上記の例は、再帰的アルゴリズムを用いて実現される深度コピー法を自分たちで使用させる.実際には、ES 5によって追加されたJSONオブジェクトから提供される2つの方法も、JSON.strigify()とJSON.parse()の深さコピーを実現することができる.前者はオブジェクトを文字列に変換します.後者は文字列をオブジェクトに変換します.次にこの方法を用いて深さコピーの関数を実現する.
function extendDeep(parent, child) {

    var i,
        proxy;

    proxy = JSON.stringify(parent); // parent        
    proxy = JSON.parse(proxy) //         ,  parent     

    child = child || {};


    for(i in proxy) {
        if(proxy.hasOwnProperty(i)) {
            child[i] = proxy[i];
        }
    }

    proxy = null; //  proxy     ,       

    return child;
}
以下はテスト例です.
var dad = {
    counts: [1, 2, 3],
    reads: {paper: true}
};

var kid = extendDeep(dad);

//  kid counts   reads  
kid.counts.push(4);
kid.reads.paper = false;


console.log(kid.counts); //[1, 2, 3, 4]
console.log(kid.reads.paper); //false

console.log(dad.counts); //[1, 2, 3]
console.log(dad.reads.paper); //true
深さコピーも実現することが分かった.基本的には、JSON.parseとJSON.strigifyは、内蔵関数であるため、処理が早いという方法がおすすめです.また、前のような方法は再帰的呼び出しを使用しており、再帰的には効率の比較的低いアルゴリズムであることを知っています.
はい、深さコピーについてはここまでです.私も勉強しながらここでまとめます.間違いがあれば、お願いします.