1、什麼是值類型?redux
2、什麼是引用類型?數組
3、使用ES Next新特性帶來的 Object.assign 方法 和 擴展運算符;工具
4、Object.assign 方法 和 擴展運算符的 「深刻淺出」 問題 —— 淺拷貝;性能
5、解決深拷貝問題常見的三種方法。spa
在 JavaScript 中,數據類型包括:3d
一、基本數據(值)類型,如 undefined、null、number、boolean、string等code
二、引用類型:如Object、Array、Function等。對象
1、什麼是值類型?blog
var a = 1; var b = a; b = b + 1 console.log(a, b) // 1, 2
值類型賦值時,會在棧內存空間中分配全新的地址,並獲得相應的值。代碼中 b 是從 a 建立來的,當 b 變化時,a 不會受到影響。這是符合常識的。遞歸
2、什麼是引用類型?
var data = { id: 0, book: 'learn redux', avaliable: false }
var newData = data
newData.id = 1; console.log(data.id, newData.id) // 1 1
再來看看引用類型(以Object對象爲例),對象實際是存在於堆內存空間中。當咱們要訪問一個對象的時候,其實是從棧內存中獲取引用地址,而後根據這個引用地址再從堆內存中獲取所須要的值。
而引用類型賦值時,其實是獲取其引用地址,而不是直接獲取值。因此,值發生改變時,源對象也會隨着改變。
3、使用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 方法 和 數組的擴展運算符來解決引用問題,這裏就省略了)
4、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
上述的結果代表,原始數據仍是被更改了。
5、解決深拷貝問題常見的三種方法
一、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纔開啓呢?
這是由於深拷貝在實戰中是比較消耗性能的,如無必要請使用淺拷貝便可。稍後咱們會實現一個本身的深拷貝工具。你能夠從中瞭解詳情。
二、巧用序列化: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
原理很簡單,就是先將對象轉換爲字符串,再從新序列化爲對象,這樣理所固然不會有引用問題。值得一提的是,這些操做對數組也是有效的。
三、遞歸拷貝(深拷貝)
在咱們封裝一個本身的深拷貝工具以前,咱們須要先跳過一些可能的拷貝錯誤認知 —— 「不要單純的認爲,只要是拷貝嵌套對象就會產生深拷貝問題」。
(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