JavaScriptは深くコピーした穴があります.

4998 ワード

前言
ある会社に面接に行った時、面接官が私に質問しました.当時私は心の中で密かに喜んでいましたが、こんな簡単な問題を考える必要がありますか?「普段よく使われているのは2つの方法があります.最初はJSON.parse(JSON.stingify(obj)を使って、2番目はfor…inを使って再帰的に完成します.」面接官はそれを聞いてうなずいて満足しています.その時は私もあまりこの問題を気にしていませんでした.この間までまたこの問題を思い出しました.上記の二つの方法は全部Bugがあります.
質問を発する
上記のBugは何ですか?
  • 特殊対象コピー
  • まずこのような相手がいると考えてみましょう.普通のタイプを考えないで、次のようなメンバーがいます.
    
    const obj = {
        arr: [111, 222],
        obj: {key: '  '},
        a: () => {console.log('  ')},
        date: new Date(),
        reg: /  /ig
    }
    
    
    私たちは上の二つの方法でそれぞれ一回コピーします.
    JSON法
    
    JSON.parse(JSON.stringify(obj))
    
    
    出力結果:objの中の一般的なオブジェクトと配列はコピーできますが、dateのオブジェクトは文字列となり、関数は直接なくなり、正則は空のオブジェクトとなります.
    また、for...inの再帰方法を参照してください.
    再帰する
    
    function isObj(obj) {
        return (typeof obj === 'object' || typeof obj === 'function') && obj !== null
    }
    function deepCopy(obj) {
        let tempObj = Array.isArray(obj) ? [] : {}
        for(let key in obj) {
            tempObj[key] = isObj(obj[key]) ? deepCopy(obj[key]) : obj[key]
        }
        return tempObj
    }
    
    
    結果:
    結論
    上記のテストにより、これらの2つの方法はいずれも関数をコピーできないことが分かりました.dateregタイプのオブジェクトです.
  • リング
  • 環とは何ですか
    環は対象の循環参照であり、自分が閉ループになることをもたらします.例えば、次のようなオブジェクトです.
    
    var a = {}
    
    a.a = a
    
    
    上の二つの方法を使ってコピーしたら、直接間違えます.
    ソリューション
  • リング
  • 1つのWeakMapを使用して、コピーされたオブジェクトを構造的に記憶してもよく、コピーするたびに、そのオブジェクトがコピーされているかどうかはWeakMapに調べておき、コピーされているなら、そのオブジェクトを取り出して返し、deepCopy関数を以下のように変換しても良い.
    
    function deepCopy(obj, hash = new WeakMap()) {
        if(hash.has(obj)) return hash.get(obj)
        let cloneObj = Array.isArray(obj) ? [] : {}
        hash.set(obj, cloneObj)
        for (let key in obj) {
            cloneObj[key] = isObj(obj[key]) ? deepCopy(obj[key], hash) : obj[key];
        }
        return cloneObj
    }
    
    
    コピーリングの結果:
  • 特殊対象のコピー
  • この問題の解決は面倒くさいです.特別に扱うべき対象の種類が多すぎるので、MDN上の構造コピーを参考にして、解決ループの方案を結び付けました.
    
    //    date,reg  ,         
    
    function deepCopy(obj, hash = new WeakMap()) {
        let cloneObj
        let Constructor = obj.constructor
        switch(Constructor){
            case RegExp:
                cloneObj = new Constructor(obj)
                break
            case Date:
                cloneObj = new Constructor(obj.getTime())
                break
            default:
                if(hash.has(obj)) return hash.get(obj)
                cloneObj = new Constructor()
                hash.set(obj, cloneObj)
        }
        for (let key in obj) {
            cloneObj[key] = isObj(obj[key]) ? deepCopy(obj[key], hash) : obj[key];
        }
        return cloneObj
    }
    
    
    コピー結果:
    完全版はlodashの深コピーを見ることができます.
  • 関数のコピー
  • MDN上の構造化コピーはまだ解決関数のコピーがありません.
    今までは、関数をevalの方法でコピーしたいだけですが、この方法は矢印関数のみ有効です.fun(){}のような形であれば、エラーが発生します.
    コピー関数の増加関数の種類
    結果をコピー
    エラーの種類
    後記
    JavaScriptの深度コピーは上記のピットだけではなく、プロトタイプチェーン上の属性をどうやってコピーしますか?どのようにコピーしたら列挙できない属性ですか?Errorオブジェクトなどのピットをコピーする方法は、ここではいちいち説明しない.
    しかし、日常的にはJSON法を使うことを提案しています.この方法はほとんどの業務需要をカバーしていますので、簡単なことを複雑にする必要はありません.しかし、面接で面接官に会ったら、この問題に対する答えは絶対に彼の顔を見せられます.