JavaScriptの中の深いコピーと浅いコピー

6557 ワード

深度コピーと浅いコピーを言う前に、簡単な二つのケースを見ます.
//  1
var num1 = 1, num2 = num1;
console.log(num1) //1
console.log(num2) //1

num2 = 2; //  num2
console.log(num1) //1
console.log(num2) //2

//  2
var obj1 = {x: 1, y: 2}, obj2 = obj1;
console.log(obj1) //{x: 1, y: 2}
console.log(obj2) //{x: 1, y: 2}

obj2.x = 2; //  obj2.x
console.log(obj1) //{x: 2, y: 2}
console.log(obj2) //{x: 2, y: 2}
従来の考え方によれば、obj1は、num1と同様に、他の値の変化によって変わることはないはずであるが、ここのobj1obj2の変化に従って変化している.同じ変数でも、なぜ違いますか?これはJSの基本タイプと引用タイプの概念を導入します.
基本タイプと参照タイプ
ECMAScript変数は、2つの異なるデータタイプの値を含むことができます.基本タイプの値と参照タイプの値です.基本タイプの値とは、スタックメモリに保存されている簡単なデータセグメント、すなわちこの値がメモリに完全に保存されている場所のことです.参照タイプの値とは、メモリに保存されているオブジェクトのことで、変数に保存されているのは実際には1つのポインタだけで、このポインタはメモリの別の位置を指し、オブジェクトを保存します.
例えば、基本タイプと引用タイプの違いは、「チェーン店」と「単店」によって理解できます.基本タイプは新しいところにチェーン店を設置するという基準で新しく出店し、新しく開店した店は他の古い店とは関係がなく、それぞれ運営しています.引用のタイプは一つの店の鍵が二つあります.二つの社長に任せて同時に管理します.二つの社長の行為は一つの店の運営に影響を与える可能性があります.
上では、基本タイプと引用タイプの定義と違いを明確に紹介しています.現在の基本タイプは、Boolean、Null、Unidefined、Number、String、Symbolです.引用タイプはObject、Aray、Functionです.「現在」というのは、SymbolはES 6なので、後で新しいタイプが出てくるかもしれません.
前のケースに戻ります.ケース1の値は基本タイプで、ケース2の値は参照タイプです.ケース2の割当値は、典型的には浅いコピーであり、深くコピーし、浅いコピーという概念は、引用タイプのみに存在する.
深度コピーと浅いコピー
深いコピーと浅いコピーの由来を知っている以上、どうやって深くコピーするべきですか?まずそれぞれArayとObjectの独自の方法がサポートされているかを見ます.
Aray
var arr1 = [1, 2], arr2 = arr1.slice();
console.log(arr1); //[1, 2]
console.log(arr2); //[1, 2]

arr2[0] = 3; //  arr2
console.log(arr1); //[1, 2]
console.log(arr2); //[3, 2]
この時、arr2の修正はarr1に影響を与えていません.深度コピーの実現はそんなに難しくないようです.arr 1を二次元配列に変えてみます.
var arr1 = [1, 2, [3, 4]], arr2 = arr1.slice();
console.log(arr1); //[1, 2, [3, 4]]
console.log(arr2); //[1, 2, [3, 4]]

arr2[2][1] = 5; 
console.log(arr1); //[1, 2, [3, 5]]
console.log(arr2); //[1, 2, [3, 5]]
えっと、arr2はまたarr1を変えました.見たところ、sliceは一次元配列の深度コピーしかできないです.
同等の特性を備えているのは、concat、Aray.from()です.
Object
  • Object.assign()
  • var obj1 = {x: 1, y: 2}, obj2 = Object.assign({}, obj1);
    console.log(obj1) //{x: 1, y: 2}
    console.log(obj2) //{x: 1, y: 2}
    
    obj2.x = 2; //  obj2.x
    console.log(obj1) //{x: 1, y: 2}
    console.log(obj2) //{x: 2, y: 2}
    
    var obj1 = {
        x: 1, 
        y: {
            m: 1
        }
    };
    var obj2 = Object.assign({}, obj1);
    console.log(obj1) //{x: 1, y: {m: 1}}
    console.log(obj2) //{x: 1, y: {m: 1}}
    
    obj2.y.m = 2; //  obj2.y.m
    console.log(obj1) //{x: 1, y: {m: 2}}
    console.log(obj2) //{x: 2, y: {m: 2}}
    
    テストしても、Object.assign()は一次元オブジェクトの深度コピーしかできません.
  • JSON.parse(JSON.strigify(obj)
  • var obj1 = {
        x: 1, 
        y: {
            m: 1
        }
    };
    var obj2 = JSON.parse(JSON.stringify(obj1));
    console.log(obj1) //{x: 1, y: {m: 1}}
    console.log(obj2) //{x: 1, y: {m: 1}}
    
    obj2.y.m = 2; //  obj2.y.m
    console.log(obj1) //{x: 1, y: {m: 1}}
    console.log(obj2) //{x: 2, y: {m: 2}}
    
    JSON.parse(JSON.stringify(obj))はよさそうですが、MDN文書の説明にははっきりと書かれています.undefined、の任意の関数およびsmbol値は、プロビジョニング中に無視され(配列外のオブジェクトの属性値に現れたとき)、またはnullに変換される(配列中に現れたとき).obj1を改造してみます.
    var obj1 = {
        x: 1,
        y: undefined,
        z: function add(z1, z2) {
            return z1 + z2
        },
        a: Symbol("foo")
    };
    var obj2 = JSON.parse(JSON.stringify(obj1));
    console.log(obj1) //{x: 1, y: undefined, z: ƒ, a: Symbol(foo)}
    console.log(JSON.stringify(obj1)); //{"x":1}
    console.log(obj2) //{x: 1}
    
    obj1 JSON.stringify()をプロビジョニングする間、y、z、aは無視され、MDN文書の説明も検証されることが分かった.このようにすれば、JSON.parse(JSON.stringify(obj))の使用にも限界があり、undefined、function、smbol値を含むオブジェクトを深くコピーすることはできないが、JSON.parse(JSON.stringify(obj))は簡単で乱暴で、90%の使用シーンを満足している.
    検証の結果、JSが提供した独自の方法は、Aray、Objectの深度コピー問題を徹底的に解決できないことが分かりました.大殺器しか祭れません.再帰します.
    function deepCopy(obj) {
        //        
        let result = {}
        let keys = Object.keys(obj),
            key = null,
            temp = null;
    
        for (let i = 0; i < keys.length; i++) {
            key = keys[i];    
            temp = obj[key];
            //                  
            if (temp && typeof temp === 'object') {
                result[key] = deepCopy(temp);
            } else {
            //           
                result[key] = temp;
            }
        }
        return result;
    }
    
    var obj1 = {
        x: {
            m: 1
        },
        y: undefined,
        z: function add(z1, z2) {
            return z1 + z2
        },
        a: Symbol("foo")
    };
    
    var obj2 = deepCopy(obj1);
    obj2.x.m = 2;
    
    console.log(obj1); //{x: {m: 1}, y: undefined, z: ƒ, a: Symbol(foo)}
    console.log(obj2); //{x: {m: 2}, y: undefined, z: ƒ, a: Symbol(foo)}
    
    再帰的に完全に前に残したすべての問題を解決することができ、我々はまた、第三者ライブラリ:jqueryの$.extendとlodashの_.cloneDeepを使用して、深度コピーを解決することができます.上はObjectで検証していますが、Arayに対しても同様に適用されます.Arayも特殊なObjectですから.
    ここでは、深度コピーの問題がほぼ一段落しました.しかし、もう一つの特別なシーンがあります.
    循環参照コピー
    var obj1 = {
        x: 1, 
        y: 2
    };
    obj1.z = obj1;
    
    var obj2 = deepCopy(obj1);
    
    このとき、先ほどのdeepCopy関数を呼び出すと、あるサイクルの再帰プロセスに陥り、爆発的なスタックを引き起こします.jqueryの$.extendも解決されていません.この問題を解決するのも簡単です.オブジェクトのフィールドがこのオブジェクトまたはオブジェクトの任意の親レベルを引用しているかどうかを判断するだけでいいです.コードを変更してください.
    function deepCopy(obj, parent = null) {
        //        
        let result = {};
        let keys = Object.keys(obj),
            key = null,
            temp= null,
            _parent = parent;
        //                  
        while (_parent) {
            //                   
            if (_parent.originalParent === obj) {
                //               
                return _parent.currentParent;
            }
            _parent = _parent.parent;
        }
        for (let i = 0; i < keys.length; i++) {
            key = keys[i];
            temp= obj[key];
            //             
            if (temp && typeof temp=== 'object') {
                //                          parent         
                result[key] = DeepCopy(temp, {
                    originalParent: obj,
                    currentParent: result,
                    parent: parent
                });
    
            } else {
                result[key] = temp;
            }
        }
        return result;
    }
    
    var obj1 = {
        x: 1, 
        y: 2
    };
    obj1.z = obj1;
    
    var obj2 = deepCopy(obj1);
    console.log(obj1); //           ~ 
    console.log(obj2); //           ~ 
    
    これで、循環参照をサポートする深いコピー関数が完成しました.もちろん、lodashの_.cloneDeepも使えますよ.
    著者:simbawuリンク:https://juejin.im/post/5ad5b908f265da23870f540d ソース:掘削金の著作権は作者の所有になります.商業転載は作者に連絡して授権を獲得してください.商業転載ではないので、出典を明記してください.