首頁  >  文章  >  web前端  >  深入了解JavaScript中的淺拷貝和深拷貝

深入了解JavaScript中的淺拷貝和深拷貝

零到壹度
零到壹度原創
2018-04-21 15:32:171078瀏覽

這篇文章介紹的內容是關於深入了解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

但物件就不同,對象傳的是按引用傳值:

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 }

複製一份obj1叫做obj2,然後把obj2.b改成#100 ,但卻不小心改到obj1.b,因為他們根本是同一個對象,這就是所謂的淺拷貝。

要避免這樣的錯誤發生就要寫成這樣:

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。

淺拷貝(Shallow Copy) VS 深拷貝(Deep Copy)

淺拷貝只複製指向某個物件的指針,而不複製物件本身,新舊物件還是共享同一塊記憶體。但深拷貝會另外創造一個一模一樣的對象,新對象跟原對像不共享內存,修改新對像不會改到原對象。

淺拷貝的實作方式

也就是簡單複製而已

#1、簡單複製語句

  <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>


結果為:

2、Object.assign()

<code> Object.assign是ES6的新函數。 Object.assign() 方法可以把任意多個的來源物件本身的可枚舉屬性拷貝給目標對象,然後回傳目標物件。但是 Object.assign() 進行的是淺拷貝,拷貝的是物件的屬性的引用,而不是物件本身。

Object.assign(target, ...sources)

參數:

target:目標物件。
sources:任意多個來源物件。
傳回值:目標物件會被傳回。

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

initalObj.a.a = "changed";
console.log(obj.a.a); // "changed"

相容性:

要注意的是:

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 }


深拷貝的實作方式

要完全複製又不能修改到原對象,這時候就要用 Deep Copy,這裡會介紹幾種Deep Copy 的方式。

1、手動複製

把一個物件的屬性複製給另一個物件的屬性

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,因為對象裡面也可能回事對象,如像下面這個狀況:

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

雖然obj1obj2是不同對象,但他們會共用同一個obj1.body所以修改obj2.body.a時也會修改到舊的。

2、物件只有一層的話可以使用上面的:Object<span class="token punctuation">.<span class="token function">assign()函數</span></span>

Object.assign({}, obj1)的意思是先建立一個空物件{},接著把obj1中所有的屬性複製過去,所以obj2會長得跟obj1一樣,這時候再修改obj2.b也不會影響obj1。

因為Object.assign跟我們手動複製的效果相同,所以一樣只能處理深度只有一層的對象,沒辦法做到真正的 Deep Copy。不過如果要複製的物件只有一層的話可以考慮使用它。

3、轉成 JSON 再轉回來

JSON.stringify把對象轉成字串,再用JSON.parse把字串轉成新的物件。

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

這樣做是真正的Deep Copy,這種方法簡單易用。

但是這種方法也有不少壞處,譬如它會拋棄對象的constructor。也就是深拷貝之後,不管這個物件原來的建構子是什麼,在深拷貝之後都會變成Object。

這個方法能正確處理的物件只有 Number, String, Boolean, Array, 扁平物件,也就是那些能夠被 json 直接表示的資料結構。 RegExp物件是無法以這種方式深拷貝。

也就是說,只有可以轉換成JSON格式的物件才可以這樣用,像function沒辦法轉換成JSON。

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中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn