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


本論文は私の個人ブログに同期して発表します.牛とモンキー.
個人ブログのコメントシステムにメールのお知らせがあります.作者と話したいことがありますか?個人ブログにコメントしてください.
後に変化があれば、個人ブログだけで更新します.ここでは修正しないようにしてください.
深くコピーしたり、浅いコピーしたりするのもjsによくある問題です.もう一度まとめて振り返ってみます.ついでに、jsのデータの種類と保存方法を復習しました.
JavaScriptのデータタイプ
最新のECMAScript標準は、7種類のオリジナルタイプとObjectを含む8種類のデータタイプを定義しています.
オリジナルタイプ(prmitive values)
jsではObject以外のすべてのタイプは可変ではなく、これらのタイプの値は と呼ばれています.jsのオリジナルタイプは以下の7種類があります.
  • Boolean
  • Null
  • Udefined
  • Number
  • BigInt(ES 10追加)
  • String
  • Symbol(ES 6追加)
  • どういう意味ですか?何が変わっていませんか
    私達はjsの中で基本的なデータのタイプのいかなる操作に対しても、元の値に影響しないで、新しい値を返します.以下の例を示します
    let str = 'nba';
    str[1] = 'c';
    console.log(str); //    nba
    上記のコードを実行すると、出力結果は依然としてnbaではなく、 です.
    基本的なデータタイプはスタックメモリに格納された簡単なデータセグメントで、データサイズは決定され、メモリの空間サイズは割り当てられ、直接に値によって保存されるので、直接にアクセスすることができます.
    オブジェクト
    コンピュータ科学では、オブジェクトはメモリ内の識別子で参照できるブロック領域を指す.Javascriptでは、オブジェクトは属性のセットとして見られます.
    jsのオブジェクトはスタックメモリに保存されています.オブジェクトを定義すると、変数は実際にスタックメモリに保存されているポインタです.
    jsの中で対象の話題は1冊の本を書くことができて、ここはもうくどくど述べません.
    浅いコピー
    定義
    新しいオブジェクトを作成します.このオブジェクトはオリジナルのオブジェクト属性値の正確なコピーを持っています.属性が基本タイプであれば、コピーは基本タイプの値であり、属性が参照タイプであれば、コピーはメモリアドレスであるため、オブジェクトの一つがこのアドレスを変更したら、他のオブジェクトに影響を与えます.
    実現する
    ES 5バージョン
    ここでは簡単な実装が提供されます.
    /**
     *      
     * @param {Object} source       
     */
    function copyObj(source) {
        //                       
        let result = Object.prototype.toString.call(source) === '[object Array]' ? [] : {};
    
        for (let key in source) {
            // for in          ,              ,   hasOwnProperty   
            if (source.hasOwnProperty(key)) {
                result[key] = source[key];
            }
        }
        return result;
    }
    多くのプロジェクトでもこのように使われていますが、ES 6時代には本当にOKでしたか?
    すべてはES 6のSymbolタイプから話さなければなりません.ES 6において、対象の属性名は文字列またはSymbolタイプとすることができ、Symbolタイプの特徴はユニークであり、他の属性と重複しないようにすることである.しかし、それに伴う問題は:
    Symbolは属性名として、この属性はfor...in、for...ofサイクルには現れません.Object.keys()、Object.getOwnPropertyNames()、JSON.strigify()には返されません.
    ES 6バージョン
    Object.assign()と展開演算子
    これらの2つの方法はいずれも上記のSymbol の問題を解決することができる.Object.assign()方法は、エニュメレート・属性のすべての値を1つまたは複数のソースオブジェクトから対象オブジェクトにコピーするために使用される.ターゲットオブジェクトに戻ります.
    let source = { a: 1, b: {c: 2} };
    
    let result = Object.assign({}, source);
    
    console.log(result);
    演算子を展開することで、浅いコピーも可能です.
    let source = { a: 1, b: {c: 2} };
    
    let result = {...source};
    
    console.log(result);
    Object.keys()
    Object.keys()メソッドは、与えられたオブジェクトの自身のエニュメレート・属性からなる行列を返します.配列内の属性名の配列順と、for...inを使ってオブジェクトを巡回したときに返される順序は一致します.
    上のES 5においてfor…inサイクルを通過する方法に比べて、この方法はhasOwnPropertyの判断を回避することができる.ES 5を浅いコピーオブジェクトにする方法で、オブジェクトを遍歴してObject.keysで実現すればいいです.
    属性を列挙できない問題
    なんですか?まだ問題がありますか?はい、まだ問題があります.何の問題ですか 私たちは、jsのオブジェクトの属性に というマークがあることを知っています.通常、その値はtrueです.しかし、Object.definePropertyによって定義されるオブジェクト属性の場合、このフラグのデフォルト値はfalseである.
    ほとんどの場合、オブジェクトを巡回するには、エニュメレート・プロパティーを処理する必要はありません.特別な需要があったらどうすればいいですか?
  • Object.getOwnPropertyNames()は、指定されたオブジェクトのすべての自身の属性の属性名(エニュメレート・属性を含むが、名前の属性としてSymbol値を含まない)からなる行列を返します.
  • Object.getOwnPropertySymbols()方法は、所与のオブジェクト自体のすべてのSymbol属性の配列を返す.
  • この2つの方法を組み合わせて、1つのオブジェクト自体がSymbol属性を含む、エニュメレート・エニュメレート・エニュメレート・エニュメレート・プロパティーを得ることができる.この2つの方法を除いて:
  • 静的方法Reflect.ownKeys()は、ターゲットオブジェクト自体の属性キーからなる配列を返す.
  • コピー
    定義
    一つのオブジェクトをメモリから完全にコピーし、新しいエリアを開いて新しいオブジェクトを保存し、新しいオブジェクトを変更すると元のオブジェクトに影響しません.
    実現する
    破産版
    let source = { a: 1, b: {c: 2} };
    
    let result = JSON.parse(JSON.stringify());
    
    console.log(result);
    破産版のコピーが深くても使えないわけではないですが、いくつかの特別な場合には足りないです.例えば:
  • ソースオブジェクトに時間オブジェクトがある場合、時間オブジェクト
  • ではなく、逆順序化された文字列が得られる.
  • ソースオブジェクトにRegExp、Errerオブジェクトがある場合、プログレッシブの結果は、空のオブジェクト
  • である.
  • ソースオブジェクトに関数、undefinedがあると、プログレッシブ時にそれらを捨てる
  • .
  • ソースオブジェクトにNaN、Infinity、Infinityがあれば、プログレッシブの結果はnull
  • になります.
  • JSON.strigify()は、列化対象の列挙可能な自有属性
  • のみを有する.
  • オブジェクトに循環参照がある場合も、深度コピー
  • を正確に実現することができません.
    パーフェクト版
    以下のコードはContardLi.githb.io/deep Clon/src/clone_6.jsから来ています.コードを読むのはclone方法から直接できます.コードに注釈をつけました.
    
    //       toString                  
    
    //          
    const mapTag = '[object Map]';
    const setTag = '[object Set]';
    const arrayTag = '[object Array]';
    const objectTag = '[object Object]';
    const argsTag = '[object Arguments]';
    
    //          
    const boolTag = '[object Boolean]';
    const dateTag = '[object Date]';
    const numberTag = '[object Number]';
    const stringTag = '[object String]';
    const symbolTag = '[object Symbol]';
    const errorTag = '[object Error]';
    const regexpTag = '[object RegExp]';
    const funcTag = '[object Function]';
    
    const deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag];
    
    //         forEach  
    function forEach(array, iteratee) {
        let index = -1;
        const length = array.length;
        while (++index < length) {
            iteratee(array[index], index);
        }
        return array;
    }
    
    //      
    function isObject(target) {
        const type = typeof target;
        return target !== null && (type === 'object' || type === 'function');
    }
    
    //     
    function getType(target) {
        return Object.prototype.toString.call(target);
    }
    
    //          -       constructor       
    function getInit(target) {
        const Ctor = target.constructor;
        return new Ctor();
    }
    
    //   Symbol  
    function cloneSymbol(targe) {
        return Object(Symbol.prototype.valueOf.call(targe));
    }
    
    //       
    function cloneReg(targe) {
        const reFlags = /\w*$/;
        const result = new targe.constructor(targe.source, reFlags.exec(targe));
        result.lastIndex = targe.lastIndex;
        return result;
    }
    
    //       
    function cloneFunction(func) {
        const bodyReg = /(?<={)(.|
    )+(?=})/m; const paramReg = /(?<=\().+(?=\)\s+{)/; const funcString = func.toString(); // prototype if (func.prototype) { const param = paramReg.exec(funcString); const body = bodyReg.exec(funcString); if (body) { if (param) { const paramArr = param[0].split(','); return new Function(...paramArr, body[0]); } else { return new Function(body[0]); } } else { return null; } } else { return eval(funcString); } } function cloneOtherType(targe, type) { const Ctor = targe.constructor; switch (type) { case boolTag: case numberTag: case stringTag: case errorTag: case dateTag: return new Ctor(targe); case regexpTag: return cloneReg(targe); case symbolTag: return cloneSymbol(targe); case funcTag: return cloneFunction(targe); default: return null; } } function clone(target, map = new WeakMap()) { // if (!isObject(target)) { return target; } // , const type = getType(target); let cloneTarget; if (deepTag.includes(type)) { cloneTarget = getInit(target, type); } else { return cloneOtherType(target, type); } // if (map.get(target)) { return map.get(target); } map.set(target, cloneTarget); // set if (type === setTag) { target.forEach(value => { cloneTarget.add(clone(value, map)); }); return cloneTarget; } // map if (type === mapTag) { target.forEach((value, key) => { cloneTarget.set(key, clone(value, map)); }); return cloneTarget; } // const keys = type === arrayTag ? undefined : Object.keys(target); forEach(keys || target, (value, key) => { if (keys) { key = value; } cloneTarget[key] = clone(target[key], map); }); return cloneTarget; }
    後ろに書いたら
    コピー対象は様々な方法がありますが、実際の業務ではビジネスシーンによって最適なものを選択します.例えばオブジェクトが1つの層だけで、かつkeyとvalueが単純な文字列である場合、本明細書で述べた多くの方法は有効である.もしあなたのシーンが複雑な相手を深くコピーするなら、よく考えてみます.
    本文で述べたように深くコピーしてもっと深く研究すれば、探究すべき問題がたくさん生まれます.例えば:
  • オブジェクトタイプ判断
  • クローン関数
  • どのように正則をクローンしますか?
    -…後は時間がありますので、もう少し深く研究してみましょう.
    参考資料
  • JavaScriptデータタイプとデータ構造
  • あなたが知らないjsメモリとスタックメモリ
  • ES 6時代、本当に対象をクローンできましたか?
  • JSON.parse(JSON.strigify(obj)について、注意すべき穴を深くコピーすることを実現します。
  • どのように1つの驚艶な面接官の深くコピーを書き出しますか?