js 什么是深拷貝問題?


一、什么是值類型?

二、什么是引用類型?

三、使用ES Next新特性帶來的 Object.assign 方法 和 擴展運算符;

四、Object.assign 方法 和 擴展運算符的 “深入淺出” 問題 —— 淺拷貝;

五、解決深拷貝問題常見的三種方法。

 


 

在 JavaScript 中,數據類型包括:

1、基本數據(值)類型,如 undefined、null、number、boolean、string等

2、引用類型:如Object、Array、Function等。

 

一、什么是值類型?

var a = 1;
var b = a;

b = b + 1
console.log(a, b) // 1, 2

值類型賦值時,會在棧內存空間中分配全新的地址,並得到相應的值。代碼中 b 是從 a 創建來的,當 b 變化時,a 不會受到影響。這是符合常識的。

 

二、什么是引用類型?

var data =  {
    id: 0,
    book: 'learn redux',
    avaliable: false
}
var newData = data

newData.id
= 1; console.log(data.id, newData.id) // 1 1

再來看看引用類型(以Object對象為例),對象實際是存在於堆內存空間中。當我們要訪問一個對象的時候,實際上是從棧內存中獲取引用地址,然后根據這個引用地址再從堆內存中獲取所需要的值。

而引用類型賦值時,實際上是獲取其引用地址,而不是直接獲取值。所以,值發生改變時,源對象也會隨着改變。

 

三、使用ES Next新特性帶來的 Object.assign 方法 和 擴展運算符

使用 Object.assign({}, ...) 來解決 "單層"對象 引用問題

let data =  {
    id: 0,
    book: 'learn redux',
    avaliable: false
}
let newData
= Object.assign({}, data)
newData.id
= 1; console.log(data.id, newData.id) // 0 1

 或者使用對象的擴展運算符

var data =  {
    id: 0,
    book: 'learn redux',
    avaliable: false
}
var newData = {title: 'fuck', ...data};
newData.id = 1;
console.log(data.id, newData.id) // 0 1

(PS:JavaScript的數組也是引用類型,同理可以使用 Array.concat 方法 和 數組的擴展運算符來解決引用問題,這里就省略了)

 

四、Object.assign 方法 和 擴展運算符的 “深入淺出” 問題 —— 淺拷貝

需要注意的是,使用 Object.assign 及 擴展運算符都屬於淺操作。如果往對象的外面再套一層的話,那問題就會浮現出來了。

var data =  {
    title: '123',
    item: {
        id: 0,
        book: 'learn redux',
        avaliable: false    
    }
}
var newData = Object.assign({}, data)
newData.item.id = 1;
console.log(data.item.id, newData.item.id) // 1 1

上述的結果表明,原始數據還是被更改了。

 

 

 

五、解決深拷貝問題常見的三種方法

1、jQuery的深拷貝工具: $.extend(true, ...)

let data = {
    title: '123',
    item: {
        id: 0,
        book: 'learn redux',
        avaliable: false
    }
}
// 首參必須設置為 true 才算是深拷貝 let newData
= $.extend(true, {}, data); newData.item.id = 1 console.log(data.item.id, newData.item.id) // 0 1

為什么$.extend默認不使用深拷貝,還需要手動傳參true才開啟呢?

這是因為深拷貝在實戰中是比較消耗性能的,如無必要請使用淺拷貝即可。稍后我們會實現一個自己的深拷貝工具。你可以從中了解詳情。

 

 

2、巧用序列化:JSON.parse(JSON.stringify(...))

let data = {
    title: '123',
    item: {
        id: 0,
        book: 'learn redux',
        avaliable: false
    }
}
let newData =  JSON.parse(JSON.stringify(data))
newData.item.id = 1
console.log(data.item.id, newData.item.id) // 0 1

原理很簡單,就是先將對象轉換為字符串,再重新序列化為對象,這樣理所當然不會有引用問題。值得一提的是,這些操作對數組也是有效的。

 

 

3、遞歸拷貝(深拷貝)

在我們封裝一個自己的深拷貝工具之前,我們需要先跳過一些可能的拷貝錯誤認知 —— “不要單純的認為,只要是拷貝嵌套對象就會產生深拷貝問題”。

(1)就算是拷貝嵌套對象,如果操作的只是基本數據類型,不會有影響的。

let data = {
    title: '123',
    item: {
        id: 0,
        book: 'learn redux',
        avaliable: false
    }
}
let newData = Object.assign({}, data);
newData.title = 'fuck'
console.log(data.title, newData.title) // 123  fuck

(2)就算是嵌套對象,如果只拷貝其中一層對象,是不會產生問題的。

let data = {
    title: '123',
    item: {
        id: 0,
        book: 'learn redux',
        avaliable: false
    }
}
let newData = Object.assign({}, data.item);
newData.id = 1;
console.log(data.item.id, newData.id) // 0 1

 

有了以上兩個認知,實際上我們可以實現手動深拷貝了。

let data = {
    title: '123',
    item: {
        id: 0,
        book: 'learn redux',
        avaliable: false
    }
}

// 創建一個空對象
let newData = {}

// 基本數據類型,可以直接拿
newData.title = data.title

// data.item對象,其實可以使用 Object.assign 來拿
// newData.item = Object.assign({}, data.item)

// 但為了體現手動深拷貝細節,還是一步一步來吧。
// 通過對data.item拆解,item對象被轉換為一個個的基本數據類型直接賦值給newData.item的每一項
// 如果 item 還有對象的話,那就只能依樣畫葫蘆繼續拆解。直到變成基本數據類型為止。
newData.item = {}
newData.item.id = data.item.id
newData.item.book = data.item.book
newData.item.avaliable = data.item.avaliable

newData.item.id = 1
console.log(data.item.id, newData.item.id) // 0 1

通過對data.item拆解,item對象就轉換為一個個的基本數據類型了,所以可以直接賦值給newData.item的每一項了。

如果 item 還有對象的話,那就只能依樣畫葫蘆繼續拆解。直到變成基本數據類型為止。

 

知道了如何手動深拷貝,現在我們把他轉換為自動化深拷貝,常見的的實現方式是遞歸拷貝了:

// 遞歸深拷貝
var
deepExtend = function(out) { out = out || {}; for (var i = 1; i < arguments.length; i++) { var obj = arguments[i]; if (!obj) continue; for (var key in obj) { if (obj.hasOwnProperty(key)) { if (typeof obj[key] === 'object') out[key] = deepExtend(out[key], obj[key]); else out[key] = obj[key]; } } } return out; };
// demo let data
= { title: '123', item: { id: 0, book: 'learn redux', avaliable: false } } var newData = deepExtend({}, data); newData.item.id = 1 console.log(data.item.id, newData.item.id) // 0 1

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM