前言
首先我們先來了解一下什么叫棧堆,基本數據類型與引用數據類型
1.棧(stack)和堆(heap)stack為自動分配的內存空間,它由系統自動釋放;而heap則是動態分配的內存,大小也不一定會自動釋放。
2.基本的數據類型:String, Number, boolean, Null, Undefined,Symbol(ES6新增)
特點: 存儲的是該對象的實際數據,(存放在棧中)
3.對象數據類型(也稱為引用數據類型):Array,Object,Function
特點: 存儲的是該對象在棧中引用,真實的數據存放在堆內存里,(存放在堆內存中的對象,每個空間大小不一樣,要根據情況進行特定的配置)
注:在JS中除了基本數據類型以外的都是對象,數據是對象,函數是對象,正則表達式是對象
1、區別: 淺拷貝/深度拷貝
判斷: 拷貝是否產生了新的數據還是拷貝的是數據的引用
知識點:對象數據存放的是對象在棧內存的引用,直接復制的是對象的引用
2、常用的拷貝技術
1). arr.concat(): 數組淺拷貝
2). arr.slice(): 數組淺拷貝
3).Object.assign()對象淺拷貝
4). JSON.parse(JSON.stringify(arr/obj)): 數組或對象深拷貝, 但不能處理函數數據
5). 淺拷貝包含函數數據的對象/數組
6). 深拷貝包含函數數據的對象/數組
1.淺拷貝
在ES6中,Object對象新增了一個assign方法,可以實現對象的淺復制。這里談談Object.assign方法的具體用法,因為稍后會分析jQuery的extend方法,實現的原理同Object.assign方法差不多。
對於基本數據類型來說,復制一個變量值,本質上就是copy了這個變量。一個變量值的修改,不會影響到另外一個變量。
<script type="text/javascript">
/*1.直接賦值給一個變量*/
let obj = {username:'genius'};
console.log(obj); //genius
//拷貝數組/對象 沒有生成新的數據而是復制了一份引用。
let arr = [1,4,{username:'lucas',age:23}];
let arr2 = arr;
arr2[0] = 'abcd0';
console.log(arr,arr2); //倆個輸出結果相同
/*Object.assign*/
let colors=['red','green','blue'];
let colors2=Object.assign([],colors);
colors2[0]="orange";
console.log(colors2);//['orange','green','blue']
console.log(colors);//['red','green','blue']
/*3.concat方式*/
let arr = [1,3,{username:'lucas'}];
let testArr = [2,4];
let arr2 = arr.concat(testArr); //連接函數
//arr2[2] = {username:'tiantian'};
console.log(arr2); //輸出如下:數組中有對象
// 輸出數組:
// 0:1
// 1:3
// 2:{username:'lucas'} //這個值會隨下面的更改而變
// 3:2
// 4:4
/*4.slice方式*/
console.log('-------------------');
arr2[2].username = 'fengdudu'; //修改了arr的值,因為arr2[2]等於arr[2]
console.log(arr); //和922行輸出結果一樣,只是下標2的內容改變了
let arr3 = arr.slice(); //有startindex,endindex選項
arr3[2].username = 'HHHHH'; //修改了arr的值,因為arr2[2]等於arr[2]
console.log(arr); //和922行輸出結果一樣,只是下標2的內容改變了
/*5.深度克隆*/
console.log('===================');
let arr4 = JSON.parse(JSON.stringify(arr));
console.log(arr4); //只拷貝了arr並沒有testArr的數組,所以只有下標3長度
arr4[2].username = 'duncan';
console.log(arr,arr4);
arr[2].username='MM'; //修改了arr、arr2、arr3的下標2的值。
//JSON.stringify要求你放入的是原生的JS對象或數組,不能放數組。
//JSON.stringify(arr)執行后arr的數據已經是JSON 字符串了,然后parse又轉字符串,最終拿到一個JSON字符串,然后轉換為一個對應的JS數組。
console.log('-------------------');
</script>
注:只會對只是一級屬性復制,比淺拷貝多深拷貝了一層而已,所以還是無法達到深度克隆的目的.
2.深拷貝
對於復雜數據類型來說,同基本數據類型實現的不太相同。對於復雜數據類型的復制,要注意的是,變量名只是指向這個對象的指針。當我們將保存對象的一個變量賦值給另一個變量時,實際上復制的是這個指針,而兩個變量都指向都一個對象。因此,一個對象的修改,會影響到另外一個對象。
在實際的開發項目中,前后端進行數據傳輸,主要是通過JSON實現的。
JSON對象下有兩個方法,一是將JS對象轉換成字符串對象的JSON.stringify方法;一個是將字符串對象轉換成JS對象的JSON.parse方法。
這兩個方法結合使用可以實現對象的深復制。也就是說,當我們需要復制一個obj對象時,可以先調用JSON.stringify(obj),將其轉換為字符串對象,然后再調用JSON.parse方法,將其轉換為JS對象。就可以輕松的實現對象的深復制。
例1
function deepClone(obj){ let _obj = JSON.stringify(obj), objClone = JSON.parse(_obj); return objClone } let a=[0,1,[2,3],4]; b=deepClone(a); a[0]=1; a[2][0]=1; console.log(a,b);
效果如下:

例2
1 <script type="text/javascript"> 2 /* 3 思考: 4 如何實現深度拷貝(克隆) 5 拷貝的數據里有對象/數組 6 拷貝的數據里不能有對象/數組,即使有對象/數組可以繼續遍歷對象、數組拿到里邊每一項值,一直拿到是基本數據類型,然后再去復制,就是深度拷貝。 7 */ 8 9 //知識點儲備 10 /* 11 如何判斷數據類型:arr-Array null -Null 12 1.typeof返回的數據類型有:String,Number,Boolean,Undefined,Object,Function。 13 2.Object.prototype.toString.call(obj)。 14 */ 15 16 let result = 'abcd'; 17 result = null; 18 result = [1,3]; 19 console.log(Object.prototype.toString.call(result).slice(8,-1)); //[object Array],sclice截取字符串后:Array(拿到分類)。 20 //console.log(typeof Object.prototype.toString.call(result)); //string 21 22 23 //for in 循環對象(屬性名)、數組(下標),推薦在循環對象屬性的時候,使用for...in,在遍歷數組的時候的時候使用for...of。 24 //for in 循環遍歷對象屬性名 25 let obj = {username:'zhangsan',age:22}; 26 for(let i in obj){ 27 console.log(i); //username age 28 } 29 30 //for in 循環遍歷數組下標 31 let arr = [1,3,'abc']; 32 for(let i in arr){ //數組的可以用for of對應數組值 33 console.log(i); //0 1 2 34 } 35 36 //定義檢測數據類型的功能函數 37 function checkedType(target){ 38 return Object.prototype.toString.call(target).slice(8,-1); 39 } 40 console.log(checkedType(result)); //Array 41 42 //實現深度克隆--對象/數組 43 function clone(target){ 44 //判斷拷貝的數據類型 45 //初始化變量的result 成為最終克隆的數據 46 let result,targetType = checkedType(target); 47 if(targetType === 'Object'){ 48 result = {}; 49 }else if(targetType === 'Array'){ 50 result = []; 51 }else{ 52 return target; //如果是基本數據類型:(String, Number, boolean, Null, Undefined)就直接反回去。 53 } 54 55 //遍歷目標數據 56 for(let i in target){ 57 //獲取遍歷數據結構的每一項值。 58 let value = target[i]; //i=1,i=2,i=.. 59 //判斷目標結構里的每一值是否存在對象/數組 60 if(checkedType(value) === 'Object' || checkedType(value) === 'Array'){ //如果對象OR數組里嵌套了對象/數組 61 //繼續遍歷獲取到的value值 62 result[i] = clone(value); //這個只執行一次,數組里只有一個對象 63 }else{ //獲取到的value值是基本的數據類型或者是函數。 64 result[i] = value; //因為arr3數組的下標0和1都是Number類型,只有下標2才是Object(轉去執行1046行) 65 } 66 } 67 return result; 68 } 69 let arr3 = [1,2,{username:'dudu',age:32}]; 70 let arr4 = clone(arr3); //相當於復制了一份arr3的基本數據 71 console.log(arr4); 72 arr4[2].username = 'gate'; 73 arr4[2].age = 65; 74 console.log(arr3,arr4); //arr3下標2是{username:'dudu':age:32},arr4下標2是{username:gate,age:65} 75 </script>
輸出如下:

總結基本數據類型和引用數據類型區別
1、聲明變量時內存分配不同
*原始類型:在棧中,因為占據空間是固定的,可以將他們存在較小的內存中-棧中,這樣便於迅速查詢變量的值
*引用類型:存在堆中,棧中存儲的變量,只是用來查找堆中的引用地址。
這是因為:引用值的大小會改變,所以不能把它放在棧中,否則會降低變量查尋的速度。相反,放在變量的棧空間中的值是該對象存儲在堆中的地址。地址的大小是固定的,所以把它存儲在棧中對變量性能無任何負面影響
2、不同的內存分配帶來不同的訪問機制
在javascript中是不允許直接訪問保存在堆內存中的對象的,所以在訪問一個對象時,首先得到的是這個對象在堆內存中的地址,然后再按照這個地址去獲得這個對象中的值,這就是傳說中的按引用訪問。
而原始類型的值則是可以直接訪問到的。
3、復制變量時的不同
1)原始值:在將一個保存着原始值的變量復制給另一個變量時,會將原始值的副本賦值給新變量,此后這兩個變量是完全獨立的,他們只是擁有相同的value而已。
2)引用值:在將一個保存着對象內存地址的變量復制給另一個變量時,會把這個內存地址賦值給新變量,
部分參考資料:JS基本數據類型和引用數據類型的區別
