關於JavaScript的淺拷貝和深拷貝

在 JS 中有一些基本類型像是NumberStringBoolean,而對象就是像這樣的東西{ name: 'Larry', skill: 'Node.js' },對象跟基本類型最大的不一樣就在於他們的傳值方式。javascript

基本類型是按值傳遞,像是這樣:在修改a時並不會改到bjava

var a = 25;
var b = a;
b = 18;
console.log(a);//25
console.log(b);//18

但對象就不一樣,對象傳的是按引用傳值:jquery

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,由於他們根本是同一個對象,這就是所謂的淺拷貝。json

要避免這樣的錯誤發生就要寫成這樣:數據結構

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)

淺拷貝只複製指向某個對象的指針,而不復制對象自己,新舊對象仍是共享同一塊內存。但深拷貝會另外創造一個如出一轍的對象,新對象跟原對象不共享內存,修改新對象不會改到原對象。性能

淺拷貝的實現方式

也就是簡單地複製而已ui

一、簡單地複製語句spa

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

結果爲:指針

二、Object.assign()

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 的方式。

一、手動複製

把一個對象的屬性複製給另外一個對象的屬性

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時也會修改到舊的。

二、對象只有一層的話可使用上面的:Object.assign()函數

Object.assign({}, obj1)的意思是先創建一個空對象{},接着把obj1中全部的屬性複製過去,因此obj2會長得跟obj1同樣,這時候再修改obj2.b也不會影響obj1。

由於Object.assign跟咱們手動複製的效果相同,因此同樣只能處理深度只有一層的對象,沒辦法作到真正的 Deep Copy。不過若是要複製的對象只有一層的話能夠考慮使用它。

三、轉成 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);
// 'function'
console.log(typeof obj2.fun);
// 'undefined' <-- 沒複製

要複製的function會直接消失,因此這個方法只能用在單純只有數據的對象。

四、遞歸拷貝

function deepClone(initalObj, finalObj) {    
  var obj = finalObj || {};    
  for (var i in initalObj) {        
    if (typeof initalObj[i] === 'object') {
      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 === 'object') {
      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);

五、使用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 === 'object') {
      obj[i] = (prop.constructor === Array) ? [] : Object.create(prop);
    } else {
      obj[i] = prop;
    }
  }    
  return obj;
}

六、jquery

jquery 有提供一個$.extend能夠用來作 Deep Copy。

var $ = require('jquery');
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

七、lodash

另一個很熱門的函數庫lodash,也有提供_.cloneDeep用來作 Deep Copy。

var _ = require('lodash');
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 中的淺拷貝和深拷貝

相關文章
相關標籤/搜索