L - 淺拷貝與深拷貝的實現

什麼是淺拷貝和深拷貝

淺拷貝

淺拷貝:將一個對象自身的屬性拷貝給另外一個對象,若是源對象的屬性是基本類型則直接進行值賦值,若是是引用類型則進行引用賦值,也就是說只進行一層賦值。html

深拷貝

深拷貝:將一個對象自身的屬性拷貝給另外一個對象,若是源對象的屬性是基本類型則直接進行值賦值,若是是引用類型則複製這個引用類型,使得目標對象擁有一個引用類型且和這個源屬性如出一轍,而非是一個指針。數組

也就是說,深拷貝與淺拷貝最主要的區別在引用類型的拷貝上。函數

注意,引用賦值不是淺拷貝!! 引用賦值僅僅只是賦值個指針,兩個變量都指向同一內存區域,而淺拷貝是使得兩個變量分別指向不一樣的內存區域測試

若是不懂,能夠參考這裏 一個小姐姐的博客prototype

以上不是重點...指針

淺拷貝的實現

方法一 使用 for in 遍歷

function shallowCopy(source){
    var target=source instanceof Array ? [] : {};
    for(var i in source){
        if(source.hasOwnProperty(i)){
            target[i]=source[i];
        }
    }
    return target;
}

// 測試
var obj={a:1,b:[1,2,3],c:function(){console.log('i am c')}}
var tar=shallowCopy(obj)
tar.c()         // "i am c"
obj.a=5
obj.a           // 5
tar.a           // 1
obj.b[0]=10
obj.b           // [10, 2, 3]
tar.b           // [10, 2, 3]

var arr=[1,2,[4,5,6]]
var newArr=shallowCopy(arr)
newArr          // [1, 2, [4,5,6]]
arr[0]=10
arr             // [10, 2, [4,5,6]]
newArr          // [1, 2, [4,5,6]]
arr[2][0]=10
arr             // [1, 2, [10,5,6]]
newArr          // [1, 2, [10,5,6]]

方法二 使用 Object.assign 或 slice、concat

Object.assigncode

var obj={a:1,b:[1,2,3],c:function(){console.log('i am c')}}
var tar={};
Object.assign(tar,obj);

固然這個方法只適合於對象類型,若是是數組可使用slice和concat方法htm

Array.prototype.slice對象

var arr=[1,2,[3,4]];
var newArr=arr.slice(0);

Array.prototype.concatblog

var arr=[1,2,[3,4]];
var newArr=arr.concat();

測試同上(assign用對象測試、slice concat用數組測試),結合淺拷貝深拷貝的概念來理解效果更佳

深拷貝的實現

方法一 JSON黑科技

var obj={a:1,b:[1,2,3],c:function(){console.log('i am c')}}
var tar=JSON.parse(JSON.stringify(obj));

// 測試
obj.a=5
obj.a     // 5
tar.a     // 1
obj.b[0]=10
obj.b     // [10, 2, 3]
tar.b     // [1, 2, 3]
tar.c()   // Uncaught TypeError: tar.c is not a function

能夠看到,不管是基本類型仍是引用類型,兩個對象的相同屬性(屬性名相同的屬性)之間並無關係了,但tar.c()報錯了,咱們打印看一下tar有什麼console.log(tar) // {a: 1, b: Array(3)}能夠看到c方法沒了,這是和JSON的語法有關,JSON 並不支持函數類型的數據。這也就是這種方法的最大缺陷。

仔細一看,這並不是黑科技,反而卻是有很大缺陷,不過很好用、效率高,只是在使用前須要稍加註意是否有函數類型的數據罷了。

方法二 遞歸拷貝

深拷貝與淺拷貝相比不就是多拷貝幾層的事嘛,這不就是遞歸常乾的事嘛。因此咱們就想,在每次拷貝時都判斷下,該屬性是不是引用類型,若是是咱們再遞歸調用拷貝方法,不然直接進行值賦值。

function deepCopy(obj,tar){
    var tar = tar || {};
    for(var i in obj){
        if(typeof obj[i] === 'object'){
            if(obj[i].constructor === Array){
                tar[i] =[];
            }else{
                tar[i] = {};
            }
            deepCopy(obj[i],tar[i]);
          }
        else{
              tar[i] = obj[i];
          }
    }
    return tar;
}

// 使用
var obj={a:1,b:[1,2,3],c:function(){console.log('i am c')}};
var tar={};
deepCopy(obj,tar);
console.log(tar);

測試同上,你會驚喜的發現第一個方法中的函數bug沒了

你看出來函數是怎樣進行拷貝的了嗎?很簡單,typeof運算符的操做對象是一個函數時,獲得的是 "function" 因此在循環裏第一個if判斷那爲false 因此走else分支,在tar[i] = obj[i]這裏,函數是進行引用賦值的,若是再造一個相同的函數不是不能夠,只是不符合思想罷了,函數佔用堆內存,若是能夠共用固然是最好的選擇。

固然這個方法仍是有些許不足之處,不夠已經很棒了

方法三 更嚴謹、更優雅的實現

這就須要去讀一些好的源碼了,好比Zepto、JQuery中extend方法的實現

現學現賣,若有不足請指出...

相關文章
相關標籤/搜索