原文地址
要說 JavaScript 的拷貝問題,首先要了解的是 JavaScript 的數(shù)據(jù)類型。
在 JavaScript 中數(shù)據(jù)類型大致可以分成三類,分別是基本數(shù)據(jù)類型、引用數(shù)據(jù)類型和特殊數(shù)據(jù)類型。
基本數(shù)據(jù)類型有:Number
、String
、Boolean
、
引用數(shù)據(jù)類型有:Object
(其中包含Array
, Object
, Date
, RegExp
)
特殊數(shù)據(jù)類型:Null
、Undefined
ES6 引入了一種新的原始數(shù)據(jù)類型 Symbol
,表示獨一無二的值。所以現(xiàn)在JavaScript中一共有7種數(shù)據(jù)類型。
其中特殊類型Undefined
是變量聲明了但是沒有賦值,Null
表示一個無的對象是一個Object
,這兩者在判斷語句中都會被轉(zhuǎn)成false
,其實都是表示沒有,但為什么要有兩個,說起來比較復(fù)雜,可以看看阮老師的文章undefined與null的區(qū)別。
繼續(xù)往下說,下面簡單說說基本數(shù)據(jù)類型和引用數(shù)據(jù)類型的區(qū)別。
最大的區(qū)別就是,基本數(shù)據(jù)類型的變量標(biāo)識符和變量值都存在棧內(nèi)存,而引用類型在棧內(nèi)存中存儲的是變量標(biāo)識符和指針,然后在堆內(nèi)存中保存對象內(nèi)容。如下圖:
現(xiàn)在開始說拷貝的問題,基本數(shù)據(jù)類型,我們直接
let a = 1;
let a1 = a;
就可以了。而如果是引用類型這樣的話
let b = {a: 1};
let b1 = b;
此時b
和b1
的值就會互相影響也就是改變了他們中任何一個值,另外一個值也會相應(yīng)的改變。
來個圖:
由此可見使用=
的方式對于基本類型是有效的而對于引用類型是不可以的。
在jQuery中我們可以直接使用jQuery的extend()
方法來進行深拷貝。
使用原生JavaScript我們應(yīng)該這么做呢?
如果是Array
,且數(shù)組中的數(shù)據(jù)都是基本數(shù)據(jù)類型,我們可以利用數(shù)組中的slice()
方法來返回一個新的數(shù)組,像這樣:
let arrA = [1, 2, 3, 4, 5, 6, 7, 8, 9];
let arrA1 = arrA.slice();
如果是Object
的keyValuePair,且對應(yīng)的value都是基本數(shù)據(jù)類型,則可以用Object.assign()
來操作。
let a = {a: 1};
let a1 = Object.assign({}, a);
上述兩種方法,只能在特定的情況下有用,一旦數(shù)組的值是引用類型或者對象中value的值是一個引用類型,就不起作用了。如果遇到復(fù)雜的數(shù)據(jù)類型該怎么進行深拷貝呢?以前在不少帖子里看過說使用JSON來做個中轉(zhuǎn),類似于這樣:
let a = {a: {a : {a: 1}}};
let a1 = JSON.parse(JSON.stringify(a));
這個方法好像很無敵,貌似無論多么復(fù)雜的數(shù)據(jù)類型都能深拷貝,但是一旦遇到key對應(yīng)的value是Function或者其他JSON不支持的類型就不行了,可以參照stackoverflow上的這個回答javascript deep copy using JSON
所以在JavaScript中深拷貝是一件非常費事的事情。大部分情況下我們并不需要深拷貝,再仔細(xì)看看自己寫的函數(shù)非得深拷貝不可嗎?
恩,如果你非要!
- 看看jQuery
extend()
方法的具體實現(xiàn),這里有個參考資料jQuery源碼 - 使用lodash的
cloneDeep()
方法實現(xiàn)比較復(fù)雜,當(dāng)然也比較好 - 再貼一個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;
}
- 自己擼一個...
- 待我有空擼一個(逃