摘要:基本類型指的是簡單的數(shù)據(jù)段,而引用類型指的是一個(gè)對象保存在堆內(nèi)存中的地址,不允許我們直接操作內(nèi)存中的地址,也就是說不能操作對象的內(nèi)存空間,所以,我們對對象的操作都只是在操作它的引用而已。
工作中經(jīng)常會(huì)遇到需要復(fù)制 JavaScript 數(shù)據(jù)的時(shí)候,遇到 bug 時(shí)實(shí)在令人頭疼;面試中也經(jīng)常會(huì)被問到如何實(shí)現(xiàn)一個(gè)數(shù)據(jù)的深淺拷貝,但是你對其中的原理清晰嗎?一起來看一下吧!一、為什么會(huì)有深淺拷貝
想要更加透徹的理解為什么 JavaScript 會(huì)有深淺拷貝,需要先了解下 JavaScript 的數(shù)據(jù)類型有哪些,一般分為基本類型(Number、String、Null、Undefined、Boolean、Symbol )和引用類型(對象、數(shù)組、函數(shù))。
基本類型是不可變的,任何方法都無法改變一個(gè)基本類型的值,也不可以給基本類型添加屬性或者方法。但是可以為引用類型添加屬性和方法,也可以刪除其屬性和方法。
基本類型和引用類型在內(nèi)存中的存儲(chǔ)方式也大不相同,基本類型保存在棧內(nèi)存中,而引用類型保存在堆內(nèi)存中。為什么要分兩種保存方式呢? 因?yàn)楸4嬖?b>棧內(nèi)存的必須是大小固定的數(shù)據(jù),引用類型的大小不固定,只能保存在堆內(nèi)存中,但是我們可以把它的地址寫在棧內(nè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è)指向該對象的指針,所有的操作都是對該引用的操作,所以對 obj2 的修改會(huì)影響 obj1。
小結(jié):
之所以會(huì)出現(xiàn)深淺拷貝,是由于 JS 對基本類型和引用類型的處理不同。基本類型指的是簡單的數(shù)據(jù)段,而引用類型指的是一個(gè)對象保存在堆內(nèi)存中的地址,JS 不允許我們直接操作內(nèi)存中的地址,也就是說不能操作對象的內(nèi)存空間,所以,我們對對象的操作都只是在操作它的引用而已。二、深淺拷貝 1. 淺拷貝在復(fù)制時(shí)也是一樣,如果我們復(fù)制一個(gè)基本類型的值時(shí),會(huì)創(chuàng)建一個(gè)新值,并把它保存在新的變量的位置上。而如果我們復(fù)制一個(gè)引用類型時(shí),同樣會(huì)把變量中的值復(fù)制一份放到新的變量空間里,但此時(shí)復(fù)制的東西并不是對象本身,而是指向該對象的指針。所以我們復(fù)制引用類型后,兩個(gè)變量其實(shí)指向同一個(gè)對象,所以改變其中的一個(gè)對象,會(huì)影響到另外一個(gè)。
淺拷貝只是復(fù)制基本類型的數(shù)據(jù)或者指向某個(gè)對象的指針,而不是復(fù)制對象本身,源對象和目標(biāo)對象共享同一塊內(nèi)存;若對目標(biāo)對象進(jìn)行修改,存在源對象被篡改的可能。
我們來看下淺拷貝的實(shí)現(xiàn):
/* sourceObj 表示源對象 * 執(zhí)行完函數(shù),返回目標(biāo)對象 */ 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)真正意義上的對象的拷貝,實(shí)現(xiàn)方法就是遞歸調(diào)用“淺拷貝”。深拷貝會(huì)創(chuàng)造一個(gè)一模一樣的對象,其內(nèi)容地址是自助分配的,拷貝結(jié)束之后,內(nèi)存中的值是完全相同的,但是內(nèi)存地址是不一樣的,目標(biāo)對象跟源對象不共享內(nèi)存,修改任何一方的值,不會(huì)對另外一方造成影響。
/* sourceObj 表示源對象 * 執(zhí)行完函數(shù),返回目標(biāo)對象 */ 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 表示源對象 * 執(zhí)行完函數(shù),返回目標(biāo)對象 */ 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ù)(不含對象),可以通過concat() 和 slice() 來實(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ù)組中有對象,可以使用 concat() 和 slice() 方法來實(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"}]
無論 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) 若拷貝對象只有一級(jí),可以通過 Object.assign()、Object.create() 來實(shí)現(xiàn)對象的深拷貝;
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) 若拷貝對象有多級(jí), Object.assign()、Object.create() 實(shí)現(xiàn)的是對象的淺拷貝。
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. 對象的解構(gòu)對象的解構(gòu)同 Object.assign() 和 Object.create(),都是一層(根級(jí))深拷貝,之下的級(jí)別為淺拷貝。
(1)若拷貝對象只有一層,可以通過對象的解構(gòu)來實(shí)現(xiàn)深拷貝;
let sourceObj = { str: "hh1", number: 10 } let targetObj = {...sourceObj}; targetObj.str = "hh2" console.log(sourceObj); // {str: "hh1", number: 10}
(2)若拷貝對象有多層,通過對象的解構(gòu)實(shí)現(xiàn)的是對象的淺拷貝。
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() 把對象轉(zhuǎn)成字符串,再用 JSON.parse() 把字符串轉(zhuǎn)成新的對象,可以實(shí)現(xiàn)對象的深復(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 的值沒有改變。
JSON.parse(JSON.stringify(obj)) 不僅能復(fù)制數(shù)組還可以復(fù)制對象,但是幾個(gè)弊端:
1)它會(huì)拋棄對象的 constructor,深拷貝之后,不管這個(gè)對象原來的構(gòu)造函數(shù)是什么,在深拷貝之后都會(huì)變成 Object;
2)這種方法能正確處理的對象只有?Number, String, Boolean, Array, 扁平對象,即那些能夠被 json 直接表示的數(shù)據(jù)結(jié)構(gòu)。RegExp 對象是無法通過這種方式深拷貝。
3)只有可以轉(zhuǎn)成 JSON 格式的對象才可以這樣用,像 function 沒辦法轉(zhuǎn)成 JSON。
以下兩種庫都能實(shí)現(xiàn)深淺拷貝,有各自的使用方法。
jQuery具體使用可以參考:官方文檔
Lodash具體使用可以參考:官方文檔
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://hztianpu.com/yun/100282.html
摘要:開門見山,有人叫對象的復(fù)制為深復(fù)制淺復(fù)制,也有人叫深拷貝淺拷貝。高級(jí)屬性修改深拷貝滿足對象的復(fù)制,淺拷貝影響原數(shù)組。關(guān)于對象的深淺拷貝,暫且探索到這里,后續(xù)有新發(fā)現(xiàn)再進(jìn)行補(bǔ)充。 showImg(https://segmentfault.com/img/remote/1460000014305581); 開門見山,有人叫對象的復(fù)制為深復(fù)制淺復(fù)制,也有人叫深拷貝淺拷貝。其實(shí)都是copy。 ...
摘要:深淺拷貝從上面的例子可以發(fā)現(xiàn),如果給一個(gè)變量賦值一個(gè)對象,那么兩者的值會(huì)是同一個(gè)引用,其中一方改變,另一方也會(huì)相應(yīng)改變。此時(shí)需要深拷貝上場深拷貝深拷貝最簡單的實(shí)現(xiàn)辦法就是使用來解決。發(fā)現(xiàn)只拷貝了而忽略了和。 深淺拷貝 let a = { age: 1 } let b = a a.age = 2 console.log(b.age) // 2 從上面的例子可以發(fā)現(xiàn),如果給一個(gè)變量...
摘要:基本數(shù)據(jù)類型的復(fù)制很簡單,就是賦值操作,所以深淺拷貝也是針對,這類引用類型數(shù)據(jù)。它會(huì)拋棄對象的。另外,查資料過程中還看到這么一個(gè)詞結(jié)構(gòu)化克隆算法還有這一篇資料也有參考,也寫得比較詳細(xì)了的深淺拷貝 基本數(shù)據(jù)類型的復(fù)制很簡單,就是賦值操作,所以深淺拷貝也是針對Object,Array這類引用類型數(shù)據(jù)。 淺拷貝對于字符串來說,是值的復(fù)制,而對于對象來說則是對對象地址的復(fù)制;而深拷貝的話,它不...
摘要:原文地址基礎(chǔ)心法深淺拷貝歡迎。上面的代碼是最簡單的利用賦值操作符實(shí)現(xiàn)了一個(gè)淺拷貝,可以很清楚的看到,隨著和改變,和也隨著發(fā)生了變化。展開運(yùn)算符結(jié)論實(shí)現(xiàn)的是對象第一層的深拷貝。 原文地址:JavaScript基礎(chǔ)心法——深淺拷貝 歡迎star。 如果有錯(cuò)誤的地方歡迎指正。 淺拷貝和深拷貝都是對于JS中的引用類型而言的,淺拷貝就只是復(fù)制對象的引用,如果拷貝后的對象發(fā)生變化,原對象也會(huì)發(fā)生...
閱讀 1609·2021-09-10 10:51
閱讀 2909·2019-08-30 15:54
閱讀 3445·2019-08-29 17:11
閱讀 1016·2019-08-29 16:44
閱讀 1504·2019-08-29 13:47
閱讀 1162·2019-08-29 13:47
閱讀 1566·2019-08-29 12:23
閱讀 1234·2019-08-28 18:18