JS中的堆(Heap)棧(Stack)內存
都是在計算機內存中開辟的空間
- 棧內存 Stack:ECStack(Execution [?eks??kju??n] Context Stack)
1.存儲原始值類型
2.代碼執(zhí)行的環(huán)境 - 堆內存 Heap:
1.存儲對象值類型
EC(Execution [?eks??kju??n] Context )執(zhí)行上下文:區(qū)分代碼執(zhí)行的環(huán)境
- 常見上下文分類:
- 全局上下文EC(G)
2.函數(shù)私有上下文EC(?)
3.塊級私有上下文EC(Bolck)
- 產(chǎn)生私有上下文->進棧執(zhí)行->出棧釋放(可能釋放)
- 變量對象:當前上下文中,用來存儲聲明的變量的地方
1.VO(Varibale Object):VO(G) 或者 VO(BLOCK)
2.AO(Active Object):AO(?)
GO(Global Object)全局對象
- window指向GO對象
- 全局上下文中,基于var/function聲明的變量是直接存儲到GO
對象上的,而基于let/const生命的變量才是存放在VO(G)中的
let 變量 = 值 的操作步驟
- 第一步:創(chuàng)建值
1.原始值類型:直接存儲在棧內存中,按值操作
number
string
boolean
null
undefined
Symbol
bigInt
- 對象類型值:按照堆內存地址來操作
@1、對象:開辟一個堆內存空間(16進制地址)、一次存儲對此昂的鍵值對、把空間地址賦值給變量
@2、函數(shù):內存空間存儲三部分信息
作用域[[scope]]:當前所處上下文
函數(shù)體中的代碼字符串
當作普通對象存儲的靜態(tài)屬性和方法[name&length]
- 第二步:聲明變量 declare
- 第三步:變量和值關聯(lián)在一起(賦值)defined
以代碼為例來詳細解讀函數(shù)的底層處理機制
1 、
var x = 12
let y = 12
z = 14
console.log(x)
console.log(window.x)
console.log(y)
console.log(window.y)
console.log(z)
console.log(window.z)
全局代碼執(zhí)行,形成EC(G)全局執(zhí)行上下文
- EC(G)全局執(zhí)行上下文
VO(G):全局變量對象(基于let/const聲明的全局變量存儲在這里)
y:13
window -- > GO全局對象(基于var/function聲明的全局變量存在這里)
x:12
z:14 // window.z = 14 直接設置在GO中,相當于省略了‘.window’ - 代碼執(zhí)行:
@1、console.log(x) :結果是 12 ;
首先看VO(G)中是否存在,如果不存在 再去GO中看看是否存在,如果都不存在則報錯:x is not defined
@2、console.log(window.x):結果是12;
直接到GO中找這個屬性,如果不存在,則是undefined(因為是訪問當前對象的某個成員,成員不存在的話結果是undefined,所以不是報錯)
@3、console.log(y):結果是13;
基于let聲明的變量存在VO(G)中
@4、console.log(window.y):結果是undefined;
@5、console.log(z):結果是14;直接去GO中找
@6、console.log(window.z) :結果是14;直接去GO中找
2、
let x = [12,23]
function fn(y) {
y[0] = 100
y = [100]
y[1] = 200
console.log(y)
}
fn(x)
console.log(x)

文字版:
再重復一邊
計算機會開辟兩兩個空間:棧內存、堆內存
@1、棧內存:存儲原始值(值類型)、提供代碼執(zhí)行的環(huán)境
@2、堆內存:存儲對象值類型上邊已經(jīng)提到:凡是變量=xxx 都是:
@1、先創(chuàng)建值
@2、定義變量
@3、賦值
代碼執(zhí)行步驟:
1、瀏覽器開辟一塊空間-->棧內存-->ECStack代碼執(zhí)行環(huán)境棧
2、瀏覽器開辟一塊空間-->堆內存-->-GO全局對象:16進制地址定為 0x000
@1、存放內置屬性:
setTimeout,
setInterval,
requestAnimationFrame
.......
3、最開始肯定是全局代碼執(zhí)行,形成EC(G):全局執(zhí)行上下文-->進棧執(zhí)行
@1、全局執(zhí)行上下文中,基于let/const聲明的變量要存放到全部變量對象VO(G)中
4、代碼自上而下執(zhí)行
5、[12,23]是個數(shù)組,所以開辟一個堆內存來存儲,16進制地址暫定為:0x001
@1、存儲的內容有:
0:12,
1: 23,
length:2
.....
6、定義變量x
基于let聲明,存儲到VO(G)中
7、x和0x001關臉 x --> 0x001
8.VO(G)中存儲的變量:
@1、x -------> 0x001
9、function xxx 也是定義一個變量,基于function/let聲明的而變量存在GO中
@1、函數(shù)也是一個對象,所以開一個堆內存,16進制地址為:0x002
@2、存儲的內容:
- 作用域[[scope]]:函數(shù)在哪個上下文中創(chuàng)建,那么它的作用域就是哪個上下文(為作用域鏈做鋪墊)
- 函數(shù)代碼字符串
- 當作普通對象來存放鍵值對值(靜態(tài)私有屬性方法)
(a)、name:fn
(b)、length:1(形參個數(shù))
所以函數(shù)如果不執(zhí)行,一點意義都沒有
[[重復:函數(shù)的作用域是在它創(chuàng)建的時候聲明的]]
@3、把10進制地址0x002賦值給fn
10、此時GO中存儲的屬性:
setTimeout,
setInterval,
requestAnimationFrame,
fn:0x002
..........
11、函數(shù)執(zhí)行并傳值:fn(x) ----> fn(0x001)
@1、產(chǎn)生全新的私有執(zhí)行上下文EC(FUN),然后進棧執(zhí)行[私有上下文中有個私有變量對象AO(FUN),用來存儲私有上下文中聲明的變量]
@2、代碼正式執(zhí)行前:
- 初始化作用域鏈<EC(FUN)當前自己的私有上下文,EC(G)函數(shù)的作用域(上級執(zhí)行上下文)>,明確變量的歸屬
- 初始化THIS指向:window
- 初始化aguments:{0:’0x001‘}
(aguments是類數(shù)組集合,存的是實參,不管有沒有形參,只要傳了實參,arguments中都會有值) - 形參賦值:y = 0x001
- 變量提升:無
- 形參和上下文中聲明的變量都是私有的
- 此時AO(G)中存的值是:
y ---> 0x001
@3、代碼執(zhí)行
y[0] = 100
y = [100]
y[1] = 200
console.log(y)
第一行:是自己的私有變量,地址指向0x001,所以把堆內存0x001中索引為0的值改成100
此時0x001中存儲的內容變成:
0:100,
1:23,
length:2,
......
y雖然是私有變量,但是和全局變量x指向的同一個地址,全局x的值也受影響,此時x的值為:[100,23]第二行 y = [100] :[100]是個數(shù)組,所以再重新開一個堆內存0x003,用來存儲這個數(shù)組
0:100,
length:1
.....
此時jy指向新的堆內存地址0x003第三行:根據(jù)y執(zhí)行的新地址,在0x003這個堆內存中增加了一個索引為1的值,此時0x003的存儲內容是:
0:100,
1:200,
length:2
.....第四行:輸出y,y是私有的,地址指向0x003
所以輸出值為:*[100,200]
@4、fn執(zhí)行完后,出棧釋放
12、函數(shù)執(zhí)行完,執(zhí)行全局代碼:console.log(x)
剛才已經(jīng)提到 x指向0x001
0x001中的值在fn執(zhí)行的時候已給修改成:[100,23]
全局代碼在瀏覽器關掉的時候才會釋放
以上就是整個代碼的運行機制
總結:
1、創(chuàng)建函數(shù)的過程:
@1、開辟堆內存[16進制地址]
@2、存儲的內容
- 作用域[[scope]]:在哪個上下文中創(chuàng)建的,那么它的作用于就是哪個上下文(為作用域鏈做鋪墊)
- 函數(shù)代碼字符串
- 作為普通對象有的屬性:
name:函數(shù)名
length:形參的個數(shù)
@3、把16進制空間地址賦值給變量(函數(shù)名)即可
2、普通函數(shù)執(zhí)行要做的事情:
@1、產(chǎn)生全新的私有執(zhí)行上下文EC(?),然后進棧執(zhí)行
私有上下文中有私有變量對象AO(G),用來存放私有變量
@2、代碼執(zhí)行前還要做的事情:
- 初始化作用域鏈:<EC(?)自己私有的執(zhí)行上下文,EC(G)函數(shù)的作用域(上級執(zhí)行上下文)>
- 初始化this
- 初始化arguments:類數(shù)組集合,存儲實參
- 形參賦值
- 變量提升
[[形參和在私有上下文中聲明的變量都是私有變量,存在AO中]]
@3、代碼正式執(zhí)行
@4.執(zhí)行完出棧(如果被外部占用,不出棧,類如閉包)
3、作用域鏈
<自己私有上下文,作用域(上級上下文)>
@1、在私有上下文中遇到個變量,首先看是否為私有變量(看AO中是否存在),如果是私有變量,則接下來操作的都是私有變量(和外界都沒有關系)
@2、如果不是自己私有的,則去上級上下文中查找,如果是上級的,則操作的都是上級的
@3、如果也不是上級的,則繼續(xù)找上級的上級的上下文....直到找到EC(G)為止
@4 如果EC(G)中也沒有,則:
- 如果是獲取變量值,則報錯
- 如果是設置變量值,則相當于給window設置對應的屬性
function fn() {
/* fn執(zhí)行,形成私有上下文EC(FN)
* AO(FN):
* 作用域鏈:<EC(FN),EC(G)>
* 形參賦值:--
* 變量提升:--
* 代碼執(zhí)行
*/
console.log(n) // 既不是形參,也沒有在這個上下文中聲明過,所以AO(FN)中沒有,沿著作用域鏈往上找,全局GO也沒有,所以會報錯:n is not defined
}
fn()
如果是:
function fn() {
n = 100 // 相當于給GO中設置一個屬性n,值是100 --> window.n = 100
}
fn()
console.log(n, window.n) // 100 100