1、棧和堆
JS引擎中對變量的存儲主要有兩種,棧內存和堆內存
- 棧內存:主要存儲JS中的基本數據類型的變量,包括String、Number、Boolean、undefined、Null、 Symbol和對象變量的指針。棧內存中的變量一般都是已知大小或者有範圍上限的,所以棧內存都是自動分配內存和自動釋放內存。
- 堆內存:主要存儲JS中的引用數據類型,包括Object、Array、function,他們的地址指針是存儲於棧中的。堆內存中的變量大小一般是不知道的,所以堆內存是動態分配內存,內存大小不一,也不會自動釋放內存。
從棧和堆的概念我們也可以分析出const 、let 定義的變量不能二次定義了?每次使用const或者let 去初始化一個變量的時候,會首先遍歷當前的棧內存,看看有沒有重名的變量,有的話就返回錯誤。
const 定義的基本數據類型不能修改,而const定義的引用數據類型就可以修改了? 因爲我們定義一個const對象的時候,我們說的常量其實是棧中的指針,就是const 對象對應的堆內存的這個指針是不變的,但是堆內存中的數據本身的大小或者屬性是可變的,而對於const定義的基礎變量而言,這個值就相當於const對象的指正,是不可變的。
// const 定義的對象
function fn(){
const c={name:'9'}
c.age='20'
c.name="90"
console.log("數據",c)
}
fn() // 數據 {name: "90", age: "20"}
//const 定義的基礎數據類型
function fn(){
const a = 9
a = 10
return a
}
fn() //VM1949:3 Uncaught TypeError: Assignment to constant variable.
2、賦值和淺拷貝的區別
- 賦值:當我們把一個對象賦值給一個新的變量時,賦的其實是該對象的在棧中的地址,而不是堆中的數據。也就是兩個對象指向的是同一個存儲空間,無論哪個對象發生改變,其實都是改變的存儲空間的內容,因此,兩個對象是聯動的。
- 淺拷貝是按位拷貝對象,它會創建一個新對象,這個對象有着原始對象屬性值的一份精確拷貝。如果屬性是基本類型,拷貝的就是基本類型的值;如果屬性是內存地址(引用類型),拷貝的就是內存地址 ,因此如果其中一個對象改變了這個地址,就會影響到另一個對象。即默認拷貝構造函數只是對對象進行淺拷貝複製(逐個成員依次拷貝),即只複製對象空間而不復制資源。
看個例子:
// 給對象賦值
var obj1={
name:'張三',
age:400,
like:{paly:'玩'}
}
var obj2 = obj1
obj2.name='李四'
obj2.like.paly="喫"
console.log(obj1,obj2)
// 淺拷貝
var obj1={
name:'張三',
age:400,
like:{paly:'玩'}
}
function copy(obj){
var objs={}
for(var item in obj){
objs[item]=obj[item]
}
return objs
}
var obj3 = copy(obj1)
obj3.name="老張"
obj3.like.paly="滿"
console.log(obj1,obj3)
從上述例子我們可以得出,賦值得到的新對象obj2改變原始對象obj1也會發生改變,淺拷貝出來的新對象obj3,只要第一層是基本數據類型的值,新對象改變它,原始對象不會發生改變,但是原始對象的第一層屬性是個對象的話,obj3改變這個對象,原始對象的數據也會發生改變。所以得出 淺拷貝只能拷貝第一層基本數據類型的值,改變他們的值不會影響原始對象,但是原始對象中含有對象的話,改變新對象中的對象,原始對象中的對象也會發生改變。
3、淺拷貝 和 深拷貝 的實現
淺拷貝和深拷貝只針對Object 和Array這樣的引用數據類型的。淺拷貝只複製指向某個對象的指針,而不復制對象本身,新舊對象還是共指向一塊內存,新對象改變舊對象也會隨着改變。但是深拷貝會另外創建一個一模一樣的對象,新對象跟舊對象不共享一塊內存,所以修改新對象,舊對象也不會發生改變
(1)淺拷貝的實現方式
- Object.assign()
Object.assign()可以把任意多個的源對象自身的可枚舉的屬性拷貝拷貝給目標對象,然後返回目標對象,但是Object.assign()進行的是淺拷貝,拷貝的是對象的屬性的引用,而不是對象本身。但是如果對象只有一層的時候,是深拷貝
var obj3={
name:'張三',
age:400
}
var objAssign = Object.assign(obj3)
objAssign.name='李四'
objAssign.age = 900
console.log(obj3) // {name:'張三',age:400}
console.log(objAssign) // {name:'李四',age:900}
- Array.prototype.concat()
這個方方法是將對個數組組合成一個新數組,返回一個新數組,跟Object.assign()類似,只拷貝引用不拷貝對象本身。
let arr = [1, 3, {
username: '科比'
}];
let arr2=arr.concat();
arr2[2].username = '我的';
console.log(arr);
var arr1=[1,2,34,4]
var arr2 = [99999]
var arr3 = arr1.concat(arr2)
arr3[2] = 88888
- Array.prototype.slice()
slice() 方法返回一個新的數組對象,這一對象是一個由 begin 和 end 決定的原數組的淺拷貝(包括 begin,不包括end)。原始數組不會被改變。
let arr = [1, 3, {
username: ' 科比'
}];
let arr3 = arr.slice();
arr3[2].username = '我的'
console.log(arr);
(2)深拷貝的實現方式
- JSON.parse(JSON.stringify())
用JSON.stringify將對象轉成JSON字符串,再用JSON.parse把字符串解析成對象,一去一來,新的對象產生了,而且對象會開闢新的棧,實現深拷貝。
let arr = [666, 888, {
username: ' 999'
}];
let arr2 = JSON.parse(JSON.stringify(arr));
arr2[2].username = '100';
console.log(arr, arr2)
缺點: 不能處理函數和正則,因爲這兩者基於JSON.stringify和JSON.parse處理後,得到的正則就不在是正則(變爲空對象),得到的函數不在是函數(變爲null)
let arr = [1,2,{name:'嘿嘿'},function(){}]
let arr3 = JSON.parse(JSON.stringify(arr))
arr3[2].name = '哈哈哈'
console.log(arr, arr3)
這是因爲JSON.stringify() 方法是將一個JavaScript值(對象或者數組)轉換爲一個 JSON字符串,不能接受函數。
- 手寫遞歸的方法
遞歸實現深度克隆的原理:遍歷對象、數組知道里邊都是基本數據類型,然後再去複製,就是深度拷貝。
//定義檢測數據類型的功能函數
function checkedType(target) {
return Object.prototype.toString.call(target).slice(8, -1)
}
//實現深度克隆---對象/數組
function clone(target) {
//判斷拷貝的數據類型
//初始化變量result 成爲最終克隆的數據
let result,
targetType = checkedType(target)
if (targetType === 'Object') {
result = {}
} else if (targetType === 'Array') {
result = []
} else {
return target
}
//遍歷目標數據
for (let i in target) {
//獲取遍歷數據結構的每一項值。
let value = target[i]
//判斷目標結構裏的每一值是否存在對象/數組
if (checkedType(value) === 'Object' || checkedType(value) === 'Array') {
//對象/數組裏嵌套了對象/數組
//繼續遍歷獲取到value值
result[i] = clone(value)
} else {
//獲取到value值是基本的數據類型或者是函數。
result[i] = value
}
}
return result
}
- 函數庫lodash
該函數庫也有提供_.cloneDeep用來做 Deep Copy
var _ = require('lodash');
var obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);
// false
本文參考文章:
淺拷貝與深拷貝
JS中的棧內存和堆內存
覺得他們寫的很好,很容易懂,記錄下來以防自己忘記。