js深コピーと浅いコピー

4731 ワード

javaScriptの変数タイプ
  • 基本タイプ:5種類の基本データタイプUdefined、Null、Boolean、Number、String、変数は直接値によって保存され、スタックメモリに保存された簡単なデータセグメントで直接アクセスできます.
  • 参照タイプ:ヒープメモリに格納されているオブジェクトは、変数が保存されているポインタであり、このポインタは別の位置を指しています.参照タイプ(オブジェクト、配列など)の値にアクセスする必要がある場合は、まずスタックからそのオブジェクトのアドレスポインタを取得し、次にスタックメモリから必要なデータを取得します.
  • JavaScriptの記憶オブジェクトはいずれもアドレスを保存しているので、浅いコピーはobj1obj2と同じブロックのメモリアドレスを指すことになる.どちらかの内容が変更されました.元のメモリで修正するとコピー対象とソースオブジェクトが変更されます.深度コピーは新しいメモリアドレスを開いて、元のオブジェクトの各属性を一つずつコピーします.コピー対象とソースオブジェクトのそれぞれの動作には影響がありません.
    浅いコピーの実現
  • 簡単な引用コピー
  •    var data = {a: 1, b: 2};
       var c = data;
    
  • Object.assign()は、第一層変数に対してのみ深くコピーされます.
    var data =  {
      a: 1,
      b: { f: { g: 1 } },
      c: [ 1, 2, 3 ]
    };
    
    var objData = Object.assign({}, data);
    
    console.log(objData === data); //false
    console.log(objData.b.f === data.b.f); //true
    
  • Arayのsliceおよびconcat方法(Object.assignと同じ)Arraysliceおよびconcat方法は、元の配列を修正せずに、元の配列の要素を浅いコピーした新しい配列だけを返す.深くコピーしたように見えますが、実は浅いコピーです.
    var array = [1,2,3]; 
    var array_shallow = array; 
    var array_concat = array.concat(); 
    var array_slice = array.slice(0); 
    console.log(array === array_shallow); //true 
    console.log(array === array_slice); //false,“   ”    
    console.log(array === array_concat); //false,“   ”    
    
    は、直接参照コピーとは異なるconcatおよびsliceによって返される異なる配列例を示している.
  • 元の配列の要素は下記の規則に従ってコピーされます.
  • 要素がオブジェクト参照(実際のオブジェクトではない)である場合、sliceは、このオブジェクトをコピーして新しい配列に参照する.両方のオブジェクトの参照は同じオブジェクトを参照しています.参照されているオブジェクトが変化すると、新しいものと元の配列のこの要素も変化します.
     var array = [1, [1,2,3], {name:"array"}]; 
     var array_concat = array.concat();
     var array_slice = array.slice(0);
    
     array_concat[1][0] = 5;  //  array_concat        
    
     console.log(array[1]); //[5,2,3] 
     console.log(array_slice[1]); //[5,2,3] 
    
     array_slice[2].name = "array_slice"; //  array_slice        
    
     console.log(array[2].name); //array_slice
     console.log(array_concat[2].name); //array_slice
    
    によれば、Arayのconcatおよびsliceは、本当に深い複製ではなく、配列中のオブジェクト要素(ObjectArrayなど)は、参照のみをコピーしているだけであることがわかる.
  • は、文字列、数字およびブール値(StringNumberまたはBooleanオブジェクトではない)に対して、これらの値を新しい配列にコピーする.他の配列でこれらの文字列または数字またはブール値を変更すると、他の配列に影響を与えません.
  • は、2つの配列のいずれかに新しい要素を追加すると、もう1つは影響を受けない.
    var array = [1, 2, 3];
    
    var array_shallow = array;
    var array_concat = array.concat();
    var array_slice = array.slice(0);
    
    array.push(4);
    
    console.log(array_concat); //[1, 2, 3]
    
  • 深くコピーした実装
    JSON対象のパーとストリングスsliceオブジェクトはJSONに導入された新しいタイプ(サポートされているブラウザはIE 8+)であり、ES5オブジェクトJSON方法はparse文字列をJSONオブジェクトに逆シーケンス化することができ、JS方法はstringifyオブジェクトシーケンスをJS文字列に変換することができ、これらの2つの方法によって、オブジェクトの深度コピーを実現することもできる.
    var source = { name:"source", child:{ name:"child" } } 
    var target = JSON.parse(JSON.stringify(source));
    target.name = "target";  //  target name  
    
    console.log(source.name); //source 
    console.log(target.name); //target
    
    target.child.name = "target child"; //  target child 
    console.log(source.child.name); //child 
    console.log(target.child.name); //target child
    
    var source = { name:function(){console.log(1);}, child:{ name:"child" } } 
    var target = JSON.parse(JSON.stringify(source));
    
    console.log(target.name); //undefined
    
    var source = { name:function(){console.log(1);}, child:new RegExp("e") }
    var target = JSON.parse(JSON.stringify(source));
    
    console.log(target.name); //undefined
    console.log(target.child); //Object {}
    
    この方法は比較的簡単で、基本的な深度コピー要求を満たすことができ、JSONフォーマットで表現できるすべてのデータタイプを処理することができるが、正規表現タイプ、関数タイプなどに対しては、深くコピーすることができない(しかも、直接に対応する値を失う).
    もう一つの悪いところは相手を捨てることができるJSONです.つまり、深くコピーした後、このオブジェクトの元の構造関数が何であれ、深くコピーした後にconstructorになります.
    オブジェクトに循環参照がある場合も正確に処理できません.
    カスタム方法で深コピーを実現
    function clone(data) {
        if (Array.isArray(data)) {
            let newArr = [];
            for (let i = 0; i < data.length; i++) {
                newArr[i] = clone(data[i]);
            }
    
            return newArr;
        } else if (data instanceof Object) {
            let obj = {};
            for (let key in data) {
                obj[key] = clone(data[[key]]);
            }
            return obj;
        }else {
            return data;
        }
    }
    
    var data =  {
        a: 1,
        b: { f: { g: 1 } },
        c: [ 1, 2, 3 ]
    };
    
    var objData = clone(data);
    console.log(objData === data);  //false
    console.log(objdata.b.f === data.b.f); //false