Javascript/js 的淺拷貝與深拷貝(複製)學習隨筆

js變量的數據類型值分基本類型值和引用類型值。chrome

在ES6(ECMAScript6)之前,基本數據類型包括String、Number、Boolean、Undefined、Null。數組

基本類型值的複製(拷貝)

從一個變量向另外一個變量複製基本類型的值。使用賦值操做符 ' = ' 便可。函數

如: this

1 var num1 = 1, num2;
2 num2 = num1;

上述代碼定義了兩個變量num一、num2。num1初始化值是1,num2是undefined。接着把num1賦值給num2。spa

num1的值與num2的值增刪改減徹底獨立、互不影響。prototype

1 ++num1;
2 num2 = null;
3 // 2 null

拓展:基於基本類型值,ES提供了三個特殊的引用類型。String、Number、Boolean。(基本包裝類型)

1 var num3 = 3;
2 var num4 = num3.toFixed(2);
3 console.log(num3, num4); // 3 3.00

如上,變量num3包含一個數字值,數字固然屬於基本類型值啦,接着num3調用了toFixed()方法。並將返回結果保存在num4中。最後在控制檯輸出下。結果是3 3.00。固然了,沒有報錯。。3d

通常來理解,基本類型值不是對象,不該該有方法。(可是它們確實有方法.。查看它們有哪些方法的一個辦法是在chrome控制檯console.log(new Number(1))。其餘基本類型值同理。baidu/翻書/強記。do whatever you want)指針

當第二行代碼訪問num3時,訪問過程處於一種讀取模式,也就是從內存中讀取這個變量的值。此時,在後臺,大概是執行了下列的es代碼:code

1 var _num3 = new Number(3);// 建立Number類型的一個實例
2 var _num4 = _num3.toFixed(2);// 在實例上調用指定的方法
3 _num3 = null;// 銷燬這個實例
4 return _num4;// 能夠想象成在一個函數裏執行這裏的4行代碼,函數返回_num4。接着被num4接收。

這也意味着咱們能夠對基本類型值作一些擴展。好比:

1 var num5 = 1;
2 Number.prototype.addTen = function () {
3   var res = this + 10;
4   return res;
5 };
6 console.log(num5.addTen());// 11    

如上,在Number原型上定義addTen()方法,全部Number類型值均可以調用這個方法。對象

其餘基本類型值同理。

ES6規範引入了一項新特性--symbol,它也是一種基本數據類型,它的功能相似於一種標識惟一性的ID。

調用Symbol函數來建立一個Symbol實例:

1 const S1 = Symbol();
2 // 能夠在調用Symbol函數時傳入一個參數,至關於給你建立的Symbol實例一個描述信息。參數可選,能夠是任意可轉化成字符串的值。
3 const S2 = Symbol('id9527');

引用類型的複製(拷貝)

常見的引用類型包括 Object、Aarry、Date、Function、RegExp...

引用類型值是引用類型的一個實例。

經過賦值操做符‘=’複製的引用類型值。實際上覆制的是一個指針(地址)。該指針指向存儲在堆中的對象。

1 const obj1 = new Object();
2 const obj2 = obj1;
3 obj1.name = 'xm';
4 console.log(obj2.name);// xm

obj1與obj2指向同一個對象,對obj1的修改,一樣做用於obj2。

多數時候這不是咱們想要的結果。咱們須要的是兩個相互獨立而又長得如出一轍的對象。

因爲引用類型值也可能包含引用類型值。由此就派生出了淺拷貝和深拷貝。

淺拷貝

數組的淺拷貝經常使用方法:

(1)concat方法

1 const arr1 = ['a', 'b', ['c', 4]];
2 const arr2 = arr1.concat([]);
3 console.log(arr2, arr1 == arr2);// ['a', 'b', ['c', 4]] false

(2)slice方法

1 const arr1 = ['a', 'b', ['c', 4]];
2 const arr2 = arr1.slice(0);
3 console.log(arr2, arr1 == arr2);// ['a', 'b', ['c', 4]] false

(3)擴展運算符...方法

1 const arr1 = ['a', 'b', ['c', 4]];
2 const arr2 = [...arr1];
3 // const [...arr2] = arr1; // 等同於上一行
4 console.log(arr2, arr1 == arr2);// ['a', 'b', ['c', 4]] false

(4)map方法

1 const arr1 = ['a', 'b', ['c', 4]];
2 const arr2 = arr1.map(item => item);
3 console.log(arr2, arr1 == arr2);// ['a', 'b', ['c', 4]] false

(5)filter方法 把上面的map改爲filter便可。

...for循環、forEach、for of、splice、Object.values等方法都可。

對象的淺拷貝經常使用方法:

一、for in遍歷方法

 1 const obj = {
 2   say(){
 3     console.log('hello');
 4   }
 5 };
 6 const obj1 = Object.create(obj);
 7 obj1.a = '對象';
 8 obj1.b = [1, 2, 3];
 9 
10 // const obj2 = Object.create(obj); // 繼承obj的屬性、方法
11 const obj2 = {};
12 for (let p in obj1) {
13   if (obj1.hasOwnProperty(p)) {
14     obj2[p] = obj1[p];
15   }
16 }

如上,obj1的原型對象是obj,淺拷貝通常不須要拷貝原型上的屬性和方法,而for in循環能夠枚舉原型上的屬性和方法。使用hasOwnProperty()方法過濾掉原型的屬性和方法。

結果以下:

(2)Object.entries()方法

 1 const obj = {
 2   say(){
 3     console.log('hello');
 4   }
 5 };
 6 
 7 const obj1 = Object.create(obj);
 8 obj1.a = '對象';
 9 obj1.b = [1, 2, 3];
10 
11 // const obj2 = Object.create(obj); // 繼承obj的屬性、方法
12 const obj2 = {};
13 Object.entries(obj1).forEach(([key, value]) => obj2[key] = value);
結果以下:

 之因此稱爲淺拷貝,其緣由在於若是引用類型值裏包含引用類型值,上述的全部方法,在對裏層的引用類型值複製操做時,使用的仍是賦值操做符'='。以下所示:

若是修改了obj1.b的值,一樣會做用於obj2。

深拷貝

如下是實現對數組、對象深拷貝的一種方法。

採用遞歸的方式,層層遍歷。

 1 const deepClone = function handleDeepClone(obj) {
 2   if (typeof obj !== 'object' || obj === null) {
 3     return obj;
 4   }
 5 
 6   let _obj;
 7   if (obj instanceof Array) {
 8     _obj = [];
 9     obj.forEach((item, i) => _obj[i] = handleDeepClone(item));
10   } else {
11     _obj = {};
12     Object.entries(obj).map(([key, value]) => _obj[key] = handleDeepClone(value));
13   }
14 
15   return _obj;
16 };

 結果以下:

相關文章
相關標籤/搜索