前言
工作中會經常遇到運算元組、物件的情況,你肯定會將原陣列、物件進行‘備份’
當真正對其操作時發現備份的也發生改變,此時你一臉懵逼,到時是為啥,不是已經備份了麼,怎麼備份的陣列、物件也會發生變化。
如果你對拷貝原理理解的不透徹,此文或許能提供一點幫助。
javascript資料型別
基本資料型別
string
、number
、null
、undefined
、boolean
、symbol
(ES6新增) 變數值存放在棧記憶體中,可直接訪問和修改變數的值
基本資料型別不存在拷貝,好比如說你無法修改數值1的值
引用型別
Object
Function
RegExp
Math
Date
值為物件,存放在堆記憶體中
在棧記憶體中變數儲存的是一個指標,指向對應在堆記憶體中的地址。
當訪問引用型別的時候,要先從棧中取出該物件的地址指標,然後再從堆記憶體中取得所需的資料
淺拷貝
為什麼備份的陣列物件也會發生變化,這裡就涉及到你用的‘備份’其實是一種淺拷貝
簡單的引用拷貝
var a = [1,2,3,4];
var b = a;
a[0] = 0;
console.log(a,b);複製程式碼
可以看到陣列a直接賦值給b
,a
、b
引用的其實是一個物件地址,只要地址值發生變化,a
、b
棧記憶體指標指向的堆地址也會發生變化,這種引用拷貝只是新增了一個變數棧記憶體的指標,意義不大
陣列的concat、slice,物件的assign拷貝
同樣的例子
var a = [1,2,3,4];
var b = a.concat();
a[0] = 0;
console.log(a,b);複製程式碼
此時陣列a[0]
值變成0
,b
陣列依然保持不變,有同學就問了,這不就是深拷貝嗎。
對,也不對, Array.prototype.slice
和 Array.prototype.conca
t 看似深拷貝,其實質上還是淺拷貝
var a = [1,2,[3,4],{name:'ccy'}];
var b = a.concat();
a[3].name = 'hs';
console.log(a[3],b[3]);複製程式碼
當陣列a
中包含物件時, Array.prototype.slice
和 Array.prototype.cancat
拷貝出來陣列中的物件還是共享同一記憶體地址,所以本質上歸屬淺拷貝
Object.assign
原理也是一樣的(對於物件的屬性都為基本型別可以當成深拷貝)
var a = {age:18,name:'ccy',info:{address:'wuhan',interest:'playCards'}};
var b = Object.assign(a);
a.info.address = 'shenzhen';
console.log(a.info,b.info);複製程式碼
那怎樣才能對物件進行深拷貝呢,請扶好眼鏡。
自己寫一個深拷貝函式
var clone = function(obj){
var construct = Object.prototype.toString.call(obj).slice(8,-1);
var res;
if(construct === 'Array'){
res = [];
}else if(construct === 'Object'){
res = {}
}
for(var item in obj){
if(typeof obj[item] === 'object'){
res[item] = clone(obj[item]);
}else{
res[item] = obj[item];
}
}
return res;
};複製程式碼
乍一看好像能處理物件的屬性為物件的問題,可以迴圈遍歷直至屬性為基本型別;
但是仔細想,如果遇到物件的屬性存在相互引用的話會出現死迴圈的情況。可以再加一次判斷,物件的屬性如果引用物件指標則跳出當前迴圈。
深拷貝
深拷貝是可以完美的解決淺拷貝的弊端,重新開闢一塊地址,深拷貝出來的屬性的基本型別值都是相同的。
JSON內建物件深拷貝
JSON
物件是ES5中引入的新的型別(支援的瀏覽器為IE8+),JSON
物件parse
方法可以將JSON
字串反序列化成JS
物件,stringify
方法可以將JS物件序列化成JSON
字串,藉助這兩個方法,也可以實現物件的深拷貝
var a = {age:1,name:'ccy',info:{address:'wuhan',interest:'playCards'}};
var b = JSON.parse(JSON.stringify(a));
a.info.address = 'shenzhen';
console.log(a.info,b.info);複製程式碼
JSON
可處理一般的物件進行深拷貝,但是不能處理函式、正則等物件
自定義深拷貝函式
我們可以對自定義的拷貝函式再進行優化
var clone = function(obj){
function getType(obj){
return Object.prototype.toString.call(obj).slice(8,-1);
}
function getReg(a){
var c = a.lastIndexOf('/');
var reg = a.substring(1,c);
var escMap = {'"': '\\"', '\\': '\\\\', '\b': '\\b', '\f': '\\f', '\n': '\\n', '\r': '\\r', '\t': '\\t', '\w': '\\w', '\s': '\\s', '\d': '\\d'};
for(var i in escMap){
if(reg.indexOf(i)){
reg.replace(i,escMap[i]);
}
}
var attr = a.substring(c+1);
return new RegExp(reg, attr);
}
var construct = getType(obj);
var res;
if(construct === 'Array'){
res = [];
}else if(construct === 'Object'){
res = {}
}
for(var item in obj){
if(obj[item] === obj) continue;//存在引用則跳出當前迴圈
if(getType(obj[item]) === 'Function'){
res[item] = new Function("return "+obj[item].toString())();
}else if(getType(obj[item]) === 'RegExp'){
res[item] = getReg(obj[item].toString());
}else if(getType(obj[item]) === 'Object'){
res[item] = clone(obj[item]);
}else{
res[item] = obj[item];
}
}
return res;
};複製程式碼
基本可以實現函式、正則物件的深拷貝,在本地只做了簡單的測試,如果存在問題,請及時評論指出。
當然像函式庫 lodash
的 _.cloneDeep
、 JQuery
的 $.extend
都實現了深拷貝,有興趣的同學可自行看下原始碼。