ホームページ > よくある問題 > ディープコピーの3つの実装方法とは何ですか?

ディープコピーの3つの実装方法とは何ですか?

coldplay.xixi
リリース: 2020-12-01 12:01:45
オリジナル
35421 人が閲覧しました

ディープ コピーの 3 つの実装方法は次のとおりです: 1. すべての階層属性を再帰的にコピーする; 2. JSON オブジェクトの解析と文字列化を使用する; 3. JQ の extend メソッドを借用する。

ディープコピーの3つの実装方法とは何ですか?

ディープ コピーの 3 つの実装方法は次のとおりです:

1、すべての階層属性を再帰的にコピーします

function deepClone(obj){
    let objClone = Array.isArray(obj)?[]:{};    if(obj && typeof obj==="object"){        for(key in obj){            if(obj.hasOwnProperty(key)){                //判断ojb子元素是否为对象,如果是,递归复制
                if(obj[key]&&typeof obj[key] ==="object"){
                    objClone[key] = deepClone(obj[key]);
                }else{                    //如果不是,简单复制
                    objClone[key] = obj[key];
                }
            }
        }
    }    return objClone;
}    
let a=[1,2,3,4],
    b=deepClone(a);
a[0]=2;
console.log(a,b);
ログイン後にコピー

前に想像したように、b は a の制御から外れ、a の影響を受けなくなります。

ディープ コピーとは、オブジェクトの各レベルの属性をコピーすることであることを再度強調します。例をご覧ください。 JQ にはオブジェクトをコピーできる extend メソッドもあります。見てみましょう。

let a=[1,2,3,4],
    b=a.slice();
a[0]=2;
console.log(a,b);
ログイン後にコピー

ということは、スライス メソッドもディープ コピーということですか? 結局のところ、 b は a. の影響を受けません。前述のように、ディープ コピーはすべてのレベルの属性をコピーしますか? この例では、 a

let a=[0,1,[2,3],4],
        b=a.slice();
a[0]=1;
a[2][0]=1;
console.log(a,b);
ログイン後にコピー

を変更します。コピーは完了していません。および b オブジェクトの 1 つ。第 1 レベルの属性は確かに影響を受けませんが、第 2 レベルの属性は依然として正常にコピーできず、依然として a の制御から逃れることができません。これは、スライスがまったく真のディープ コピーではないことを示しています。

これは Zhihu Q&A から引用した図です

最初の層の属性は実際に深くコピーされ、独立したメモリを持っていますが、より深い属性はアドレスは依然として共有されているため、上記の問題が発生します。

concat メソッドやスライスも同様に、真のディープコピーではないので注意が必要です。

2. 再帰に加えて、JSON オブジェクトの解析と文字列化も借用できます

function deepClone(obj){
    let _obj = JSON.stringify(obj),
        objClone = JSON.parse(_obj);
    return objClone
}    
let a=[0,1,[2,3],4],
    b=deepClone(a);
a[0]=1;
a[2][0]=1;
console.log(a,b);
ログイン後にコピー

ご覧のとおり、ここでは b a の影響はまったく受けません。

ちなみに、ディープ コピーの実装に加えて、JSON.stringify と JSON.parse を localStorage と組み合わせてオブジェクト配列ストレージを実装することもできます。興味があれば、ブロガーによるこの記事を読んでください。

localStorage には配列、オブジェクト、localStorage、sessionStorage には配列オブジェクトが格納されます

3. 上記 2 つのメソッドに加えて、JQ の extend メソッドを借用することもできます。

$.extend( [ディープ ], ターゲット, オブジェクト 1 [, オブジェクト N ] )

  • deep は深くコピーするかどうかを示します。true の場合は深いコピー、false の場合は浅いコピーです。

  • オブジェクト

    ターゲット オブジェクトを入力すると、他のオブジェクトのメンバー プロパティがこのオブジェクトにアタッチされます。 #object1 objectN

  • オプション。オブジェクト タイプ 最初と N 番目のマージされたオブジェクト。
  • let a=[0,1,[2,3],4],
        b=$.extend(true,[],a);
    a[0]=1;
    a[2][0]=1;
    console.log(a,b);
    ログイン後にコピー
    効果は上記の方法と同じですが、JQ ライブラリに依存するだけであることがわかります。

ここまで述べましたが、ディープコピーを理解することは、面接での質問に対処するためだけでなく、実際の開発にも非常に役立ちます。たとえば、バックグラウンドで大量のデータが返され、このデータ群に対して操作を実行する必要がありますが、複数人で開発する場合、このデータ群に他に必要な機能があるかどうかはわかりません。直接変更すると隠れた問題が発生する可能性がある コピーすることでより安心・安全にデータを運用できる ディープコピーを実態に合わせて使いましょう ということなのでしょう。

4.lodash の _.cloneDeep()

以下は、私が読んだディープ コピーの問題の解決策です。 JSON.parse

まず、オブジェクトを json オブジェクトに変換します。次に、この json オブジェクトを解析します。

let obj = {a:{b:22}};let copy = JSON.parse(JSON.stringify(obj));
ログイン後にコピー

この方法の利点は、コードの作成が比較的簡単であることです。しかし、欠点も明らかです。まず一時的な、場合によっては大きな文字列を作成し、それをパーサーに戻します。もう 1 つの欠点は、このメソッドでは循環オブジェクトを処理できないことです。 このメソッドを使用すると、次のループ オブジェクトは例外をスローします

let a = {};let b = {a};a.b = b;let copy = JSON.parse(JSON.stringify(a));
ログイン後にコピー

##Map、Set、RegExp、Date、ArrayBuffer、その他の組み込み型など連載中に失われます。

let a = {};let b = new Set();b.add(11);a.test = b;let copy = JSON.parse(JSON.stringify(a));
ログイン後にコピー

a の値は次のように出力されます

ディープコピーの3つの実装方法とは何ですか?

copy的值打印如下

ディープコピーの3つの実装方法とは何ですか?

对比发现,Set已丢失。

Structured Clone 结构化克隆算法

MessageChannel

建立两个端,一个端发送消息,另一个端接收消息。

function structuralClone(obj) {    return new Promise(resolve =>{
        const {port1, port2} = new MessageChannel();
        port2.onmessage = ev => resolve(ev.data);
        port1.postMessage(obj);
    })
}
const obj = /* ... */;
structuralClone(obj).then(res=>{
     console.log(res);
})
ログイン後にコピー

这种方法的优点就是能解决循环引用的问题,还支持大量的内置数据类型。缺点就是这个方法是异步的。

History API

利用history.replaceState。这个api在做单页面应用的路由时可以做无刷新的改变url。这个对象使用结构化克隆,而且是同步的。但是我们需要注意,在单页面中不要把原有的路由逻辑搞乱了。所以我们在克隆完一个对象的时候,要恢复路由的原状。

function structuralClone(obj) {
  const oldState = history.state;
  history.replaceState(obj, document.title);
  const copy = history.state;
  history.replaceState(oldState, document.title);  return copy;
}var obj = {};var b = {obj};
obj.b = bvar copy = structuralClone(obj); 
console.log(copy);
ログイン後にコピー

这个方法的优点是。能解决循环对象的问题,也支持许多内置类型的克隆。并且是同步的。但是缺点就是有的浏览器对调用频率有限制。比如Safari 30 秒内只允许调用 100 次

Notification API

这个api主要是用于桌面通知的。如果你使用Facebook的时候,你肯定会发现时常在浏览器的右下角有一个弹窗,对就是这家伙。我们也可以利用这个api实现js对象的深拷贝。

function structuralClone(obj) {  return new Notification('', {data: obj, silent: true}).data;
}var obj = {};var b = {obj};
obj.b = bvar copy = structuralClone(obj);
console.log(copy)
ログイン後にコピー

同样是优点和缺点并存,优点就是可以解决循环对象问题,也支持许多内置类型的克隆,并且是同步的。缺点就是这个需要api的使用需要向用户请求权限,但是用在这里克隆数据的时候,不经用户授权也可以使用。在http协议的情况下会提示你再https的场景下使用。

lodash的_.cloneDeep()

支持循环对象,和大量的内置类型,对很多细节都处理的比较不错。推荐使用。

支持的类型有很多

ディープコピーの3つの実装方法とは何ですか?

我们这里再次关注一下lodash是如何解决循环应用这个问题的?

ディープコピーの3つの実装方法とは何ですか?

从相关的代码中。我们可以发现。lodash是用一个栈记录了。所有被拷贝的引用值。如果再次碰到同样的引用值的时候,不会再去拷贝一遍。而是利用之前已经拷贝好的值。

实现一个简易点的深拷贝,以解决循环引用的问题为目标

我们仅仅实现一个简易点的深拷贝。能优雅的处理循环引用的即可。在实现深拷贝之前,我们首先温习回顾一下js中的遍历对象的属性的方法和各种方法的优缺点。

js中遍历一个对象的属性的方法

  • Object.keys() 仅仅返回自身的可枚举属性,不包括继承来的,更不包括Symbol属性
  • Object.getOwnPropertyNames() 返回自身的可枚举和不可枚举属性。但是不包括Symbol属性
  • Object.getOwnPropertySymbols() 返回自身的Symol属性
  • for...in 可以遍历对象的自身的和继承的可枚举属性,不包含Symbol属性
  • Reflect.ownkeys() 返回对象自身的所有属性,不管是否可枚举,也不管是否是Symbol。注意不包括继承的属性

实现深拷贝,解决循环引用问题

/**
 * 判断是否是基本数据类型
 * @param value 
 */function isPrimitive(value){  return (typeof value === 'string' || 
  typeof value === 'number' || 
  typeof value === 'symbol' ||  typeof value === 'boolean')
}/**
 * 判断是否是一个js对象
 * @param value 
 */function isObject(value){  return Object.prototype.toString.call(value) === "[object Object]"}/**
 * 深拷贝一个值
 * @param value 
 */function cloneDeep(value){  // 记录被拷贝的值,避免循环引用的出现
  let memo = {};  function baseClone(value){
    let res;    // 如果是基本数据类型,则直接返回
    if(isPrimitive(value)){      return value;    // 如果是引用数据类型,我们浅拷贝一个新值来代替原来的值
    }else if(Array.isArray(value)){
      res = [...value];
    }else if(isObject(value)){
      res = {...value};
    }    // 检测我们浅拷贝的这个对象的属性值有没有是引用数据类型。如果是,则递归拷贝
    Reflect.ownKeys(res).forEach(key=>{      if(typeof res[key] === "object" && res[key]!== null){        //此处我们用memo来记录已经被拷贝过的引用地址。以此来解决循环引用的问题
        if(memo[res[key]]){
          res[key] = memo[res[key]];
        }else{
          memo[res[key]] = res[key];
          res[key] = baseClone(res[key])
        }
      }
    })    return res;  
  }  return baseClone(value)
}
ログイン後にコピー

验证我们写的cloneDeep是否能解决循环应用的问题

var obj = {};var b = {obj};
obj.b = bvar copy = cloneDeep(obj); 
console.log(copy);
ログイン後にコピー

以上がディープコピーの3つの実装方法とは何ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート