日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

JS 深拷贝与浅拷贝

發布時間:2024/6/21 综合教程 27 生活家
生活随笔 收集整理的這篇文章主要介紹了 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 深拷贝与浅拷贝的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。