JavaScript專題之深淺拷貝

冴羽發表於2017-07-12

JavaScript 專題系列第六篇,講解深淺拷貝的技巧和以及實現深淺拷貝的思路

前言

拷貝也是面試經典吶!

陣列的淺拷貝

如果是陣列,我們可以利用陣列的一些方法比如:slice、concat 返回一個新陣列的特性來實現拷貝。

比如:

var arr = ['old', 1, true, null, undefined];

var new_arr = arr.concat();

new_arr[0] = 'new';

console.log(arr) // ["old", 1, true, null, undefined]
console.log(new_arr) // ["new", 1, true, null, undefined]複製程式碼

用 slice 可以這樣做:

var new_arr = arr.slice();複製程式碼

但是如果陣列巢狀了物件或者陣列的話,比如:

var arr = [{old: 'old'}, ['old']];

var new_arr = arr.concat();

arr[0].old = 'new';
arr[1][0] = 'new';

console.log(arr) // [{old: 'new'}, ['new']]
console.log(new_arr) // [{old: 'new'}, ['new']]複製程式碼

我們會發現,無論是新陣列還是舊陣列都發生了變化,也就是說使用 concat 方法,克隆的並不徹底。

如果陣列元素是基本型別,就會拷貝一份,互不影響,而如果是物件或者陣列,就會只拷貝物件和陣列的引用,這樣我們無論在新舊陣列進行了修改,兩者都會發生變化。

我們把這種複製引用的拷貝方法稱之為淺拷貝,與之對應的就是深拷貝,深拷貝就是指完全的拷貝一個物件,即使巢狀了物件,兩者也相互分離,修改一個物件的屬性,也不會影響另一個。

所以我們可以看出使用 concat 和 slice 是一種淺拷貝。

陣列的深拷貝

那如何深拷貝一個陣列呢?這裡介紹一個技巧,不僅適用於陣列還適用於物件!那就是:

var arr = ['old', 1, true, ['old1', 'old2'], {old: 1}]

var new_arr = JSON.parse( JSON.stringify(arr) );

console.log(new_arr);複製程式碼

是一個簡單粗暴的好方法,就是有一個問題,不能拷貝函式,我們做個試驗:

var arr = [function(){
    console.log(a)
}, {
    b: function(){
        console.log(b)
    }
}]

var new_arr = JSON.parse(JSON.stringify(arr));

console.log(new_arr);複製程式碼

我們會發現 new_arr 變成了:

不能拷貝函式
不能拷貝函式

淺拷貝的實現

以上三個方法 concat、slice、JSON.stringify 都算是技巧類,可以根據實際專案情況選擇使用,接下來我們思考下如何實現一個物件或者陣列的淺拷貝。

想一想,好像很簡單,遍歷物件,然後把屬性和屬性值都放在一個新的物件不就好了~

嗯,就是這麼簡單,注意幾個小點就可以了:

var shallowCopy = function(obj) {
    // 只拷貝物件
    if (typeof obj !== 'object') return;
    // 根據obj的型別判斷是新建一個陣列還是物件
    var newObj = obj instanceof Array ? [] : {};
    // 遍歷obj,並且判斷是obj的屬性才拷貝
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = obj[key];
        }
    }
    return newObj;
}複製程式碼

深拷貝的實現

那如何實現一個深拷貝呢?說起來也好簡單,我們在拷貝的時候判斷一下屬性值的型別,如果是物件,我們遞迴呼叫深拷貝函式不就好了~

var deepCopy = function(obj) {
    if (typeof obj !== 'object') return;
    var newObj = obj instanceof Array ? [] : {};
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
        }
    }
    return newObj;
}複製程式碼

效能問題

儘管使用深拷貝會完全的克隆一個新物件,不會產生副作用,但是深拷貝因為使用遞迴,效能會不如淺拷貝,在開發中,還是要根據實際情況進行選擇。

下期預告

難道到這裡就結束了?是的。然而本篇實際上是一個鋪墊,我們真正要看的是 jquery 的 extend 函式的實現,下一篇,我們會講一講如何從零實現一個 jquery 的 extend 函式。

專題系列

JavaScript專題系列目錄地址:github.com/mqyqingfeng…

JavaScript專題系列預計寫二十篇左右,主要研究日常開發中一些功能點的實現,比如防抖、節流、去重、型別判斷、拷貝、最值、扁平、柯里、遞迴、亂序、排序等,特點是研(chao)究(xi) underscore 和 jQuery 的實現方式。

如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。如果喜歡或者有所啟發,歡迎 star,對作者也是一種鼓勵。

相關文章