JS 深拷贝与浅拷贝
寫在前面:
在了解深淺拷貝之前,我們先來了解一下堆棧。
堆棧是一種數(shù)據(jù)結(jié)構(gòu),在JS中
棧:由編譯器自動分配釋放 ,存放函數(shù)的參數(shù)值,局部變量的值等。
讀寫快,但是存儲的內(nèi)容較少
堆:一般由程序員分配釋放, 若程序員不釋放,程序結(jié)束時可能由OS回收(垃圾回收機(jī)制)
讀寫慢,但是可以存儲較多的內(nèi)容
(!!注意:若堆中已動態(tài)分配的內(nèi)存,在使用完之后由于某種原因沒有被釋放或者無法釋放,就會造成系統(tǒng)內(nèi)存的浪費,導(dǎo)致程序運行速度降低甚至崩潰,這種情況稱為內(nèi)存泄漏!!)
JS數(shù)據(jù)按照在內(nèi)存中的存儲形式可以分為兩種:
基本數(shù)據(jù)類型(存儲在棧中):string,number,boolean,undefined,null,symbol,以及復(fù)雜類型的指針
復(fù)雜數(shù)據(jù)類型(存儲在堆中):object,array,function 等
棧內(nèi)存和堆內(nèi)存
當(dāng)我們創(chuàng)建變量并賦值的時候,如果是基本數(shù)據(jù)類型會直接存儲在棧中,
而復(fù)雜數(shù)據(jù)類型會存儲在堆中, 當(dāng)我們將復(fù)雜數(shù)據(jù)類型賦值給某個變量時,只是將該數(shù)據(jù)在堆中的地址,賦值給了這個變量(指針)。這個變量(指針)存儲了一個指向堆中數(shù)據(jù)的地址,一般稱之為指針
淺拷貝
堆棧中數(shù)據(jù)的拷貝,如果是基本數(shù)據(jù)類型,那么拷貝的就是數(shù)據(jù);
1 var a = 10;
2 var b = a;
3 console.log('改變前 打印變量 a b');
4 console.log(a); // 10
5 console.log(b); // 10
6
7 var c = 10;
8 var d = c; // 拷貝的是數(shù)據(jù)
9 d = 20; // 修改拷貝之后的數(shù)據(jù)
10 console.log("打印變量 c d");
11 console.log(c); // 10
12 console.log(d); // 20
如果拷貝的書復(fù)雜數(shù)據(jù)類型,以對象為例,當(dāng)我們想通過簡單的賦值方式拷貝一個對對象時,
例如:
1 //復(fù)雜數(shù)據(jù)類型的拷貝
2 var obj1 = { a: 10 };
3 var obj2 = obj;
4 console.log(obj1); // { a: 10 }
5 console.log(obj2); // { a: 10 }
6 //到這里 我們可以看到obj1和obj2 的打印結(jié)果完全相同 也許我們完成了數(shù)據(jù)的拷貝,
7 //但是當(dāng)我們修改拷貝過來的對象的數(shù)據(jù)時就會出現(xiàn)一個問題
8 obj2.a = 20; // 修改拷貝之后的數(shù)據(jù)
9 console.log(obj1); // { a: 20 }
10 console.log(obj2); // { a: 20 }
11 //此處我們將obj2的a 修改為了20 但是當(dāng)我們打印這個對象時 , 發(fā)現(xiàn) obj1 中的 a 也被改變了
12 // 思考: 為什么會發(fā)生這種情況??
分析: 文章開頭關(guān)于堆棧的描述中,有提到過當(dāng)我們新建對象并賦值給一個變量(var obj1 = { a: 10 })的時候,該變量存儲的不是對象的數(shù)據(jù),而是該對象在堆中的地址。因此當(dāng)我們通過這種簡單的方式(obj2 = obj;)拷貝復(fù)雜數(shù)據(jù)類型時,只是拷貝了指針中的地址而已,當(dāng)你通過原引用修改了對象中的數(shù)據(jù),另一個也會感知到這個對象的變化。這種行為被稱為淺拷貝
復(fù)雜數(shù)據(jù)類型通過普通方式(obj1=obj2)拷貝的是指針,兩個指針引用地址相同,讀取操作的都是同一個數(shù)據(jù)。
一般情況下,等號賦值,函數(shù)傳參,都是淺拷貝,也就是只拷貝了數(shù)據(jù)的地址。
1 let foo = {title: "hello obj"}
2
3 // 等號賦值
4 let now = foo;
5 now.title = "this is new title";
6
7 console.log(foo.title); // this is new title
8 console.log(now.title); // this is new title
9
10 // 函數(shù)傳參
11 function change(o) {
12 o.title = "this is function change title";
13 }
14 change(foo);
15 console.log(foo.title); // this is function change title
如何實現(xiàn)深拷貝?
所謂對象的拷貝,其實就是基于復(fù)雜數(shù)據(jù)在拷貝時的異常處理,我們將復(fù)雜數(shù)據(jù)的默認(rèn)拷貝定義為淺拷貝;就是只拷貝復(fù)雜數(shù)據(jù)的地址,而不拷貝值。那么與之對應(yīng)的就是不拷貝地址,只拷貝值,也被稱為深拷貝。
1.函數(shù)遞歸方式
1 //代碼分析: 形參obj 代表被拷貝目標(biāo), 調(diào)用函數(shù) 傳入拷貝目標(biāo),
2 // 通過Array.isArray(obj)判斷obj的類型是否為數(shù)組
3 // 通過 for in 遍歷拷貝目標(biāo),
4 // 使用 typeof 判斷其每一個元素或者屬性, 是否為obj類型(typeof Array/Object 返回值皆為object)
5 // 若該屬性/元素, 部位null 并且 typeof返回值為object, 則代表其為復(fù)雜數(shù)據(jù)類型, 遞歸調(diào)用 deepCopy(obj[key]),繼續(xù)拷貝其內(nèi)部
6 // 否則: 代表該元素非 數(shù)組 非對象, 為基本數(shù)據(jù)類型/函數(shù) 等 , 直接賦值拷貝即可
7 // 最后返回拷貝完成的result ,函數(shù)執(zhí)行完畢
8 function deepCopy(obj) {
9 var result = Array.isArray(obj) ? [] : {};
10 for (var key in obj) {
11 if (typeof obj[key] === 'object' && obj[key] !== null) {
12 result[key] = deepCopy(obj[key]); //遞歸復(fù)制
13 } else {
14 result[key] = obj[key];
15 }
16 }
17 return result;
18 }
2.利用JS中對JSON的解析方法
什么是JSON?
JSON(JavaScriptObjectNotation) 是一種輕量級的存儲和傳輸數(shù)據(jù)的格式。經(jīng)常在數(shù)據(jù)從服務(wù)器發(fā)送到網(wǎng)頁時使用。
JavaScript JSON方法
JSON.stringify(value) 方法用于將 JavaScript 值轉(zhuǎn)換為 JSON 字符串,并返回該字符串。
JSON.parse(value) 用于將一個 JSON 字符串轉(zhuǎn)換為對象 并返回該對象。
1 let obj = {
2 title: {
3 sTitle: 0,
4 list: [1, 2, { a: 3, b: 4 }]
5 }
6 }
7 let obj2 = JSON.parse(JSON.stringify(obj));
8 //通過JSON的方式對數(shù)據(jù)進(jìn)行處理轉(zhuǎn)換時, 不是改變原數(shù)據(jù), 而是在內(nèi)存中開辟一個新空間來存儲轉(zhuǎn)換的數(shù)據(jù),
9 //這樣兩次轉(zhuǎn)換后, 返回的數(shù)據(jù) ,與原數(shù)據(jù)內(nèi)容相同但是存儲地址不同, 不存在引用關(guān)系
10 console.log(obj,obj2);
11 // 深拷貝成功
12 console.log(foo === now); // false
缺陷: 受json數(shù)據(jù)的限制,無法拷貝函數(shù),undefined,NaN屬性
1 let obj={
2 a:10,
3 b:[1,2,3,{c:10}],
4 d:undefined,
5 e(){
6 console.log(this.a);
7 },
8 f:NaN
9 }
10 let obj2 = JSON.parse(JSON.stringify(obj));
11 console.log(obj,obj2);
3.利用ES6 提供的 Object.assign()
只能可以拷貝一層數(shù)據(jù),無法拷貝多層數(shù)據(jù),內(nèi)層依然為淺拷貝
1 let foo = {
2 title:{
3 show:function(){},
4 num:NaN,
5 empty:undefined
6 }
7 }
8
9 let now = {};
10
11 Object.assign(now, foo);
12
13 console.log(foo); // {title: {{num: NaN, empty: undefined, show: ?}}}
14 console.log(now); // {title: {{num: NaN, empty: undefined, show: ?}}}
15
16 // 外層對象深拷貝成功
17 console.log(foo === now); // false
18 // 內(nèi)層對象依然是淺拷貝
19 console.log(foo.title === now.title); // true
4. 利用ES6 提供的展開運算符:...
1 let foo = {
2 title:{
3 show:function(){},
4 num:NaN,
5 empty:undefined
6 }
7 }
8
9 let now = {...foo};
10
11 console.log(foo); // {title: {{num: NaN, empty: undefined, show: ?}}}
12 console.log(now); // {title: {{num: NaN, empty: undefined, show: ?}}}
13
14 // 外層對象深拷貝成功
15 console.log(foo === now); // false
16 // 內(nèi)層對象依然是淺拷貝
17 console.log(foo.title === now.title); // true
5.使用函數(shù)庫lodash中的cloneDeep()方法
使用方法:
1.下載模塊
1 cnpm i lodash --save 2 yarn add lodash
2.引入模塊
1 import _ from 'lodash'
3.使用
1 let obj1 = loodash.cloneDeep(obj)
6. 使用immutable-js
【簡單學(xué)習(xí)】https://www.jianshu.com/p/2ae0507ed86d
【常見API簡介】https://segmentfault.com/a/1190000010676878
其他(參考用, 仍可完善)
1 function cloneObj(source, target) {
2 // 如果目標(biāo)對象不存在,根據(jù)源對象的類型創(chuàng)建一個目標(biāo)對象
3 if (!target) target = new source.constructor();
4 // 獲取源對象的所有屬性名,包括可枚舉和不可枚舉
5 var names = Object.getOwnPropertyNames(source);
6 // 遍歷所有屬性名
7 for (var i = 0; i < names.length; i++) {
8 // 根據(jù)屬性名獲取對象該屬性的描述對象,描述對象中有configurable,enumerable,writable,value
9 var desc = Object.getOwnPropertyDescriptor(source, names[i]);
10 // 表述對象的value就是這個屬性的值
11 // 判斷屬性值是否不是對象類型或者是null類型
12 if (typeof desc.value !== "object" || desc.value === null) {
13 // 定義目標(biāo)對象的屬性名是names[i],值是上面獲取該屬性名的描述對象
14 // 這樣可以將原屬性的特征也復(fù)制了,比如原屬性是不可枚舉,不可修改,這里都會定義一樣
15 Object.defineProperty(target, names[i], desc);
16 } else {
17 // 新建一個t對象
18 var t = {};
19 // desc.value 就是源對象該屬性的值
20 // 判斷這個值是什么類型,根據(jù)類型創(chuàng)建新對象
21 switch (desc.value.constructor) {
22 // 如果這個類型是數(shù)組,創(chuàng)建一個空數(shù)組
23 case Array:
24 t = [];
25 break;
26 // 如果這個類型是正則表達(dá)式,則將原值中正則表達(dá)式的source和flags設(shè)置進(jìn)來
27 // 這兩個屬性分別對應(yīng)正則desc.value.source 正則內(nèi)容,desc.value.flags對應(yīng)修飾符
28 case RegExp:
29 t = new RegExp(desc.value.source, desc.value.flags);
30 break;
31 // 如果是日期類型,創(chuàng)建日期類型,并且把日期值設(shè)置相同
32 case Date:
33 t = new Date(desc.value);
34 break;
35 default:
36 // 如果這個值是屬于HTML標(biāo)簽,根據(jù)這個值的nodeName創(chuàng)建該元素
37 if (desc.value instanceof HTMLElement)
38 t = document.createElement(desc.value.nodeName);
39 break;
40 }
41 // 將目標(biāo)元素,設(shè)置屬性名是names[i],設(shè)置value是當(dāng)前創(chuàng)建的這個對象
42 Object.defineProperty(target, names[i], {
43 enumerable: desc.enumerable,
44 writable: desc.writable,
45 configurable: desc.configurable,
46 value: t
47 });
48 // 遞歸調(diào)用該方法將當(dāng)前對象的值作為源對象,將剛才創(chuàng)建的t作為目標(biāo)對象
49 cloneObj(desc.value, t);
50 }
51 }
52 return target;
53 }
總結(jié)
以上是生活随笔為你收集整理的JS 深拷贝与浅拷贝的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 手机电脑电视该如何实现投屏手机如何电脑和
- 下一篇: 钉钉直播最全教程电脑钉钉如何直播