公告:攜程招聘java、前端、測試、產品等,請發簡歷至[email protected],幫內推!

什么是js深拷貝和淺拷貝及其實現方式

14194次瀏覽

前言

這個月太忙了,一直沒時間更新博客。幫樓下房友用react寫系統,react終于應用于實際項目了。房友前端技術棧是react+dva+antd+nodejs+webpack,這些后面有時間具體總結。今天寫一下js的深拷貝和淺拷貝,這個名詞經常聽到,但是可能有的同學不是很明白他們的意思。今天就用淺顯易懂的語言和大家介紹一下!

淺拷貝

淺拷貝其實我之前有文章具體講過,不過沒有提及這個名詞罷了,例如:js內存空間及this關鍵詞詳解,這篇文章,里面講到如下:

var m = { a: 10, b: 20 }
var n = m;
n.a = 15;
// 這時m.a的值是多少

m.a會輸出15,因為這是淺拷貝,n和m指向的是同一個堆,對象復制只是復制的對象的引用。

深拷貝

深拷貝和上面淺拷貝不同,就是徹底copy一個對象,而不是copy對象的引用,例如,還是之前的例子,我們這么寫:

var m = { a: 10, b: 20 }
var n = {a:m.a,b:m.b};
n.a = 15;

這次,我們再來輸出m.a ,發現m.a的值還是10,并沒有改變,m對象和n對象是雖然所有的值都是一樣的,但是在堆里面,對應的不是同一個了,這個就是深拷貝。

深拷貝和淺拷貝

深拷貝和淺拷貝的示意圖大致如下:

enter image description here

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

淺拷貝的實現方式

1、可以通過簡單的賦值實現

類似上面的例子,當然,我們也可以封裝一個簡單的函數,如下:

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

2、Object.assign()實現

Object.assign() 方法可以把任意多個的源對象自身的可枚舉屬性拷貝給目標對象,然后返回目標對象。但是 Object.assign() 進行的是淺拷貝,拷貝的是對象的屬性的引用,而不是對象本身。

var obj = { a: {a: "hello", b: 21} };

var initalObj = Object.assign({}, obj);

initalObj.a.a = "changed";

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

注意:當object只有一層的時候,是深拷貝,例如如下:

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 }

深拷貝的實現方式

1、方法一還是手動復制

和上面的舉例一樣,手動復制可以實現深拷貝。

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

3、轉成 JSON 再轉回來

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

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

可以封裝如下函數

var cloneObj = function(obj){
    var str, newobj = obj.constructor === Array ? [] : {};
    if(typeof obj !== 'object'){
        return;
    } else if(window.JSON){
        str = JSON.stringify(obj), //系列化對象
        newobj = JSON.parse(str); //還原
    } else {
        for(var i in obj){
            newobj[i] = typeof obj[i] === 'object' ? 
            cloneObj(obj[i]) : obj[i]; 
        }
    }
    return newobj;
};

4、遞歸拷貝

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

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

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

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

這個性能還不錯,使用起來也很簡單。

小結

上面就是對js深拷貝和淺拷貝的總結,其中部分參考了一些文章, 例如

javascript中的深拷貝和淺拷貝?

關于 JS 中的淺拷貝和深拷貝

JavaScript中對象的深拷貝

Tags: js深拷貝淺拷貝

相關文章:

  1. aiya
    1
    遞歸拷貝在嚴格模式下,會有問題的呢
  2. 彩蛋
    2
    "use strict"; function deepCopy(target){ let copyed_objs = [];//此數組解決了循環引用和相同引用的問題,它存放已經遞歸到的目標對象 function _deepCopy(target){ if((typeof target !== &#039;object&#039;)||!target){return target;} for(let i= 0 ;i<copyed_objs.length;i++){ if(copyed_objs[i].target === target){ return copyed_objs[i].copyTarget; } } let obj = {}; if(Array.isArray(target)){ obj = [];//處理target是數組的情況 }
  3. 彩蛋
    3
    copyed_objs.push({target:target,copyTarget:obj}) Object.keys(target).forEach(key=>{ if(obj[key]){ return;} obj[key] = _deepCopy(target[key]); }); return obj; } return _deepCopy(target); } var a = { arr:[1,2,3,{key:&#039;123&#039;}],//數組測試 }; a.self0 = a;//循環引用測試 a.common1 = {name:&#039;ccc&#039;}; a.common2 = a.common1;//相同引用測試 var c = deepCopy(a); c.common1.name = &#039;changed&#039;; console.log(c);
任选9场吧