JavaScript中的淺拷貝和深拷貝

在JavaScript中,對於ObjectArray這類引用類型值,當從一個變量向另外一個變量複製引用類型值時,這個值的副本實際上是一個指針,兩個變量指向同一個堆對象,改變其中一個變量,另外一個也會受到影響。數組

這種拷貝分爲兩種狀況:拷貝引用和拷貝實例,也就是咱們說的淺拷貝和深拷貝函數

淺拷貝(shallow copy)

拷貝原對象的引用,這是最簡單的淺拷貝。prototype

// 對象
var o1 = {a: 1};
var o2 = o1;

console.log(o1 === o2);  // =>true
o2.a = 2; 
console.log(o1.a); // => 2

// 數組
var o1 = [1,2,3];
var o2 = o1;

console.log(o1 === o2); // => true
o2.push(4);
console.log(o1); // => [1,2,3,4]

拷貝原對象的實例,可是對其內部的引用類型值,拷貝的是其引用,經常使用的就是如jquey中的$.extend({}, obj); Array.prototype.slice()Array.prototype.concat()都會返回一個數組或者對象的淺拷貝,舉個例子:指針

var o1 = ['darko', {age: 22}];
var o2 = o1.slice(); // 根據Array.prototype.slice()的特性,這裏會返回一個o1的淺拷貝對象

console.log(o1 === o2); // => false,說明o2拷貝的是o1的一個實例

o2[0] = 'lee';
console.log(o1[0]); // => "darko" o1和o2內部包含的基本類型值,複製的是其實例,不會相互影響

o2[1].age = 23;
console.log(o1[1].age); // =>23 o1和o2內部包含的引用類型值,複製的是其引用,會相互影響

能夠經過Array.prototype.slice()jQuery中的$.extend({}, obj)完成對一個數組或者對象的淺拷貝,咱們也能夠本身寫一個簡單淺拷貝函數來加深對淺拷貝的理解、code

// 淺拷貝實現,僅供參考
function shallowClone(source) {
    if (!source || typeof source !== 'object') {
        throw new Error('error arguments');
    }
    var targetObj = source.constructor === Array ? [] : {};
    for (var keys in source) {
        if (source.hasOwnProperty(keys)) {
            targetObj[keys] = source[keys];
        }
    }
    return targetObj;
}

深拷貝(deep copy)

深拷貝也就是拷貝出一個新的實例,新的實例和以前的實例互不影響,深拷貝的實現有幾種方法,首先咱們能夠藉助jQuery,lodash等第三方庫完成一個深拷貝實例。在jQuery中能夠經過添加一個參數來實現遞歸extend,調用$.extend(true, {}, ...)就能夠實現一個深拷貝。對象

咱們也能夠本身實現一個深拷貝的函數,一般有兩種方式,一種就是用遞歸的方式來作,還有一種是利用JSON.stringifyJSON.parse來作,這兩種方式各有優劣,先來看看遞歸的方法怎麼作。遞歸

jQuery中的extend方法基本的就是按照這個思路實現的,可是沒有辦法處理源對象內部循環引用的問題,同時對Date,Funcion等類型值也沒有實現真正的深度複製,可是這些類型的值在從新定義的時候通常都是直接覆蓋,因此也不會對源對象產生影響,從必定程度上來講也算是實現了一個深拷貝。ip

// 遞歸實現一個深拷貝
function deepClone(source){
   if(!source || typeof source !== 'object'){
     throw new Error('error arguments', 'shallowClone');
   }
   var targetObj = source.constructor === Array ? [] : {};
   for(var keys in source){
      if(source.hasOwnProperty(keys)){
         if(source[keys] && typeof source[keys] === 'object'){
           targetObj[keys] = source[keys].constructor === Array ? [] : {};
           targetObj[keys] = deepClone(source[keys]);
         }else{
           targetObj[keys] = source[keys];
         }
      } 
   }
   return targetObj;
}
// test example
var o1 = {
  arr: [1, 2, 3],
  obj: {
    key: 'value'
  },
  func: function(){
    return 1;
  }
};
var o3 = deepClone(o1);
console.log(o3 === o1); // => false
console.log(o3.obj === o1.obj); // => false
console.log(o2.func === o1.func); // => true

還有一種實現深拷貝的方式是利用JSON對象中的parsestringify,JOSN對象中的stringify能夠把一個js對象序列化爲一個JSON字符串,parse能夠把JSON字符串反序列化爲一個js對象,經過這兩個方法,也能夠實現對象的深複製。ci

咱們從下面的例子就能夠看到,源對象的方法在拷貝的過程當中丟失了,這是由於在序列化JavaScript對象時,全部函數和原型成員會被有意忽略,這個實現能夠知足一些比較簡單的狀況,可以處理JSON格式所能表示的全部數據類型,同時若是在對象中存在循環應用的狀況也沒法正確處理。字符串

// 利用JSON序列化實現一個深拷貝
function deepClone(source){
  return JSON.parse(JSON.stringify(source));
}
var o1 = {
  arr: [1, 2, 3],
  obj: {
    key: 'value'
  },
  func: function(){
    return 1;
  }
};
var o2 = deepClone(o1);
console.log(o2); // => {arr: [1,2,3], obj: {key: 'value'}}
相關文章
相關標籤/搜索