php深浅拷贝,JavaScript 中的深浅拷贝
工作中經(jīng)常會(huì)遇到需要復(fù)制 JavaScript 數(shù)據(jù)的時(shí)候,遇到 bug 時(shí)實(shí)在令人頭疼;面試中也經(jīng)常會(huì)被問(wèn)到如何實(shí)現(xiàn)一個(gè)數(shù)據(jù)的深淺拷貝,但是你對(duì)其中的原理清晰嗎?一起來(lái)看一下吧!
一、為什么會(huì)有深淺拷貝
想要更加透徹的理解為什么 JavaScript 會(huì)有深淺拷貝,需要先了解下 JavaScript 的數(shù)據(jù)類(lèi)型有哪些,一般分為基本類(lèi)型(Number、String、Null、Undefined、Boolean、Symbol )和引用類(lèi)型(對(duì)象、數(shù)組、函數(shù))。
基本類(lèi)型是不可變的,任何方法都無(wú)法改變一個(gè)基本類(lèi)型的值,也不可以給基本類(lèi)型添加屬性或者方法。但是可以為引用類(lèi)型添加屬性和方法,也可以刪除其屬性和方法。
基本類(lèi)型和引用類(lèi)型在內(nèi)存中的存儲(chǔ)方式也大不相同,基本類(lèi)型保存在棧內(nèi)存中,而引用類(lèi)型保存在堆內(nèi)存中。為什么要分兩種保存方式呢? 因?yàn)楸4嬖跅?nèi)存的必須是大小固定的數(shù)據(jù),引用類(lèi)型的大小不固定,只能保存在堆內(nèi)存中,但是我們可以把它的地址寫(xiě)在棧內(nèi)存中以供我們?cè)L問(wèn)。
說(shuō)來(lái)這么多,我們來(lái)看個(gè)示例:
let num1 = 10;
let obj1 = {
name: "hh"
}
let num2 = num1;
let obj2 = obj1;
num2 = 20;
obj2.name = "kk";
console.log(num1); // 10
console.log(obj1.name); // kk
執(zhí)行完這段代碼,內(nèi)存空間里是這樣的:
可以看到 obj1 和 obj2 都保存了一個(gè)指向該對(duì)象的指針,所有的操作都是對(duì)該引用的操作,所以對(duì) obj2 的修改會(huì)影響 obj1。
小結(jié):
之所以會(huì)出現(xiàn)深淺拷貝,是由于 JS 對(duì)基本類(lèi)型和引用類(lèi)型的處理不同。基本類(lèi)型指的是簡(jiǎn)單的數(shù)據(jù)段,而引用類(lèi)型指的是一個(gè)對(duì)象保存在堆內(nèi)存中的地址,JS 不允許我們直接操作內(nèi)存中的地址,也就是說(shuō)不能操作對(duì)象的內(nèi)存空間,所以,我們對(duì)對(duì)象的操作都只是在操作它的引用而已。
在復(fù)制時(shí)也是一樣,如果我們復(fù)制一個(gè)基本類(lèi)型的值時(shí),會(huì)創(chuàng)建一個(gè)新值,并把它保存在新的變量的位置上。而如果我們復(fù)制一個(gè)引用類(lèi)型時(shí),同樣會(huì)把變量中的值復(fù)制一份放到新的變量空間里,但此時(shí)復(fù)制的東西并不是對(duì)象本身,而是指向該對(duì)象的指針。所以我們復(fù)制引用類(lèi)型后,兩個(gè)變量其實(shí)指向同一個(gè)對(duì)象,所以改變其中的一個(gè)對(duì)象,會(huì)影響到另外一個(gè)。
二、深淺拷貝
1. 淺拷貝
淺拷貝只是復(fù)制基本類(lèi)型的數(shù)據(jù)或者指向某個(gè)對(duì)象的指針,而不是復(fù)制對(duì)象本身,源對(duì)象和目標(biāo)對(duì)象共享同一塊內(nèi)存;若對(duì)目標(biāo)對(duì)象進(jìn)行修改,存在源對(duì)象被篡改的可能。
我們來(lái)看下淺拷貝的實(shí)現(xiàn):
/* sourceObj 表示源對(duì)象
* 執(zhí)行完函數(shù),返回目標(biāo)對(duì)象
*/
function shadowClone (sourceObj = {}) {
let targetObj = Array.isArray(sourceObj) ? [] : {};
let copy;
for (var key in sourceObj) {
copy = sourceObj[key];
targetObj[key] = copy;
}
return targetObj;
}
// 定義 source
let sourceObj = {
number: 1,
string: 'source1',
boolean: true,
null: null,
undefined: undefined,
arr: [{name: 'arr1'}, 1],
func: () => 'sourceFunc1',
obj: {
string: 'obj1',
func: () => 'objFunc1'
}
}
// 拷貝sourceObj
let copyObj = shadowClone(sourceObj);
// 修改 sourceObj
copyObj.number = 2;
copyObj.string = 'source2';
copyObj.boolean = false;
copyObj.arr[0].name = 'arr2';
copyObj.func = () => 'sourceFunc2';
copyObj.obj.string = 'obj2';
copyObj.obj.func = () => 'objFunc2';
// 執(zhí)行
console.log(sourceObj);
/* {
number: 1,
string: 'source1',
boolean: true,
null: null,
undefined: undefined,
arr: [{name: 'arr2'}],
func: () => 'sourceFunc1',
obj: {
func: () => 'objFunc2',
string: 'obj2'
}
}
*/
2. 深拷貝
深拷貝能夠?qū)崿F(xiàn)真正意義上的對(duì)象的拷貝,實(shí)現(xiàn)方法就是遞歸調(diào)用“淺拷貝”。深拷貝會(huì)創(chuàng)造一個(gè)一模一樣的對(duì)象,其內(nèi)容地址是自助分配的,拷貝結(jié)束之后,內(nèi)存中的值是完全相同的,但是內(nèi)存地址是不一樣的,目標(biāo)對(duì)象跟源對(duì)象不共享內(nèi)存,修改任何一方的值,不會(huì)對(duì)另外一方造成影響。
/* sourceObj 表示源對(duì)象
* 執(zhí)行完函數(shù),返回目標(biāo)對(duì)象
*/
function deepClone (sourceObj = {}) {
let targetObj = Array.isArray(sourceObj) ? [] : {};
let copy;
for (var key in sourceObj) {
copy = sourceObj[key];
if (typeof(copy) === 'object') {
if (copy instanceof Object) {
targetObj[key] = deepClone(copy);
} else {
targetObj[key] = copy;
}
} else if (typeof(copy) === 'function') {
targetObj[key] = eval(copy.toString());
} else {
targetObj[key] = copy;
}
}
return targetObj;
}
// 定義 sourceObj
let sourceObj = {
number: 1,
string: 'source1',
boolean: true,
null: null,
undefined: undefined,
arr: [{name: 'arr1'}],
func: () => 'sourceFunc1',
obj: {
string: 'obj1',
func: () => 'objFunc1'
}
}
// 拷貝sourceObj
let copyObj = deepClone(sourceObj);
// 修改 source
copyObj.number = 2;
copyObj.string = 'source2';
copyObj.boolean = false;
copyObj.arr[0].name = 'arr2';
copyObj.func = () => 'sourceFunc2';
copyObj.obj.string = 'obj2';
copyObj.obj.func = () => 'objFunc2';
// 執(zhí)行
console.log(sourceObj);
/* {
number: 1,
string: 'source1',
boolean: true,
null: null,
undefined: undefined,
arr: [{name: 'arr1'}],
func: () => 'sourceFunc1',
obj: {
func: () => 'objFunc1',
string: 'obj1'
}
}
*/
兩個(gè)方法可以合并在一起:
/* deep 為 true 表示深復(fù)制,為 false 表示淺復(fù)制
* sourceObj 表示源對(duì)象
* 執(zhí)行完函數(shù),返回目標(biāo)對(duì)象
*/
function clone (deep = true, sourceObj = {}) {
let targetObj = Array.isArray(sourceObj) ? [] : {};
let copy;
for (var key in sourceObj) {
copy = sourceObj[key];
if (deep && typeof(copy) === 'object') {
if (copy instanceof Object) {
targetObj[key] = clone(deep, copy);
} else {
targetObj[key] = copy;
}
} else if (deep && typeof(copy) === 'function') {
targetObj[key] = eval(copy.toString());
} else {
targetObj[key] = copy;
}
}
return targetObj;
}
三、使用技巧
1. concat()、slice()
(1)若拷貝數(shù)組是純數(shù)據(jù)(不含對(duì)象),可以通過(guò)concat() 和 slice() 來(lái)實(shí)現(xiàn)深拷貝;
let a = [1, 2];
let b = [3, 4];
let copy = a.concat(b);
a[1] = 5;
b[1] = 6;
console.log(copy);
// [1, 2, 3, 4]
let a = [1, 2];
let copy = a.slice();
copy[0] = 3;
console.log(a);
// [1, 2]
(2)若拷貝數(shù)組中有對(duì)象,可以使用 concat() 和 slice() 方法來(lái)實(shí)現(xiàn)數(shù)組的淺拷貝。
let a = [1, {name: 'hh1'}];
let b = [2, {name: 'kk1'}];
let copy = a.concat(b);
copy[1].name = 'hh2';
copy[3].name = 'kk2';
console.log(copy);
// [1, {name: 'hh2'}, 2, {name: 'kk2'}]
無(wú)論 a[1].name 或者 b[1].name 改變,copy[1].name 的值都會(huì)改變。
let a = [1, {name: 'hh1'}];
let copy = a.slice();
copy[1].name = 'hh2';
console.log(a);
// [1, {name: 'hh2'}]
改變了 a[1].name 后,copy[1].name 的值也改變了。
2. Object.assign()、Object.create()
Object.assign()、Object.create() 都是一層(根級(jí))深拷貝,之下的級(jí)別為淺拷貝。
(1) 若拷貝對(duì)象只有一級(jí),可以通過(guò) Object.assign()、Object.create() 來(lái)實(shí)現(xiàn)對(duì)象的深拷貝;
let sourceObj = {
str: 'hh1',
number: 10
}
let targetObj = Object.assign({}, sourceObj)
targetObj.str = 'hh2'
console.log(sourceObj);
// {str: 'hh1', number: 10}
let sourceObj = {
str: 'hh1',
number: 10
}
let targetObj = Object.create(sourceObj)
targetObj.str = 'hh2'
console.log(sourceObj);
// {str: 'hh1', number: 10}
(2) 若拷貝對(duì)象有多級(jí), Object.assign()、Object.create() 實(shí)現(xiàn)的是對(duì)象的淺拷貝。
let sourceObj = {
str: 'hh',
number: 10,
obj: {
str: 'kk1'
}
}
let targetObj = Object.assign({}, sourceObj)
targetObj.obj.str = 'kk2'
console.log(sourceObj);
// {
// str: 'hh',
// number: 10,
// obj: {
// str: 'kk2'
// }
// }
let sourceObj = {
str: 'hh',
number: 10,
obj: {
str: 'kk1'
}
}
let targetObj = Object.create(sourceObj)
targetObj.obj.str = 'kk2'
console.log(sourceObj);
// {
// str: 'hh',
// number: 10,
// obj: {
// str: 'kk2'
// }
// }
修改了 targetObj.obj.str 的值之后,sourceObj.obj.str 的值也改變了。
3. 對(duì)象的解構(gòu)
對(duì)象的解構(gòu)同 Object.assign() 和 Object.create(),都是一層(根級(jí))深拷貝,之下的級(jí)別為淺拷貝。
(1)若拷貝對(duì)象只有一層,可以通過(guò)對(duì)象的解構(gòu)來(lái)實(shí)現(xiàn)深拷貝;
let sourceObj = {
str: 'hh1',
number: 10
}
let targetObj = {...sourceObj};
targetObj.str = 'hh2'
console.log(sourceObj);
// {str: 'hh1', number: 10}
(2)若拷貝對(duì)象有多層,通過(guò)對(duì)象的解構(gòu)實(shí)現(xiàn)的是對(duì)象的淺拷貝。
let sourceObj = {
str: 'hh',
number: 10,
obj: {
str: 'kk1'
}
}
let targetObj = {...sourceObj};
targetObj.obj.str = 'kk2'
console.log(sourceObj);
// {
// str: 'hh',
// number: 10,
// obj: {
// str: 'kk2'
// }
// }
4. JSON.parse()
用 JSON.stringify() 把對(duì)象轉(zhuǎn)成字符串,再用 JSON.parse() 把字符串轉(zhuǎn)成新的對(duì)象,可以實(shí)現(xiàn)對(duì)象的深復(fù)制。
let source = ['hh', 1, [2, 3], {name: 'kk1'}];
let copy = JSON.parse(JSON.stringify(source));
copy[2][1] = 4;
copy[3].name = 'kk2';
console.log(source);
// ['hh', 1, [2, 3], {name: 'kk1'}]
可以看出,雖然改變了 copy[2].name 的值,但是 source[2].name 的值沒(méi)有改變。
JSON.parse(JSON.stringify(obj)) 不僅能復(fù)制數(shù)組還可以復(fù)制對(duì)象,但是幾個(gè)弊端:
1)它會(huì)拋棄對(duì)象的 constructor,深拷貝之后,不管這個(gè)對(duì)象原來(lái)的構(gòu)造函數(shù)是什么,在深拷貝之后都會(huì)變成 Object;
2)這種方法能正確處理的對(duì)象只有?Number, String, Boolean, Array, 扁平對(duì)象,即那些能夠被 json 直接表示的數(shù)據(jù)結(jié)構(gòu)。RegExp 對(duì)象是無(wú)法通過(guò)這種方式深拷貝。
3)只有可以轉(zhuǎn)成 JSON 格式的對(duì)象才可以這樣用,像 function 沒(méi)辦法轉(zhuǎn)成 JSON。
5. 可以使用的庫(kù)
以下兩種庫(kù)都能實(shí)現(xiàn)深淺拷貝,有各自的使用方法。
jQuery
具體使用可以參考:官方文檔
Lodash
具體使用可以參考:官方文檔
總結(jié)
以上是生活随笔為你收集整理的php深浅拷贝,JavaScript 中的深浅拷贝的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: HDU 3966 Aragorn's S
- 下一篇: 计算机abcd地址,IP地址ABCDE的