一、js中的對象
談到對象的克隆,必定要說一下對象的概念。
js中的數據類型分為兩大類:原始類型和對象類型。
(1)原始類型包括:數值、字符串、布爾值、null、undefined(后兩個是特殊的原始值,這里不做詳細的說明,我的上一篇博客有談到過一些)
(2)對象類型包括:對象即是屬性的集合,當然這里又兩個特殊的對象----函數(js中的一等對象)、數組(鍵值的有序集合)。
好了既然對象分為這兩類,這兩種類型在復制克隆的時候是有很大區別的。原始類型存儲的是對象的實際數據,而對象類型存儲的是對象的引用地址(對象的實際內容單獨存放,為了減少數據開銷通常存放在內存中)。ps:說到這里,大家要知道,對象的原型也是引用對象,它把原型的方法和屬性放在內存當中,通過原型鏈的方式來指向這個內存地址。
二、克隆的概念
淺度克隆:原始類型為值傳遞,對象類型仍為引用傳遞。
深度克隆:所有元素或屬性均完全復制,與原對象完全脫離,也就是說所有對於新對象的修改都不會反映到原對象中。
三、淺克隆的表現
1,原始類型
看下面一段代碼:
//數值克隆的表現
var a="1";
var b=a;
b="2";
console.log(a);// "1"
console.log(b);// "2"
//字符串克隆的表現
var c="1";
var d=c;
d="2";
console.log(c);// "1"
console.log(d);// "2"
//字符串克隆的表現
var x=true;
var y=x;
y=false;
console.log(x);// true
console.log(y);// false
從上面的代碼大家可以看出,原始類型即使我們采用普通的克隆方式仍能得到正確的結果,原因就是原始類型存儲的是對象的實際數據。
PS:對象的簡易克隆小技巧:var tmpObj = JSON.parse(JSON.stringify(傳入對象));
2.對象類型
前面說過,函數式一等對象,當然也是對象類型,但是函數的克隆通過淺克隆即可實現
var m=function(){alert(1);};
var n=m;
n=function(){alert(2);};
console.log(m());//1
console.log(n());//2
大家能看到,我們直接通過普通賦值的方式,就實現了函數的克隆,並且不會影響之前的對象。原因就是函數的克隆會在內存單獨開辟一塊空間,互不影響。
好了,說了這個特殊的”關系戶“以后,我們來說說普通的”選手“。為了方便后續的代碼表現,我這里定義一個復雜的對象類型oPerson。下面看一下對象類型的淺復制有什么危害:
var oPerson={
oName:"rookiebob",
oAge:"18",
oAddress:{
province:"beijing"
},
ofavorite:[
"swimming",
{reading:"history book"}
],
skill:function(){
console.log("bob is coding");
}
};
function clone(obj){
var result={};
for(key in obj){
result[key]=obj[key];
}
return result;
}
var oNew=clone(oPerson);
console.log(oPerson.oAddress.province);//beijing
oNew.oAddress.province="shanghai";
console.log(oPerson.oAddress.province);//shanghai
通過上面的代碼,大家能看到,經過對象克隆以后,我修改oNew的地址,發現原對象oPerson也被修改了。這說明對象的克隆不夠徹底,那也就是說深度克隆失敗!
四、深克隆的實現
為了保證對象的所有屬性都被復制到,我們必須知道如果for循環以后,得到的元素仍是Object或者Array,那么需要再次循環,直到元素是原始類型或者函數為止。為了得到元素的類型,我們定義一個通用函數,用來返回傳入對象的類型。
//返回傳遞給他的任意對象的類
function isClass(o){
return Object.prototype.toString.call(o).slice(8,-1);
}
PS:Object.prototype.toString.call(o)能直接返回對象的類屬性,形如"[object class]"的字符串,我們通過截取class,並能知道傳入的對象是什么類型
當然這里有兩個疑問需要解釋下:
(1)為什么不直接用toString方法?這是為了防止對象中的toString方法被重寫,為了正確的調用toString()版本,必須間接的調用Function.call()方法
(2)為什么不使用typeof來直接判斷類型?因為對於Array而言,使用typeof(Array)返回的是object,所以不能得到正確的Array,這里對於后續的數組克隆將產生致命的問題。
下面就是真正的深度克隆
//深度克隆
function deepClone(obj){
var result,oClass=isClass(obj);
//確定result的類型
if(oClass==="Object"){
result={};
}else if(oClass==="Array"){
result=[];
}else{
return obj;
}
for(key in obj){
var copy=obj[key];
if(isClass(copy)=="Object"){
result[key]=arguments.callee(copy);//遞歸調用
}else if(isClass(copy)=="Array"){
result[key]=arguments.callee(copy);
}else{
result[key]=obj[key];
}
}
return result;
}
//返回傳遞給他的任意對象的類
function isClass(o){
if(o===null) return "Null";
if(o===undefined) return "Undefined";
return Object.prototype.toString.call(o).slice(8,-1);
}
var oPerson={
oName:"rookiebob",
oAge:"18",
oAddress:{
province:"beijing"
},
ofavorite:[
"swimming",
{reading:"history book"}
],
skill:function(){
console.log("bob is coding");
}
};
//深度克隆一個對象
var oNew=deepClone(oPerson);
oNew.ofavorite[1].reading="picture";
console.log(oNew.ofavorite[1].reading);//picture
console.log(oPerson.ofavorite[1].reading);//history book
oNew.oAddress.province="shanghai";
console.log(oPerson.oAddress.province);//beijing
console.log(oNew.oAddress.province);//shanghai
從上面的代碼可以看到,深度克隆的對象可以完全脫離原對象,我們對新對象的任何修改都不會反映到原對象中,這樣深度克隆就實現了。
這里要注意一點的就是:為什么deepClone這個函數中的result一定要判斷類型?這里有一種情況,如果你的result直接是{}對象,我明明傳進去的是一個數組,結果你復制完了以后,變成了一個對象了。
//深度克隆
function deepClone(obj){
var result={},oClass=isClass(obj);
// if(oClass==="Object"){
// result={};
// }else if(oClass==="Array"){
// result=[];
// }else{
// return obj;
// }
for(key in obj){
var copy=obj[key];
if(isClass(copy)=="Object"){
result[key]=arguments.callee(copy);
}else if(isClass(copy)=="Array"){
result[key]=arguments.callee(copy);
}else{
result[key]=obj[key];
}
}
return result;
}
function isClass(o){
if(o===null) return "Null";
if(o===undefined) return "Undefined";
return Object.prototype.toString.call(o).slice(8,-1);
}
//克隆一個數組
var arr=["a","b","c"];
var oNew=deepClone(arr);
console.log(oNew);//Object {0: "a", 1: "b", 2: "c"}
作為知識點記錄,轉載自:http://www.2cto.com/kf/201409/332955.html