對象的深淺拷貝,一直是老生常談的話題,平臺上的文章數量可謂是汗牛充棟,要從這塊素材裏找突破幾乎是不可能。索性我就寫一篇文章,積累一下本身的學習心得,以便後續複習的時候,能有一個比較清晰的思路。javascript
淺拷貝:將數據中全部的數據引用下來,並指向同一個存放地址,拷貝的數據修改以後,會對原數據產生反作用。前端
深拷貝:將數據中全部的數據拷貝下來,對拷貝以後的數據進行修改不會對原始數據產生反作用。java
業務中,不少時候你作的是淺拷貝,若是不影響業務邏輯,你可能不關心這些東西。面試
引用類型的等號賦值是最多見淺拷貝,以下所示:數組
var obj = {
name: 'Nick'
}
var newObj = obj
複製代碼
此時你修改 newObj.name = 'Chen'
,則會使得 obj
也會跟着變化,這是由於聲明的 obj
屬於引用類型的變量,存在了全局做用域下的堆內存中。賦值給 newObj
,只是將內存的地址賦值給了它,因此修改 newObj
的屬性,也就是修改了堆內存中數據的屬性,從而 obj
也會跟着改變。性能優化
var obj = {
name: 'Nick'
}
var newObj = obj
newObj.name = 'Chen'
console.log(obj.name) // 'Chen'
console.log(newObj.name) // 'Chen'
複製代碼
你覺得 Object.assign
是深拷貝方法,其實否則。它也是淺拷貝,只不過是第一級的原始類型的數據,不受牽連,引用類型仍是會被篡改,咱們用數聽說話:markdown
var obj = {
name: 'Nick',
hobby: ['code', 'movie', 'travel', { a: 1 }]
}
var newObj = Object.assign({}, obj)
newObj.name = 'Chen'
newObj.hobby[0] = 'codeing'
newObj.hobby[3].a = 2
console.log('obj', obj)
console.log('newObj', newObj)
複製代碼
打印結果以下:函數
綠色箭頭表明原始類型,沒有被篡改。紅色箭頭表明的是引用類型,都隨着 newObj
的修改而變化。工具
它比較特殊,若是要拷貝的對象,第一層是原始類型,則爲深拷貝。若是是引用類型,則爲淺拷貝,不妨作個小實驗:post
var obj = {
name: 'Nick',
salary: {
high: 1,
mid: 2,
low: 3
}
}
var newObj = { ...obj }
newObj.name = 'Chen'
newObj.salary.high = 2
console.log(obj)
console.log(newObj)
複製代碼
obj
的 name
屬性沒有被改變,salary
中的 high
被改爲了 2。
因此咱們若是想用 ...
擴展運算符完成深拷貝,就得這樣操做:
var obj = {
name: 'Nick',
salary: {
high: 1,
mid: 2,
low: 3
}
}
var newObj = {
...obj,
salary: {
...obj.salary
}
}
複製代碼
我以爲這樣操做的人,確定是有毛病。
不少有志之士,會在代碼中使用這種方式去作深拷貝。固然,多數業務場景中,這種方式仍是比較香的,可是仍是會有那麼些狀況,會出現大大小小的問題。
var obj = {
name: 'Nick',
hobby: ['code', 'movie', 'travel', { a: 1 }],
callback: function() {
console.log('test')
}
}
var newObj = JSON.parse(JSON.stringify(obj))
newObj.name = 'Chen'
newObj.hobby[0] = 'codeing'
newObj.hobby[3].a = 2
console.log('obj', obj)
console.log('newObj', newObj)
複製代碼
確實沒有被關聯到,數據已經脫離了控制,可是函數 callback
麼的了。
var obj = {
name: 'Nick',
date: [new Date(1621259998866), new Date(1621259998866)],
};
var newObj = JSON.parse(JSON.stringify(obj))
複製代碼
obj
中的 date
內的時間對象被執行了。
var obj = {
name: 'Nick',
date: new RegExp('\\s+'),
};
var newObj = JSON.parse(JSON.stringify(obj));
obj.name = 'Chen'
複製代碼
拷貝以後,date
變成了一個空值。
var obj = {
name: undefiend
}
var newObj = JSON.parse(JSON.stringify(obj));
複製代碼
undefiend
在拷貝的過程當中,被丟失了。
var obj = {
name1: NaN,
name2: Infinity,
name3: -Infinity
}
var newObj = JSON.parse(JSON.stringify(obj))
複製代碼
直接所有變成
null
,不跟你嘻嘻哈哈,可是這種狀況應該也很少。
function Animal(name) {
this.name = name
}
var animal = new Animal('dog')
var obj = {
test: animal
}
var newObj = JSON.parse(JSON.stringify(obj))
複製代碼
直接就把構造函數給丟了,拷貝以後,直接指向了 Object
。
諸如上述種種的狀況,在真實開發環境中遇到的可能不是不少,可是你真的遇到了,在不知情的狀況下,可能會耗費一些沒必要要的時間去找出問題所在。
首先,大可使用 lodash.cloneDeep
這類工具實現深拷貝,有工具不用,哎,放着玩兒?
這裏我要手動寫一個深拷貝,從中能夠學習到一些小知識點,愛看不看吧,我寫給本身看。
var obj = {
name: 'Nick',
date: [new Date(1621261792177)],
callback: function() { console.log('shadiao') },
link: undefined
}
function deepClone(origin) {
if(origin === null) return null
if(typeof origin !== 'object') return origin;
if(origin.constructor === Date) return new Date(origin);
// 接受兩個參數,origin 是原對象
var _target = origin.constructor() //保持繼承鏈
// 循環 origin
for(var key in origin) {
//不遍歷其原型鏈上的屬性
if (origin.hasOwnProperty(key)) {
// 若是 origin[key] 是一個引用類型的值,則進入遞歸邏輯
if (typeof origin[key] === 'object' && origin[key] !== null) {
// 進入遞歸,此時原始值就是 origin[key],被賦值的對象是 _target[key]
// 注意,上述第一次聲明的 _target 將會貫穿整個遞歸,後續全部的賦值,都將會被 return 到 _target
_target[key] = deepClone(origin[key])
} else {
// 若是不是對象或數組,則進入此邏輯,直接賦值給 _target[key]
_target[key] = origin[key]
}
}
}
// for...in 循環結束後,return 當前上下文的 _target 值
return _target
}
const newObj = deepClone(obj)
複製代碼
上述 obj
對象的屬性都被完整的拷貝下來了。
上述代碼中,有一個關鍵步驟,若是理解了它,基本上你就理解爲何能夠實現遞歸賦值,咱們來看下面這段代碼:
function test() {
var obj = {}
const _obj = test1(obj)
console.log('obj', obj)
console.log('_obj', _obj)
console.log(_obj === obj)
}
function test1(_obj) {
_obj.a = 1
return _obj
}
test()
複製代碼
上述代碼,在函數 test
內部聲明 obj
對象,並將其以參數的形式,傳遞給 test1
方法。test1
內部的操做是給傳進來的 _obj
參數賦值一個 a
屬性,而且 return _obj
。
此時查看打印結果,obj
被也被添加了 a
屬性,而且 _obj
全等於 obj
。這說明它們指向了同一個內存地址,就是 test
內的函數做用域。在《JavaScript 高級程序設計》第 86 頁,對引用類型在函數之間的傳遞的知識有詳細的分析。
利用這個原理,上述 deepClone
方法內部,執行遞歸的時候,所傳進去的 _target[key]
,其實這個 _target
就是第一次執行 deepClone
的引用類型變量,後續遞歸操做對 _target[key]
的賦值,都將反映到最初的 _target
。最後函數執行結束,return _target
即是最終遞歸深拷貝後的最終值。
這個知識點很是細節,我不敢說會在業務開發中大量用到。但至少當你遇到這類問題的時候,你不會一頭霧水、傷春悲秋,以爲本身不適合這個行業。再一次強調,基礎知識很重要,不要小看這些平時不起眼的知識,真到了拼刺刀的時候,你一無所知。
打通任督二脈的前端環境變量 — env 點贊數👍 228
Vite 2.0 + React + Ant Design 4.0 搭建開發環境 點贊數👍 385
面不面試的,你都得懂原型和原型鏈 點贊數👍 593
Vue 3 和 Webpack 5 來了,手動搭建的知識該更新了 點贊數👍 521
換一個角度分析,網頁性能優化 點贊數👍 200
你好,談談你對前端路由的理解 點贊數👍 625
之前我沒得選,如今我只想用 Array.prototype.reduce 點贊數👍 588
無處不在的發佈訂閱模式 —— 此次必定 點贊數👍 164
聊聊 JSX 和虛擬 DOM 點贊數👍 110