日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

js变量后面加问号是什么_js没那么简单(1)-- 执行上下文

發布時間:2025/5/22 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 js变量后面加问号是什么_js没那么简单(1)-- 执行上下文 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

我為什么寫這個文章?也許換個耳熟能詳的話題會有更多人看吧。之前發了個tls感覺閱讀量不行。

要講ecma語法嗎?我覺得還是不了吧,畢竟這些繁瑣,枯燥,而且門檻低。

那講什么好?講一點我自己覺得大家都知道,但是可能理解不到位都東西。

我自己理解到位嗎?我想不一定很到位,但是一定很有思考價值。

這是一個系列?它可能是一個系列,就從執行上下文和運行開始吧。

js難不難?看你自己都目標吧,我覺得沒有簡單的東西,當你思考越多,就會看到更多東西,相對以前的理解就是難的。

那就開始吧

正文

js或者ecmascript?

大家用了那么久js,有沒有搞清楚規范和實現的區別呢?ECMA這個組織定義了這個語言的規范,Javascript是這個規范的一個實現。這意味著,他可以有很多實現的可能,只是Javascript是其中一個最熱門的實現。我們通常說的ecma規范,那指的是一種口頭協議規范,通常我們說javascript語言,那指的是已經實現了ecma某個規范的一種語言。

在這個基礎上,執行上下文就是ecma規范里面提到的一個抽象概念。這意味著,這東西不是一個具體已經實現出來的東西,他僅僅只是一個抽象模型,具體在計算機內部是怎么編譯運行,以什么樣的面向對象代碼呈現,那應該是引擎(v8)實現的細節的內容。

那么執行上下文的意義在于,它可以給一個抽象模型,讓我們更簡單的預測js的運行機制。同時,執行上下文對后續理解js內存,垃圾回收,閉包等具有深刻意義,他可以幫助我們在不需要很了解基礎底層情況下去分析內存,執行過程。

js代碼是如何工作的?

為了不復雜化思路,我們可以暫時把js運行過程分成上圖三個大步驟。 1. 獲取js代碼 2. 編譯 3. 運行

編譯階段:js代碼在編譯階段(序列化-->抽象語法樹-->可執行代碼)被編譯成機器可識別大可執行代碼

運行:運行代碼

執行上下文(Execution Contexts)

執行上下文(Execution Contexts)是ECMA規范262第八章節中提出的抽象概念。這個概念定義了,js代碼在運行時,所處的上下文環境。在簡單的代碼中,我們可以簡單的理解上下文環境結構由:詞法環境(Lexical Environments)和 變量環境(Variable Environment)兩個部分。我們這里只需要關注這兩部分:

詞法環境: 詞法環境定義了由代碼編譯過程中,ecma規范詞法對應的一些關系,比如記錄函數內部的this內容,不對外暴露,可以理解為ecma內部自己的語法關系。

變量環境:變量環境指的的是在詞法環境中,代碼運行時生成的變量關系,可以理解為由我們創建的變量。

另外,我們寫的代碼,包括函數里的代碼執行,在規范中叫可執行代碼。于是,我們可以把代碼的運行流程,更細致的概括為,那么執行上下文和可以執行代碼會伴隨在js的運行周期里:

這我們在進一步的理解執行上下文,在js中,有三個比較場景會生成上下文對象: 1. 全局上下文 2. 函數上下文 3. eval上下文

所以,JS只有三種環境下會生成執行上下文,這意味著js不像c語言那樣,具有單獨塊作用域的概念,只有函數作用域和全局作用域

執行上下文的生成時機

上面我們把代碼的過程抽象成編譯時和運行時。而執行上下文會在編譯時就確定上下文關系,所以可以認為,在編譯過程中,在解析js代碼所對應的詞法關系時候,編譯器就已經確定了代碼中每個環境對應的執行上下文的關系,只是說,這時候還沒被激活。雖然這里還沒提到作用域鏈,但是我們通常把這種在詞法階段確定的關系叫做靜態。由于執行上下文中也會有作用域鏈,所以JS通常被稱為詞法作用域或者靜態作用域。

這意味著,js在編譯階段其實已經做好了很多事情,當然也包括我們常說的變量提升。 讓我們看下真實代碼中如何體現:

運行過程和調用棧

既然加執行上下文,那它必然和執行時候密切相關。相信大部分人都知道,我們常說的會說的名詞,函數調用棧。這個函數調用棧其實就是執行上下文的調用棧。我們上面提到,還有全局環境會生成全局上下文,eval環境會生成eval上下文。所以,這些上下文都會在激活時候進入調用棧。

比如,全局上下文,在編譯完,代碼開始運行時候就開始入棧,因為全局環境是最先開始運行的。

function a(){} function b(){}a() b()

如圖,對于全局環境來說,可執行代碼如圖。實際在運行時,內存里應該以機器碼形式存在。當運行到a(),a函數到執行上下文會生成然后入棧。

從執行上下文中看變量提升

變量提升是一個我們經常關注的內容,我們通常把變量提升解釋為,在js預編譯階段會對變量做一個提升,這里可以用一個簡單對demo來重現這一經典現象:

console

可以看到,在變量聲明前使用它,完全沒有問題。對于經常使用js的人,這代碼并沒有任何稀奇。

但是,如果我們更深一層的去思考,變量提升的本質是什么。我們回想上面js的運行過程。從一段js代碼,編譯成可執行代碼。我們把這個代碼帶到這個流程中去,我可以進一步把上面的代碼抽象成這樣:

入圖所示,以上js腳本代碼,通過詞法解析,編譯器會確認為該段代碼具有兩個不同的上下文環境,每一個環境中對應的內容我也標記出來了。比如全局上下文中,對應可執行代碼是:

console.log(val) add(1, 2) var val = 1

其對應的環境變量是val和add函數指針,函數指針值得是其對應的是靜態代碼區域的可執行代碼。實際上是函數上下文中對應的可執行代碼。

那么在運行時候,全局上下文首先激活入棧,然后全局的腳本代碼開始執行:

當執行到console.log時候,我們看到,雖然我們腳本代碼中val在console.log后面,但是依然打出來了undifined,而不是報錯。這是得力于詞法環境到功勞。因為js在編譯時候就幫我們生成對應到變量,只不過,其還沒有對應到值而已。

然后當游標執行到add(1, 2)時,由于函數變量也已經生成,并且由于時函數聲明形式。所以編譯器時知道函數對應到可執行代碼所處到指針,于是調用了函數,然后激活函數到上下文,并且入棧。其調用棧正如上文所示。即使函數在代碼中是在執行代碼到后面,但是得力于詞法解析到功勞,add函數變量在編譯時已經生成。

最后當執行到val = 1時候,函數先出棧,然后變量環境到val也會對應得到賦值。

這里需要說下,就是為了能夠大家看懂,我用js的方式展示執行代碼。但是實際上編譯完成的執行代碼應該是機器碼。可以看到圖中,變量環境中,兩個變量val,和add分別是undefined和一個指向函數的一個引用。

到這里,我相信應該就很容易理解,為什么會存在變量提升這樣的現象。本質上是因為js在編譯過程中的詞法解析階段,就已經生成了執行上下文的關系,所以代碼還沒運行時候,變量的環境已經創建好了,而在代碼運行時候。即使我們的執行代碼是比變量更前的,依然可以拿到變量的引用,在代碼運行時,上下文對象才會激活。

所以這一章節重點就是:上下文對象生成時機在詞法解析階段,而上下文對象激活時機在運行階段

eval環境

Eval代碼在運行時,上下文中會多一個調用所處環境多上下文引用。

變量提升的問題

變量提升可以認為是最初js設計上的一些不足,因為由上面的描述得知,這種從簡的設計導致了變量提升。這種提升會在一些可能的塊作用域中產生一些影響。比如while,for循環。對于那些曾經接觸過c或者java這類語言的人來說,js這樣簡單的只有函數作用域塊的特點會很難以理解。在for循環和while里面變量的提升,都會導致變量在全局情況下被覆蓋,無法緩存的問題。

當然后面es6也有let和const的概念去解決塊作用域的問題。但是本質上來說,變量提升不是一個很好的特性。

最后,可以通過上下文對象試著去想,閉包多本質是怎么樣的,后續有時間在討論。

總結

以上是生活随笔為你收集整理的js变量后面加问号是什么_js没那么简单(1)-- 执行上下文的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。