天天看點

JavaScript淺拷貝與深拷貝的差別和實作方式

JavaScript淺拷貝與深拷貝的差別和實作方式

如何區分深拷貝與淺拷貝,簡單點來說,就是假設B複制了A,當修改A時,看B是否會發生變化,如果B也跟着變了,說明這是淺拷貝,拿人手短,如果B沒變,那就是深拷貝,自食其力。

1. 如果是基本資料類型,名字和值都會儲存在棧記憶體中

var a = 1;
b = a; // 棧記憶體會開辟一個新的記憶體空間,此時b和a都是互相獨立的
b = 2;
console.log(a); // 1      

當然,這也算不上深拷貝,因為深拷貝本身隻針對較為複雜的object類型資料。

2. 如果是引用資料類型,名字存在棧記憶體中,值存在堆記憶體中,但是棧記憶體會提供一個引用的位址指向堆記憶體中的值。

比如淺拷貝:

JavaScript淺拷貝與深拷貝的差別和實作方式

當b=a進行拷貝時,其實複制的是a的引用位址,而并非堆裡面的值。

JavaScript淺拷貝與深拷貝的差別和實作方式

而當我們a[0]=1時進行數組修改時,由于a與b指向的是同一個位址,是以自然b也受了影響,這就是所謂的淺拷貝了。

JavaScript淺拷貝與深拷貝的差別和實作方式

那,要是在堆記憶體中也開辟一個新的記憶體專門為b存放值,就像基本類型那樣,豈不就達到深拷貝的效果了

JavaScript淺拷貝與深拷貝的差別和實作方式

3. 實作淺拷貝的方法​

(1)for···in隻循環第一層​

// 隻複制第一層的淺拷貝
function simpleCopy(obj1) {
   var obj2 = Array.isArray(obj1) ? [] : {};
   for (let i in obj1) {
   obj2[i] = obj1[i];
  }
   return obj2;
}
var obj1 = {
   a: 1,
   b: 2,
   c: {
         d: 3
      }
}
var obj2 = simpleCopy(obj1);
obj2.a = 3;
obj2.c.d = 4;
alert(obj1.a); // 1
alert(obj2.a); // 3
alert(obj1.c.d); // 4
alert(obj2.c.d); // 4      

(2)Object.assign方法​

var obj = {
    a: 1,
    b: 2
}
var obj1 = Object.assign(obj);
obj1.a = 3;
console.log(obj.a) // 3      

(3)直接用=指派

let a=[0,1,2,3,4],
    b=a;
console.log(a===b);
a[0]=1;
console.log(a,b);      
JavaScript淺拷貝與深拷貝的差別和實作方式

4. 實作深拷貝的方法

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

結果:

JavaScript淺拷貝與深拷貝的差別和實作方式

(2) 通過jsON對象來實作深拷貝​

function deepClone2(obj) {
  var _obj = jsON.stringify(obj),
    objClone = JSON.parse(_obj);
  return objClone;
}      

缺點:無法實作對對象中方法的深拷貝,會顯示為undefined

(3)通過jQuery的extend方法實作深拷貝。

var array = [1,2,3,4];
var newArray = $.extend(true,[],array); // true為深拷貝,false為淺拷貝      

(4)lodash函數庫實作深拷貝

let result = _.cloneDeep(test)      

(5)Reflect法​

// 代理法
function deepClone(obj) {
    if (!isObject(obj)) {
        throw new Error('obj 不是一個對象!')
    }


    let isArray = Array.isArray(obj)
    let cloneObj = isArray ? [...obj] : { ...obj }
    Reflect.ownKeys(cloneObj).forEach(key => {
        cloneObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key]
    })


    return cloneObj
}      

(6)手動實作深拷貝

let obj1 = {
   a: 1,
   b: 2
}
let obj2 = {
   a: obj1.a,
   b: obj1.b
}
obj2.a = 3;
alert(obj1.a); // 1
alert(obj2.a); // 3      

(7)如果對象的value是基本類型的話,也可以用Object.assign來實作深拷貝,但是要把它指派給一個空對象

var obj = {
    a: 1,
    b: 2
}
var obj1 = Object.assign({}, obj); // obj指派給一個空{}
obj1.a = 3;
console.log(obj.a);// 1      
JavaScript淺拷貝與深拷貝的差別和實作方式

(8)用slice實作對數組的深拷貝​

// 當數組裡面的值是基本資料類型,比如String,Number,Boolean時,屬于深拷貝
// 當數組裡面的值是引用資料類型,比如Object,Array時,屬于淺拷貝
var arr1 = ["1","2","3"]; 
var arr2 = arr1.slice(0);
arr2[1] = "9";
console.log("數組的原始值:" + arr1 );
console.log("數組的新值:" + arr2 );      

(9)用concat實作對數組的深拷貝

// 當數組裡面的值是基本資料類型,比如String,Number,Boolean時,屬于深拷貝
var arr1 = ["1","2","3"];
var arr2 = arr1.concat();
arr2[1] = "9";
console.log("數組的原始值:" + arr1 );
console.log("數組的新值:" + arr2 );
// 當數組裡面的值是引用資料類型,比如Object,Array時,屬于淺拷貝
var arr1 = [{a:1},{b:2},{c:3}];
var arr2 = arr1.concat();
arr2[0].a = "9";
console.log("數組的原始值:" + arr1[0].a ); // 數組的原始值:9
console.log("數組的新值:" + arr2[0].a ); // 數組的新值:9      

(10)直接使用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;
}      
// 當value是基本資料類型,比如String,Number,Boolean時,是可以使用拓展運算符進行深拷貝的
// 當value是引用類型的值,比如Object,Array,引用類型進行深拷貝也隻是拷貝了引用位址,是以屬于淺拷貝
var car = {brand: "BMW", price: "380000", length: "5米"}
var car1 = { ...car, price: "500000" }
console.log(car1); // { brand: "BMW", price: "500000", length: "5米" }
console.log(car); // { brand: "BMW", price: "380000", length: "5米" }