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