JavaScriptオブジェクトの深度コピー/浅いコピーに遭遇したピットと解決方法

6904 ワード

もしこの文章はあなたに対して何か助けがありますか?あるいは何かご意見や質問がありましたら、この文章の下で返答してください.皆様が討論に参加することを心から歓迎します.どうぞよろしくお願いします.
自分の小さなブログJavaScriptの相手が出会った穴と解決方法をコピーしてくれます.ハンドル君の小閣です.だから、恥知らずにオリジナルを計算します.
最近ある合宿に参加して、JavaScriptは対象のコピー問題が発生しました.需要があります.対象にあげる関数を作成して、対象をコピーして、このコピーで得られた新しい対象に戻ります.例を挙げて、下記のようになります.
function clone(obj){
    //DO SOMETHING
    return newObject; //          
}
まず解法を思い付きました.
>ES 6デフォーカシング値(浅いコピー):
function clone(obj){
    return {...obj};
}
新しいオブジェクトを元のオブジェクトとして浅いコピー、すなわち属性Keyが一致しています.値が数または文字列であれば、値が伝達されます.そうでなければ、アドレス伝達、すなわちValue参照はソースオブジェクトと一致しています.
var a = {a:1, b:2, c:3, d:[0, 1, 2]}
var b = clone(a);
console.log(b.d[1]); //1
b.d[1] = 2;
console.log(b.d[1]); //2
console.log(a.d[1]); //2
コピーされたオブジェクトに含まれる配列またはオブジェクトを編集し、ソースオブジェクトに影響を与えます.これは明らかに私たちが望む結果ではありませんが、オブジェクト内に配列またはオブジェクトが含まれていない場合、この方法はオブジェクトコピーを素早く作成するための実用的な方法といえます.ES 6において、Objectはassign()の方法を提供し、同じ効果を実現することができる.
>ES 6 Object.assign(浅いコピー):
function clone(obj){
    return Object.assign({},obj);
}
動作効果は前の方法と基本的に一致しており、MDNによると、Object.assign()方法は、エニュメレート・属性のすべての値を1つ以上のソースオブジェクトからターゲットオブジェクトにコピーするために使用され、少なくとも2つのパラメータを許可し、最初のパラメータはコピーの対象となり、方法が実行された後に戻り、残りのパラメータはコピー元として返される.前の2つの方法はいずれも浅いコピーです.オブジェクトや配列を含む対象に対して、どうやってコピーすればいいですか?私達の先生は次のような方法を提供しました.欠陥は後で話します.
>For...inは遍歴して再帰(深コピー):
function clone(obj) {
    var newobj = obj.constructor === Array ? [] : {};
    if (typeof obj !== "object") {
        return obj;
    } else {
        for (var i in obj) {
            newobj[i] = typeof obj[i] === "object" ? clone(obj[i]) : obj[i];
        }
    }
    return newobj;
}
前の文のテストデータも同じです.
var a = {a:1, b:2, c:3, d:[0, 1, 2]}
var b = clone(a);
console.log(b.d[1]); //1
b.d[1] = 2;
console.log(b.d[1]); //2
console.log(a.d[1]); //1
この方法はオブジェクトを正確に深くコピーし、パラメータの種類に応じて配列またはオブジェクトを判断して別々に処理することができるが、この方法には一定の欠陥がある.
1,Symbolタイプの属性keyが存在する場合、正確にコピーできないので、以下のテストデータを試してもいいです.
var sym = Symbol();
var a = {a:1, b:2, c:3, d:[0, 1, 2], [sym]:"symValue"}
var b = clone(a);
b.d[1] = 2;
console.log(b.d[1]); //2
console.log(a.d[1]); //1
console.log(a[sym]); //"symValue"
console.log(b[sym]); //undefined
コピーされたオブジェクトbは、Symbolタイプのオブジェクトが属性名である属性が存在しないことがわかる.では、問題は主にFor...in遍歴属性でSymbolタイプKeyを獲得できないということですが、これらを遍歴する方法はありますか?ES 6においてReflectが含む静的方法ownKeys()はこれらのkeyを取得することができ、MDNによると、この方法で得られた戻り値はObject.getOwnPropertyNames.concat(Object.getOwn ProtySymbors)に相当する.
ES 6を使って、分配値とReflect.ownKeys()を組み合わせて使用して、上記の関数を書き換えます.
>ES 6解凍値&Reflect.ownKeys()遍歴して再帰(深度コピー):
function clone(obj) {
    var newobj = obj.constructor === Array ? [...obj] : {...obj};
    if (typeof obj !== "object") {
        return obj;
    } else {
        Reflect.ownKeys(newobj).forEach(i => {
            newobj[i] = typeof obj[i] === "object" ? clone(obj[i]) : obj[i];
        });
    }
    return newobj;
}
同じ試験文を実行します.
var sym = Symbol();
var a = {a:1, b:2, c:3, d:[0, 1, 2], [sym]:"symValue"}
var b = clone(a);
b.d[1] = 2;
console.log(b.d[1]); //2
console.log(a.d[1]); //1
console.log(a[sym]); //"symValue"
console.log(b[sym]); //"symValue"
b[sym] = "newValue";
console.log(a[sym]); //"symValue"
console.log(b[sym]); //"newValue"
Symbolタイプのkeyも正確にコピーされ、割り当てられていることが分かりますが、この方法にはまだ一定の問題があります.
2,リングがオブジェクト内部に存在する場合、スタックがオーバーフローし、次のテスト文を実行してみます.
var a = { info: "a", arr: [0, 1, 2] };
var b = { data: a, info: "b", arr: [3, 4, 5] };
a.data = b;
var c = clone(a); //Error: Maximum call stack size exceeded.   :    
これを解決する方法は後ほど説明しますが、既存の2つの深コピー方法は十分に日常的に使用されています.次に、ES 5.1に含まれるJSONオブジェクトは、オブジェクトを深くコピーすることもできます.遭遇する問題と最初の深コピー方式は同じです.Symbolは属性名の属性として記録できません.また、JSON文字列で表すことができるデータタイプのみが含まれています.コードは以下の通りです.
>JSONオブジェクトの変換(深コピー):
function clone(obj) {
    return JSON.parse(JSON.stringify(obj);
}
JSON.strigify()は、まず対象を文字列に並べ、JSON.parse()の逆順序を対象に新たなオブジェクトを形成します.前に述べた問題2に戻ります.対象内にリングが含まれている場合、どうすればいいですか?二つのオブジェクトをHashMapに似たようなものとして使用し、ソースオブジェクトの構造を記録し、各フロアを巡回する前に対象物がコピーされているかどうかを確認します.もしそうであれば、コピーされたオブジェクトに再び指し、無限再帰を防止します.実現コードは以下の通りです.
>Mapを記録して再帰する(深コピー)
:
/**
 *    (  Symbol)
 * @param {Object} obj
 */
function clone(obj) {
    const map = {}; //   ,     
    const mapCopy = {}; //   ,      
    /**
     *  theThis   ,  e   key,     ,  false
     * @param {Object} e       
     * @param {Object} theThis        
     * @returns {symbol | boolean}
     */
    function indexOfFun(e, theThis) {
        let re = false;
        for (const key of Reflect.ownKeys(theThis)) {
            if (e === theThis[key]) {
                re = key;
                break;
            }
        }
        return re;
    }
    /**
     *  Map   ,  e   key
     * @param {Object} e 
     */
    const indexOfMap = e => indexOfFun(e, map);
    /**
     *  Map   obj          
     * @param {Object} obj        
     */
    function bindMap(obj) {
        map[Symbol()] = obj;
        Reflect.ownKeys(obj).forEach(key => {
            //      Object       
            if (typeof obj[key] === "object" && !indexOfMap(obj[key])) {
                bindMap(obj[key]); //      
            }
        });
    }
    bindMap(obj);
    /**
     *     
     * @param {Object} obj        
     */
    function copyObj(obj) {
        let re;//    
        if (Array.isArray(obj)) {
            re = [...obj]; // obj   
        } else {
            re = { ...obj }; // obj   
        }
        mapCopy[indexOfMap(obj)] = re; //        
        Reflect.ownKeys(re).forEach(key => { //       
            if (typeof re[key] === "object") { //      Object
                if (mapCopy[indexOfMap(re[key])]) { //         
                    re[key] = mapCopy[indexOfMap(re[key])]; //               
                } else {//         
                    re[key] = copyObj(re[key]); //      ,         
                }
            }
        });
        return re; //        
    }
    return copyObj(obj); //       
}
前のテスト文を実行します.
var a = { info: "a", arr: [0, 1, 2] };
var b = { data: a, info: "b", arr: [3, 4, 5] };
a.data = b;

var c = clone(a);
c.info = "c";
c.data.info = "d";
console.log(a.info); //"a"
console.log(a.data.info); //"b"
console.log(c.info); //"c"
console.log(c.data.info); //"d"
この関数を得ると、キャリングオブジェクトを正確にコピーすることができます.
以上の討論と研究が終わった後で、学友は私に1つの倉庫のlodashを推薦して、この倉庫の存在をテストしました.clone Deep()方法は、深いコピーがより完全かつ精緻であることを実現し、前文の問題はいずれもこの方法で発見されておらず、ここで一つの問題を提起している.
もしこの文章はあなたに対して何か助けがありますか?あるいは何かご意見や質問がありましたら、この文章の下で返答してください.皆様が討論に参加することを心から歓迎します.どうぞよろしくお願いします.本論文はもとのとおりであるhttps://www.bysb.net/3113.html