JavaScript の浅いコピーと深いコピーについての深い理解

零到壹度
リリース: 2018-04-21 15:32:17
オリジナル
1151 人が閲覧しました

この記事の内容は、JavaScript の浅いコピーと深いコピーについての詳細な理解に関するもので、必要な友人に参考にしていただけるように共有します。いくつかの基本的なタイプがあります。 JS では、NumberStringBoolean であり、オブジェクトは次のようなものです { name: 'Larry'、skill: 'Node .js' } の場合、オブジェクトと基本型の最大の違いは、値を渡すメソッドにあります。

基本的な型は、次のように値によって渡されます: a を変更しても、b には変更されません

var a = 25;
var b = a;
b = 18;
console.log(a);//25
console.log(b);//18
ログイン後にコピー
NumberStringBoolean,而对象就是像这样的东西{ name: 'Larry', skill: 'Node.js' },对象跟基本类型最大的不同就在于他们的传值方式。

基本类型是按值传递,像是这样:在修改a时并不会改到b

var obj1 = { a: 10, b: 20, c: 30 };
var obj2 = obj1;
obj2.b = 100;
console.log(obj1);
// { a: 10, b: 100, c: 30 } <-- b 被改到了
console.log(obj2);
// { a: 10, b: 100, c: 30 }
ログイン後にコピー

但对象就不同,对象传的是按引用传值:

var obj1 = { a: 10, b: 20, c: 30 };
var obj2 = { a: obj1.a, b: obj1.b, c: obj1.c };
obj2.b = 100;
console.log(obj1);
// { a: 10, b: 20, c: 30 } <-- b 沒被改到
console.log(obj2);
// { a: 10, b: 100, c: 30 }
ログイン後にコピー

复制一份obj1叫做obj2,然后把obj2.b改成100,但却不小心改到obj1.b,因为他们根本是同一个对象,这就是所谓的浅拷贝。

要避免这样的错误发生就要写成这样:

  <script type="text/javascript">
    function simpleClone(initalObj) {    
      var obj = {};    
      for ( var i in initalObj) {
        obj[i] = initalObj[i];
      }    
      return obj;
    }    var obj = {
      a: "hello",
      b:{
          a: "world",
          b: 21
        },
      c:["Bob", "Tom", "Jenny"],
      d:function() {
          alert("hello world");
        }
    }    var cloneObj = simpleClone(obj); 
    console.log(cloneObj.b); 
    console.log(cloneObj.c);
    console.log(cloneObj.d);

    cloneObj.b.a = "changed";
    cloneObj.c = [1, 2, 3];
    cloneObj.d = function() { alert("changed"); };
    console.log(obj.b);
    console.log(obj.c);
    console.log(obj.d);  </script>
ログイン後にコピー


这样就是深拷贝,不会改到原本的obj1。

浅拷贝(Shallow Copy) VS 深拷贝(Deep Copy)

浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

浅拷贝的实现方式

也就是简单地复制而已

1、简单地复制语句

Object.assign(target, ...sources)
ログイン後にコピー


结果为:

2、Object.assign()

<code>Object.assign是ES6的新函数。Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 <code>Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。

var obj = { a: {a: "hello", b: 21} };
var initalObj = Object.assign({}, obj);

initalObj.a.a = "changed";
console.log(obj.a.a); // "changed"
ログイン後にコピー

参数:

target:目标对象。
sources:任意多个源对象。
返回值:目标对象会被返回。

Object.assign()可以处理一层的深度拷贝,如下:
ログイン後にコピー

兼容性:

需要注意的是:

var obj1 = { a: 10, b: 20, c: 30 };
var obj2 = Object.assign({}, obj1);
obj2.b = 100;
console.log(obj1);
// { a: 10, b: 20, c: 30 } <-- 沒被改到
console.log(obj2);
// { a: 10, b: 100, c: 30 }
ログイン後にコピー
var obj1 = { a: 10, b: 20, c: 30 };
var obj2 = { a: obj1.a, b: obj1.b, c: obj1.c };
obj2.b = 100;
console.log(obj1);
// { a: 10, b: 20, c: 30 } <-- 沒被改到
console.log(obj2);
// { a: 10, b: 100, c: 30 }
ログイン後にコピー


深拷贝的实现方式

要完全复制又不能修改到原对象,这时候就要用 Deep Copy,这里会介绍几种Deep Copy 的方式。

1、手动复制

把一个对象的属性复制给另一个对象的属性

var obj1 = { body: { a: 10 } };
var obj2 = { body: obj1.body };
obj2.body.a = 20;
console.log(obj1);
// { body: { a: 20 } } <-- 被改到了
console.log(obj2);
// { body: { a: 20 } }
console.log(obj1 === obj2);
// false
console.log(obj1.body === obj2.body);
// true
ログイン後にコピー

但这样很麻烦,要一个一个自己复制;而且这样的本质也不能算是 Deep Copy,因为对象里面也可能回事对象,如像下面这个状况:

var obj1 = { body: { a: 10 } };
var obj2 = JSON.parse(JSON.stringify(obj1));
obj2.body.a = 20;
console.log(obj1);
// { body: { a: 10 } } <-- 沒被改到
console.log(obj2);
// { body: { a: 20 } }
console.log(obj1 === obj2);
// false
console.log(obj1.body === obj2.body);
// false
ログイン後にコピー

虽然obj1obj2是不同对象,但他们会共享同一个obj1.body所以修改obj2.body.a时也会修改到旧的。

2、对象只有一层的话可以使用上面的:Object<span class="token punctuation">.<span class="token function">assign()函数</span></span>

<code>Object.assign({}, obj1)的意思是先建立一个空对象{},接着把obj1中所有的属性复制过去,所以obj2会长得跟obj1一样,这时候再修改obj2.b也不会影响obj1。

因为<code>Object.assign跟我们手动复制的效果相同,所以一样只能处理深度只有一层的对象,没办法做到真正的 Deep Copy。不过如果要复制的对象只有一层的话可以考虑使用它。

3、转成 JSON 再转回来

JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象。

var obj1 = { fun: function(){ console.log(123) } };
var obj2 = JSON.parse(JSON.stringify(obj1));
console.log(typeof obj1.fun);
// &#39;function&#39;
console.log(typeof obj2.fun);
// &#39;undefined&#39; <-- 没复制
ログイン後にコピー
ログイン後にコピー

这样做是真正的Deep Copy,这种方法简单易用。

但是这种方法也有不少坏处,譬如它会抛弃对象的constructor。也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object。

这种方法能正确处理的对象只有 Number, String, Boolean, Array, 扁平对象,即那些能够被 json 直接表示的数据结构。RegExp对象是无法通过这种方式深拷贝。

也就是说,只有可以转成JSON格式的对象才可以这样用,像function没办法转成JSON。しかし、オブジェクトは異なります。値渡し 値渡し:

function deepClone(initalObj, finalObj) {    
  var obj = finalObj || {};    
  for (var i in initalObj) {        
    if (typeof initalObj[i] === &#39;object&#39;) {
      obj[i] = (initalObj[i].constructor === Array) ? [] : {};            
      arguments.callee(initalObj[i], obj[i]);
    } else {
      obj[i] = initalObj[i];
    }
  }    
  return obj;
}var str = {};var obj = { a: {a: "hello", b: 21} };
deepClone(obj, str);
console.log(str.a);
ログイン後にコピー
ログイン後にコピー
🎜 obj1 のコピーをコピーして、それを obj2, と呼び、obj2.b に変更します。 >100, >しかし、基本的に同じオブジェクトであるため、誤って obj1.b, に変更されました。これは、いわゆる浅いコピーです。 🎜🎜そのようなエラーを回避するには、次のように記述します: 🎜
function deepClone(initalObj, finalObj) {    
  var obj = finalObj || {};    
  for (var i in initalObj) {        
    var prop = initalObj[i];        // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况
    if(prop === obj) {            
      continue;
    }        
    if (typeof prop === &#39;object&#39;) {
      obj[i] = (prop.constructor === Array) ? [] : {};            
      arguments.callee(prop, obj[i]);
    } else {
      obj[i] = prop;
    }
  }    
  return obj;
}var str = {};var obj = { a: {a: "hello", b: 21} };
deepClone(obj, str);
console.log(str.a);
ログイン後にコピー
ログイン後にコピー
🎜
🎜🎜これはディープコピーであり、元の obj1 には変更されません。 🎜

浅いコピー VS 深いコピー

🎜🎜🎜浅いコピーは、オブジェクト自体ではなく、オブジェクトへのポインターをコピーするだけです。古いオブジェクトと新しいオブジェクトは同じメモリを共有します。ただし、ディープ コピーでは同一のオブジェクトが作成されます。新しいオブジェクトは元のオブジェクトとメモリを共有しないため、新しいオブジェクトを変更しても元のオブジェクトは変更されません。 🎜

浅いコピーの実装方法

🎜つまり、単純にコピーします🎜🎜1. ステートメントを単純にコピーします🎜
function deepClone(initalObj, finalObj) {    
  var obj = finalObj || {};    
  for (var i in initalObj) {        
    var prop = initalObj[i];        // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况
    if(prop === obj) {            
      continue;
    }        
    if (typeof prop === &#39;object&#39;) {
      obj[i] = (prop.constructor === Array) ? [] : Object.create(prop);
    } else {
      obj[i] = prop;
    }
  }    
  return obj;
}
ログイン後にコピー
ログイン後にコピー
🎜
🎜🎜結果は次のようになります: 🎜🎜< img src="https://img.php.cn/upload/article/000/099/200/4ba857fabcd595bc7f4ced30f40d649c-1.png" alt=""/>🎜🎜2. Object.assign()🎜🎜<code>Object.assign は ES6 の新しい関数です。 Object.assign() メソッドは、ソース オブジェクト独自の列挙可能なプロパティを任意の数でターゲット オブジェクトにコピーし、ターゲット オブジェクトを返すことができます。ただし、<code>Object.assign() は浅いコピーを実行します。コピーされるのはオブジェクト自体ではなく、オブジェクトのプロパティへの参照です。 🎜
var $ = require(&#39;jquery&#39;);var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};var obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f);// false
ログイン後にコピー
ログイン後にコピー
🎜パラメータ: 🎜🎜target: ターゲットオブジェクト。
ソース: 任意の数のソース オブジェクト。
戻り値: 対象のオブジェクトが返されます。 🎜🎜
var _ = require(&#39;lodash&#39;);var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false
ログイン後にコピー
ログイン後にコピー
🎜互換性: 🎜🎜🎜🎜支払いが必要です注意 答えは次のとおりです: 🎜rrreeerrreee🎜
🎜

ディープコピーの実装方法

🎜 元のオブジェクトを変更せずに完全にコピーしたい場合は、ディープコピーを使用する必要があります。いくつかのディープコピー方法。 🎜🎜1. 手動コピー🎜🎜あるオブジェクトのプロパティを別のオブジェクトのプロパティにコピーします🎜rrreee🎜しかし、これは非常に面倒です。1つずつコピーする必要があり、これは不可能です。以下の状況のように、オブジェクト内にオブジェクトも存在する可能性があるため、ディープ コピーとみなされます: 🎜rrreee🎜 obj1obj2 は異なるオブジェクトですが、それらは共有されます同じ obj1.body したがって、obj2.body.a を変更すると、古いものも変更されます。 🎜🎜2. オブジェクトにレイヤーが 1 つしかない場合は、上記を使用できます: Object<span class="token punctuation">.<span class="token function" >assign() 関数 🎜🎜🎜🎜<code>Object.assign({}, obj1) は、最初に空のオブジェクトを作成することを意味します { obj1 のすべての属性をコピーして、obj2obj1 と同じになるようにします。次に、obj2.b< を変更します。 /code> は <code>obj1 にも影響しません。

<code>Object.assign は手動コピーと同じ効果があるため、深さ 1 層のみのオブジェクトしか処理できません。真のディープコピーを実現します。ただし、コピーするオブジェクトのレイヤーが 1 つしかない場合は、これを使用することを検討できます。 🎜🎜🎜3. 🎜 を JSON に変換して元に戻す🎜🎜JSON.stringify 変換を使用します。オブジェクトを文字列に変換し、JSON.parse を使用して文字列を新しいオブジェクトに変換します。 🎜🎜rrreee🎜これは本物のディープ コピーです。この方法はシンプルで使いやすいです。 🎜
🎜🎜しかし、このメソッドには、オブジェクトのコンストラクターが破棄されるなど、多くの欠点もあります。つまり、ディープ コピー後は、オブジェクトの元のコンストラクターが何であっても、ディープ コピー後はオブジェクトになります。 🎜🎜このメソッドが正しく処理できる唯一のオブジェクトは、数値、文字列、ブール値、配列、フラット オブジェクト、つまり、json で直接表現できるデータ構造です。 RegExp オブジェクトをこの方法でディープ コピーすることはできません。 🎜🎜 つまり、この方法で使用できるのは JSON 形式に変換できるオブジェクトのみです。たとえば、functionJSON に変換できません。 🎜
var obj1 = { fun: function(){ console.log(123) } };
var obj2 = JSON.parse(JSON.stringify(obj1));
console.log(typeof obj1.fun);
// &#39;function&#39;
console.log(typeof obj2.fun);
// &#39;undefined&#39; <-- 没复制
ログイン後にコピー
ログイン後にコピー

要复制的function会直接消失,所以这个方法只能用在单纯只有数据的对象。

4、递归拷贝

function deepClone(initalObj, finalObj) {    
  var obj = finalObj || {};    
  for (var i in initalObj) {        
    if (typeof initalObj[i] === &#39;object&#39;) {
      obj[i] = (initalObj[i].constructor === Array) ? [] : {};            
      arguments.callee(initalObj[i], obj[i]);
    } else {
      obj[i] = initalObj[i];
    }
  }    
  return obj;
}var str = {};var obj = { a: {a: "hello", b: 21} };
deepClone(obj, str);
console.log(str.a);
ログイン後にコピー
ログイン後にコピー

上述代码确实可以实现深拷贝。但是当遇到两个互相引用的对象,会出现死循环的情况。

为了避免相互引用的对象导致死循环的情况,则应该在遍历的时候判断是否相互引用对象,如果是则退出循环。

改进版代码如下:

function deepClone(initalObj, finalObj) {    
  var obj = finalObj || {};    
  for (var i in initalObj) {        
    var prop = initalObj[i];        // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况
    if(prop === obj) {            
      continue;
    }        
    if (typeof prop === &#39;object&#39;) {
      obj[i] = (prop.constructor === Array) ? [] : {};            
      arguments.callee(prop, obj[i]);
    } else {
      obj[i] = prop;
    }
  }    
  return obj;
}var str = {};var obj = { a: {a: "hello", b: 21} };
deepClone(obj, str);
console.log(str.a);
ログイン後にコピー
ログイン後にコピー


5、使用Object.create()方法

直接使用var newObj = Object.create(oldObj),可以达到深拷贝的效果。

function deepClone(initalObj, finalObj) {    
  var obj = finalObj || {};    
  for (var i in initalObj) {        
    var prop = initalObj[i];        // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况
    if(prop === obj) {            
      continue;
    }        
    if (typeof prop === &#39;object&#39;) {
      obj[i] = (prop.constructor === Array) ? [] : Object.create(prop);
    } else {
      obj[i] = prop;
    }
  }    
  return obj;
}
ログイン後にコピー
ログイン後にコピー


6、jquery

jquery 有提供一个$.extend可以用来做 Deep Copy。

var $ = require(&#39;jquery&#39;);var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};var obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f);// false
ログイン後にコピー
ログイン後にコピー


7、lodash

另外一个很热门的函数库lodash,也有提供_.cloneDeep用来做 Deep Copy。

var _ = require(&#39;lodash&#39;);var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false
ログイン後にコピー
ログイン後にコピー

这个性能还不错,使用起来也很简单。

 

参考:

JavaScript 中对象的深拷贝

关于 JS 中的浅拷贝和深拷贝

iOS 深拷贝两种实现

以上がJavaScript の浅いコピーと深いコピーについての深い理解の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

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