在 JavaScript 中,對于Object
和Array
這類引用類型值弧可,當從一個變量向另一個變量復制引用類型值時,這個值的副本其實是一個指針劣欢,兩個變量指向同一個堆對象棕诵,改變其中一個變量,另一個也會受到影響凿将。
這種拷貝分為兩種情況:拷貝引用和拷貝實例校套,也就是我們說的淺拷貝和深拷貝。
淺拷貝(shallow copy)
拷貝原對象的引用牧抵,這是最簡單的淺拷貝笛匙。
// 對象
var o1 = {a: 1};
var o2 = o1;
console.log(o1 === o2); // =>true
o2.a = 2;
console.log(o1.a); // => 2
// 數(shù)組
var o1 = [1,2,3];
var o2 = o1;
console.log(o1 === o2); // => true
o2.push(4);
console.log(o1); // => [1,2,3,4]
拷貝原對象的實例,但是對其內部的引用類型值犀变,拷貝的是其引用妹孙,常用的就是如jquey
中的$.extend({}, obj); Array.prototype.slice()
和Array.prototype.concat()
都會返回一個數(shù)組或者對象的淺拷貝,舉個例子:
var o1 = ['darko', {age: 22}];
var o2 = o1.slice(); // 根據(jù)Array.prototype.slice()的特性获枝,這里會返回一個o1的淺拷貝對象
console.log(o1 === o2); // => false蠢正,說明o2拷貝的是o1的一個實例
o2[0] = 'lee';
console.log(o1[0]); // => "darko" o1和o2內部包含的基本類型值,復制的是其實例映琳,不會相互影響
o2[1].age = 23;
console.log(o1[1].age); // =>23 o1和o2內部包含的引用類型值机隙,復制的是其引用,會相互影響
可以通過Array.prototype.slice()
或jQuery
中的$.extend({}, obj)
完成對一個數(shù)組或者對象的淺拷貝萨西,我們也可以自己寫一個簡單淺拷貝函數(shù)來加深對淺拷貝的理解有鹿。
// 淺拷貝實現(xiàn),僅供參考
function shallowClone(source) {
if (!source || typeof source !== 'object') {
throw new Error('error arguments');
}
var targetObj = source.constructor === Array ? [] : {};
for (var keys in source) {
if (source.hasOwnProperty(keys)) {
targetObj[keys] = source[keys];
}
}
return targetObj;
}
深拷貝(deep copy)
深拷貝也就是拷貝出一個新的實例谎脯,新的實例和之前的實例互不影響葱跋,深拷貝的實現(xiàn)有幾種方法,首先我們可以借助jQuery
源梭,lodash
等第三方庫完成一個深拷貝實例娱俺。在jQuery
中可以通過添加一個參數(shù)來實現(xiàn)遞歸extend
,調用$.extend(true, {}, ...)
就可以實現(xiàn)一個深拷貝废麻。
我們也可以自己實現(xiàn)一個深拷貝的函數(shù)荠卷,通常有兩種方式,一種就是用遞歸的方式來做烛愧,還有一種是利用JSON.stringify
和JSON.parse
來做油宜,這兩種方式各有優(yōu)劣掂碱,先來看看遞歸的方法怎么做。
jQuery
中的extend
方法基本的就是按照這個思路實現(xiàn)的慎冤,但是沒有辦法處理源對象內部循環(huán)引用的問題疼燥,同時對Date
,Function
等類型值也沒有實現(xiàn)真正的深度復制蚁堤,但是這些類型的值在重新定義的時候一般都是直接覆蓋醉者,所以也不會對源對象產生影響,從一定程度上來說也算是實現(xiàn)了一個深拷貝披诗。
// 遞歸實現(xiàn)一個深拷貝
function deepClone(source){
if(!source || typeof source !== 'object'){
throw new Error('error arguments', 'shallowClone');
}
var targetObj = source.constructor === Array ? [] : {};
for(var keys in source){
if(source.hasOwnProperty(keys)){
if(source[keys] && typeof source[keys] === 'object'){
targetObj[keys] = source[keys].constructor === Array ? [] : {};
targetObj[keys] = deepClone(source[keys]);
}else{
targetObj[keys] = source[keys];
}
}
}
return targetObj;
}
// test example
var o1 = {
arr: [1, 2, 3],
obj: {
key: 'value'
},
func: function(){
return 1;
}
};
var o3 = deepClone(o1);
console.log(o3 === o1); // => false
console.log(o3.obj === o1.obj); // => false
console.log(o2.func === o1.func); // => true
還有一種實現(xiàn)深拷貝的方式是利用 JSON 對象中的parse
和stringify
撬即, JOSN對象中的stringify
可以把一個
js 對象序列化為一個 JSON 字符串,parse
可以把 JSON 字符串反序列化為一個 js 對象藤巢,通過這兩個方法搞莺,也可以實現(xiàn)對象的深復制。
我們從下面的例子就可以看到掂咒,源對象的方法在拷貝的過程中丟失了,這是因為在序列化JavaScript
對象時迈喉,所有函數(shù)和原型成員會被有意忽略绍刮,這個實現(xiàn)可以滿足一些比較簡單的情況,能夠處理 JSON 格式所能表示的所有數(shù)據(jù)類型挨摸,同時如果在對象中存在循環(huán)應用的情況也無法正確處理孩革。
// 利用JSON序列化實現(xiàn)一個深拷貝
function deepClone(source){
return JSON.parse(JSON.stringify(source));
}
var o1 = {
arr: [1, 2, 3],
obj: {
key: 'value'
},
func: function(){
return 1;
}
};
var o2 = deepClone(o1);
console.log(o2); // => {arr: [1,2,3], obj: {key: 'value'}}