在網(wǎng)上瀏覽了不少關(guān)于深淺拷貝的解析,在此,要總結(jié)的幾點(diǎn)內(nèi)容大致如下:
- 基本類型和引用類型
- 淺拷貝與深拷貝的區(qū)別
- 常用的淺拷貝與深拷貝的方法
基本類型和引用類型
ECMAScript 中的變量類型分為兩類:
基本類型:undefined,null,布爾值(boolean),字符串(string),數(shù)值(number)
引用類型: 統(tǒng)稱為Object類型,細(xì)分的話粘秆,有:Object類型希停,Array類型,Date類型赖舟,F(xiàn)unction類型等。
這里補(bǔ)充一下夸楣,很多人認(rèn)為null也是引用類型,因?yàn)?typeof null === 'object',之所以會這樣可能是js語言設(shè)計(jì)的缺陷宾抓,與底層的二進(jìn)制判斷有關(guān)。其實(shí)null是基本類型的變量豫喧,null不指向任何變量石洗,它是一個常量,所以說是基本類型紧显。引用類型的變量其實(shí)也是基本類型讲衫,而引用指向的對象本身才是引用類型。
原理是這樣的孵班,不同的對象在底層都表示為二進(jìn)制涉兽,在javascript中二進(jìn)制前3位都為0的話會被判斷為object類型,null的二進(jìn)制表示全為0篙程,自然前3位也是0枷畏,所以執(zhí)行typeof 時會返回''object'' —— 來自《你不知道的javascript》
基本類型的變量
基本類型的變量保存在棧內(nèi)存,棧內(nèi)存中分別存儲著變量的標(biāo)識符以及變量的值(盜圖一張如下)
var a = 'hello, world'
引用類型變量
引用類型 保存在 堆內(nèi)存 中房午, 棧內(nèi)存存儲的是變量的標(biāo)識符以及對象在堆內(nèi)存中的存儲地址矿辽,當(dāng)需要訪問引用類型(如對象,數(shù)組等)的值時郭厌,首先從棧中獲得該對象的地址指針袋倔,然后再從對應(yīng)的堆內(nèi)存中取得所需的數(shù)據(jù)。(盜圖一張如下)
var a = {name: 'jack'}
上述2種類型是如何復(fù)制的
1 基本類型的復(fù)制: 當(dāng)你把基本類型的變量復(fù)制給一個新的變量的時候折柠,相當(dāng)于把基本類型的值賦值給了新的變量宾娜。
var a = 'jack'
var b = a
console.log(a === b)
a = 'bob'
console.log(a)
console.log(b)
從上述結(jié)果可以看出,改變a 的值不會影響 b 的值扇售。
2 引用類型的復(fù)制:當(dāng)把引用類型賦值給一個新的變量的時候前塔,實(shí)際上復(fù)制了指向堆內(nèi)存的地址,原變量和新變量指向同一個對象承冰。
var a = {name: 'jack' , age: '30'}
var b = a
console.log(a === b)
a.age = 30
console.log(a)
console.log(b)
從上述結(jié)果可以看出华弓,改變a 的值,b的值也發(fā)生了改變
配圖過程如下:
淺拷貝和深拷貝的區(qū)別
對于僅僅是復(fù)制了引用(地址)困乒,換句話說寂屏,復(fù)制了之后,原來的變量和新的變量指向同一個東西,彼此之間的操作會互相影響迁霎,為 淺拷貝吱抚。
而如果是在堆中重新分配內(nèi)存,擁有不同的地址考廉,但是值是一樣的秘豹,復(fù)制后的對象與原來的對象是完全隔離,互不影響昌粤,為 深拷貝既绕。
深淺拷貝 的主要區(qū)別就是:復(fù)制的是引用(地址)還是復(fù)制的是實(shí)例。
從上述圖可以看出是深拷貝婚苹。
下面看下淺拷貝的實(shí)現(xiàn)
淺拷貝的實(shí)現(xiàn)
var obj = {
name: 'willie',
friends: ['jack', 'rose', 'bob'],
address: {
province: 'guangdong',
city: 'shenzhen'
}
}
/**
*
* 對象的潛拷貝
* @param {ojects} obj
* @returns object
*/
function shallowCopy (obj) {
var copyObject = {}
for (var attr in obj) {
if (obj.hasOwnProperty(attr)) {
copyObject[attr] = obj[attr]
}
}
return copyObject
}
var person = shallowCopy(obj)
console.log(person === obj)
person.friends.push('tom')
console.log(person)
console.log(obj)
運(yùn)行結(jié)果如下:可以看出淺拷貝對于引用類型岸更,只是復(fù)制了地址,他們指向同一個對象膊升。
深拷貝的實(shí)現(xiàn)
var obj = {
name: 'willie',
friends: ['jack', 'rose', 'bob'],
address: {
province: 'guangdong',
city: 'shenzhen'
}
}
/**
*
* 對象的深度復(fù)制:引用類型對象不會出現(xiàn)共享
* @param {object} obj
*/
function deepCopy (obj) {
// 首先判斷參數(shù)是否存在以及參數(shù)是否是一個對象
if (!obj && typeof obj !== 'object') {
throw new Error('error params')
}
// 判斷參數(shù)是對組類型還是對象類型
var deepObject = Array.isArray(obj) ? [] : {}
for (var attr in obj) {
// 判斷屬性是否為元素自身的
if (obj.hasOwnProperty(attr)) {
// 判斷屬性值存在并且屬性值還是為一個引用類型的值(數(shù)組或?qū)ο螅瑒t進(jìn)行遞歸谭企,直到拷貝到基本屬性值為止
if(obj[attr] && typeof obj[attr] === 'object') {
deepObject[attr] = deepCopy(obj[attr]) //進(jìn)行遞歸操作
} else {
// 非引用類型的值廓译,直接進(jìn)行賦值
deepObject[attr] = obj[attr]
}
}
}
return deepObject
}
var person = deepCopy(obj)
console.log(person === obj)
person.friends.push('tom')
console.log(person)
console.log(obj)
運(yùn)行結(jié)果如下:當(dāng)對引用類型進(jìn)行操作時,并不會影響另一個變量
常用的淺拷貝與深拷貝的方法
- slice 和 concat
var a = [1,2,3,4]
var b = a.slice()
console.log(a === b)
b.push(5)
console.log(a)
console.log(b)
var a = [1,2,3,4]
var b = a.concat()
console.log(a === b)
b.push(5)
console.log(a)
console.log(b)
看到以上债查,會認(rèn)為非区,slice和concat是深拷貝方法,那繼續(xù)往下看
var a = [[1,2,3],4,5];
var b = a.slice();
console.log(a === b);
b[0][0] = 6;
console.log(a);
console.log(b);
從以上結(jié)果可以看到盹廷,這兩個方法都不是真正的深拷貝征绸,concat同上。
- jQuery中的 extend 復(fù)制方法
可以用來擴(kuò)展對象俄占,這個方法可以傳入一個參數(shù):deep(true or false)管怠,表示是否執(zhí)行深復(fù)制(如果是深復(fù)制則會執(zhí)行遞歸復(fù)制)。
// 深拷貝
var obj = {name:"jack",age:24};
//extend方法缸榄,第一個參數(shù)為true渤弛,為深拷貝,為false甚带,或者沒有為淺拷貝她肯。
var obj_extend = $.extend(true,{}, obj);
console.log(obj === obj_extend); // false
obj.name = "rose";
console.log(obj); // 輸出 {name:"rose",age:24}
console.log(obj_extend); 輸出 {name:"jack",age:24}
// 淺拷貝
var obj = {name:"jack",age:24};
//extend方法,第一個參數(shù)為true鹰贵,為深拷貝晴氨,為false,或者沒有為淺拷貝碉输。
var obj_extend = $.extend(false,{}, obj);
console.log(obj === obj_extend); // false
obj.name = "rose";
console.log(obj); // 輸出 {name:"rose",age:24}
console.log(obj_extend); // 輸出 {name:"rose",age:24}
效果怎么會一樣呢
其實(shí)總結(jié)一下就是:
Array 的 slice 和 concat 方法 和 jQuery 中的 extend 復(fù)制方法籽前,他們都會復(fù)制第一層的值,對于 第一層 的值都是 深拷貝,而到 第二層 的時候 Array 的 slice 和 concat 方法就是 復(fù)制引用 聚假,jQuery 中的 extend 復(fù)制方法 則 取決于 你的 第一個參數(shù)块蚌, 也就是是否進(jìn)行遞歸復(fù)制。所謂第一層 就是 key 所對應(yīng)的 value 值是基本數(shù)據(jù)類型膘格,也就像上面例子中的name峭范、age,而對于 value 值是引用類型 則為第二層瘪贱,例如{name: 'jack', address: {province: 'guangdong', city: 'shenzhen'}}
- JSON 對象的 parse 和 stringify
JOSN 對象中的 stringify 可以把一個 js 對象序列化為一個 JSON 字符串纱控,parse 可以把 JSON 字符串反序列化為一個 js 對象,這兩個方法實(shí)現(xiàn)的是深拷貝菜秦。
var obj = {name:'jack', age:24, address : {province : 'guangdong',city : 'shenzhen'} };
var obj_json = JSON.parse(JSON.stringify(obj));
console.log(obj === obj_json);
obj.address.city = "fushan";
obj.name = "abc";
console.log(obj);
console.log(obj_json);
從以上可以看出甜害,進(jìn)行了深拷貝