原文地址
要說(shuō) JavaScript 的拷貝問(wèn)題,首先要了解的是 JavaScript 的數(shù)據(jù)類(lèi)型。
在 JavaScript 中數(shù)據(jù)類(lèi)型大致可以分成三類(lèi),分別是基本數(shù)據(jù)類(lèi)型、引用數(shù)據(jù)類(lèi)型和特殊數(shù)據(jù)類(lèi)型。
基本數(shù)據(jù)類(lèi)型有:Number、String、Boolean、
引用數(shù)據(jù)類(lèi)型有:Object(其中包含Array, Object, Date, RegExp)
特殊數(shù)據(jù)類(lèi)型:Null、Undefined
ES6 引入了一種新的原始數(shù)據(jù)類(lèi)型 Symbol,表示獨(dú)一無(wú)二的值。所以現(xiàn)在JavaScript中一共有7種數(shù)據(jù)類(lèi)型。
其中特殊類(lèi)型Undefined是變量聲明了但是沒(méi)有賦值,Null表示一個(gè)無(wú)的對(duì)象是一個(gè)Object,這兩者在判斷語(yǔ)句中都會(huì)被轉(zhuǎn)成false,其實(shí)都是表示沒(méi)有,但為什么要有兩個(gè),說(shuō)起來(lái)比較復(fù)雜,可以看看阮老師的文章undefined與null的區(qū)別。
繼續(xù)往下說(shuō),下面簡(jiǎn)單說(shuō)說(shuō)基本數(shù)據(jù)類(lèi)型和引用數(shù)據(jù)類(lèi)型的區(qū)別。
最大的區(qū)別就是,基本數(shù)據(jù)類(lèi)型的變量標(biāo)識(shí)符和變量值都存在棧內(nèi)存,而引用類(lèi)型在棧內(nèi)存中存儲(chǔ)的是變量標(biāo)識(shí)符和指針,然后在堆內(nèi)存中保存對(duì)象內(nèi)容。如下圖:

現(xiàn)在開(kāi)始說(shuō)拷貝的問(wèn)題,基本數(shù)據(jù)類(lèi)型,我們直接
let a = 1;
let a1 = a;
就可以了。而如果是引用類(lèi)型這樣的話
let b = {a: 1};
let b1 = b;
此時(shí)b和b1的值就會(huì)互相影響也就是改變了他們中任何一個(gè)值,另外一個(gè)值也會(huì)相應(yīng)的改變。
來(lái)個(gè)圖:

由此可見(jiàn)使用=的方式對(duì)于基本類(lèi)型是有效的而對(duì)于引用類(lèi)型是不可以的。
在jQuery中我們可以直接使用jQuery的extend()方法來(lái)進(jìn)行深拷貝。
使用原生JavaScript我們應(yīng)該這么做呢?
如果是Array,且數(shù)組中的數(shù)據(jù)都是基本數(shù)據(jù)類(lèi)型,我們可以利用數(shù)組中的slice()方法來(lái)返回一個(gè)新的數(shù)組,像這樣:
let arrA = [1, 2, 3, 4, 5, 6, 7, 8, 9];
let arrA1 = arrA.slice();
如果是Object的keyValuePair,且對(duì)應(yīng)的value都是基本數(shù)據(jù)類(lèi)型,則可以用Object.assign()來(lái)操作。
let a = {a: 1};
let a1 = Object.assign({}, a);
上述兩種方法,只能在特定的情況下有用,一旦數(shù)組的值是引用類(lèi)型或者對(duì)象中value的值是一個(gè)引用類(lèi)型,就不起作用了。如果遇到復(fù)雜的數(shù)據(jù)類(lèi)型該怎么進(jìn)行深拷貝呢?以前在不少帖子里看過(guò)說(shuō)使用JSON來(lái)做個(gè)中轉(zhuǎn),類(lèi)似于這樣:
let a = {a: {a : {a: 1}}};
let a1 = JSON.parse(JSON.stringify(a));
這個(gè)方法好像很無(wú)敵,貌似無(wú)論多么復(fù)雜的數(shù)據(jù)類(lèi)型都能深拷貝,但是一旦遇到key對(duì)應(yīng)的value是Function或者其他JSON不支持的類(lèi)型就不行了,可以參照stackoverflow上的這個(gè)回答javascript deep copy using JSON
所以在JavaScript中深拷貝是一件非常費(fèi)事的事情。大部分情況下我們并不需要深拷貝,再仔細(xì)看看自己寫(xiě)的函數(shù)非得深拷貝不可嗎?
恩,如果你非要!
- 看看jQuery
extend()方法的具體實(shí)現(xiàn),這里有個(gè)參考資料jQuery源碼 - 使用lodash的
cloneDeep()方法實(shí)現(xiàn)比較復(fù)雜,當(dāng)然也比較好 - 再貼一個(gè)stackoverflow上的回答How to Deep clone in javascript
function clone(item) {
if (!item) { return item; } // null, undefined values check
var types = [ Number, String, Boolean ],
result;
// normalizing primitives if someone did new String('aaa'), or new Number('444');
types.forEach(function(type) {
if (item instanceof type) {
result = type( item );
}
});
if (typeof result == "undefined") {
if (Object.prototype.toString.call( item ) === "[object Array]") {
result = [];
item.forEach(function(child, index, array) {
result[index] = clone( child );
});
} else if (typeof item == "object") {
// testing that this is DOM
if (item.nodeType && typeof item.cloneNode == "function") {
var result = item.cloneNode( true );
} else if (!item.prototype) { // check that this is a literal
if (item instanceof Date) {
result = new Date(item);
} else {
// it is an object literal
result = {};
for (var i in item) {
result[i] = clone( item[i] );
}
}
} else {
// depending what you would like here,
// just keep the reference, or create new object
if (false && item.constructor) {
// would not advice to do that, reason? Read below
result = new item.constructor();
} else {
result = item;
}
}
} else {
result = item;
}
}
return result;
}
- 自己擼一個(gè)...
- 待我有空擼一個(gè)(逃