詳細解讀函數(shù)的底層處理機制

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)境
  • 常見上下文分類:
  1. 全局上下文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. 對象類型值:按照堆內存地址來操作
    @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)
image.png
文字版:

再重復一邊

  • 計算機會開辟兩兩個空間:棧內存、堆內存
    @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
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容