JavaScript程式碼是怎麼執行的?
阿新 • • 發佈:2020-10-10
前言
眾所周知,JavaScript是單執行緒語言。所以JavaScript是按順序執行的!
先編譯再執行
變數提升
請看下面的例子:
執行上下文
步驟如下: 1、建立全域性執行上下文,並將其壓入棧底。 2、執行全域性程式碼:bar()。呼叫bar函式時,JS引擎會編譯bar函式,併為其建立一個函式執行上下文。最後將其執行上下文壓入棧中,並且將變數b賦予預設值undefined。 3、執行bar函式內部的程式碼。先執行b = 1的賦值操作,然後呼叫foo函式。JS引擎編譯foo函式,併為其建立一個函式執行上下文。最後將其執行上下文壓入棧中,並且將變數a賦予預設值undefined。 4、執行foo內部的程式碼。執行a = 1賦值操作,然後輸出a的值。foo函式執行完畢後,呼叫棧就將其執行上下文從棧頂彈出。接著執行bar函式。 5、執行完bar函式後,呼叫棧就將其執行上下文從棧頂彈出。剩下全域性執行上下文 整個JavaScript流程執行就到此結束了。 呼叫棧是JS引擎追蹤函式執行的一個機制,當一次有多個函式被呼叫時,通過呼叫棧就能夠追蹤到哪個函式正在被執行以及各函式之間的呼叫關係。 var缺陷與塊級作用域 變數提升帶來的問題 1、變數被覆蓋
console.log(cat) catName("Chloe"); var cat = 'Chloe' function catName(name) { console.log("我的貓名叫 " + name); }
按照得出的結論:"JavaScript是按順序執行的"來看,步驟如下:
- 執行第一句的時候,cat並沒有定義,結果應該是丟擲一個錯誤,然後結束執行。
Uncaught ReferenceError: cat is not defined
但實際的執行結果並不是這樣: 不僅可以執行,catName()執行結果也輸出了。 這種現象就是: 變數提升 從概念的字面意義上說,“變數提升”就是把變數和函式的宣告移動到程式碼的最前面,變數被提升後,會給變數設定預設值--undefined。 調整之後的執行順序如下:
- 首先執行var cat = undefined和function catName(){}
- 然後執行console.log(cat) // undefined
- 接著呼叫catName()
- 最後給cat賦值cat = 'Chloe'
變數提升(Hoisting)被認為是, Javascript中執行上下文 (特別是建立和執行階段)工作方式的一種認識在編譯階段,JavaScript會為上述程式碼建立一個執行上下文和可執行程式碼。 執行上下文是JavaScript執行一段程式碼時的執行環境,包含this、變數、物件以及函式等。 1、在編譯階段
- JavaScript引擎會將var變數宣告和函式宣告等的變數提升內容放在變數環境中。
- 接下來JavaScript引擎會把宣告以外的程式碼編譯為位元組碼--可執行程式碼。
-
執行到console.log(cat)時,JavaScript引擎在變數環境中查詢cat這個變數,由於變數環境存在cat變數,並且其值為undefined,所以這時候就輸出undefined。
- 當執行到catName函式時,引擎在變數環境中查詢該函式,由於變數環境中存在該函式的引用,所以引擎執行該函式,並輸出執行結果。
- 執行cat賦值,引擎在變數環境查詢到cat變數,並進行賦值。
棧是電腦科學中的一種抽象資料型別,只允許在有序的線性資料集合的一端(稱為堆疊頂端,英語:top)進行加入資料(英語:push)和移除資料(英語:pop)的運算。因而按照後進先出(LIFO, Last In First Out)的原理運作在一個執行上下文建立好後,JS引擎就會它壓進棧中。管理執行上下文的棧結構就稱為呼叫棧,或者執行上下文棧。 請看下面例子:
function foo() { var a = 0 console.log(a) } function bar() { var b = 1 foo() console.log(b) } bar()
步驟如下: 1、建立全域性執行上下文,並將其壓入棧底。 2、執行全域性程式碼:bar()。呼叫bar函式時,JS引擎會編譯bar函式,併為其建立一個函式執行上下文。最後將其執行上下文壓入棧中,並且將變數b賦予預設值undefined。 3、執行bar函式內部的程式碼。先執行b = 1的賦值操作,然後呼叫foo函式。JS引擎編譯foo函式,併為其建立一個函式執行上下文。最後將其執行上下文壓入棧中,並且將變數a賦予預設值undefined。 4、執行foo內部的程式碼。執行a = 1賦值操作,然後輸出a的值。foo函式執行完畢後,呼叫棧就將其執行上下文從棧頂彈出。接著執行bar函式。 5、執行完bar函式後,呼叫棧就將其執行上下文從棧頂彈出。剩下全域性執行上下文 整個JavaScript流程執行就到此結束了。 呼叫棧是JS引擎追蹤函式執行的一個機制,當一次有多個函式被呼叫時,通過呼叫棧就能夠追蹤到哪個函式正在被執行以及各函式之間的呼叫關係。 var缺陷與塊級作用域 變數提升帶來的問題 1、變數被覆蓋
var cat = "foo" function catName(){ console.log(cat); if(false){ var cat = "bar" } console.log(cat); } catName()呼叫catName時,呼叫棧如下圖所示:
- 建立catName執行上下文時,JavaScript引擎會將var變數宣告cat提升內容放在變數環境中,賦予預設值undefined。
- 執行到catName內部的console.log(cat)時,在catName執行上下文中的變數環境找到了cat的值,輸出undefined。
- if判斷為false,不執行。
- 執行console.log(cat),參照第二步,輸出undefined。
function foo () { for (var i=0; i<10; i++){} console.log(i) } foo()
直觀的來說,會以為for迴圈結束後,i會被銷燬。結果並非如此,console.log(i)輸出10。 原因也是變數提升,在建立foo執行上下文時,i被提升了。所以for迴圈結束後,i並沒有被銷燬。 塊級作用域 儲存變數中的值以及對這個值進行訪問或修改,是程式語言的基本功能。而 作用域 則是如何儲存變數以及如何訪問這些變數的規則。 在ES6前,JavaScript只支援兩種方法建立作用域:
- 全域性作用域
- 函式作用域
var cat = "foo" function catName(){ if(true){ var cat = "bar" console.log(cat); } console.log(cat); } catName()
在這段程式碼中,有兩處聲明瞭cat變數,一處在全域性作用域,一處在catName函式作用域中的if語句裡面。 在執行if語句內部時,呼叫棧如下圖所示: 從圖中可看出兩處console.log(cat)都輸出bar。 使用let改寫上面程式碼
var cat = "foo" function catName(){ if(true){ let cat = "bar" console.log(cat); } console.log(cat); } catName()
if語句執行結束後,let宣告的cat變數就會被銷燬,第二處的console.log(cat)就會輸出foo JavaScript內部實現塊級作用域 請看下面的例子
function foo(){ var a = 1 let b = 2 { let b = 3 var c = 4 let d = 5 console.log(a) console.log(b) } console.log(b) console.log(c) console.log(d) } foo()
步驟如下: 1、第一步建立全域性執行上下文 2、執行foo(),建立foo函式的執行上下文
- 在函式內部使用var宣告的變數都放在變數環境中,並賦予一個預設值undefined。
- 在函式內部使用let宣告的變數被放在詞法環境中,沒有賦予一個預設值。
- 在函式內部中的{}內部使用let宣告的變數沒有放在詞法環境中。
if (true) { console.log(typeof value); // 引用錯誤 let value = 'blue' }
因為value位於暫時性死區(temporal dead zone, TDZ)的區域內--該名稱並沒有在ECMAScript規範中被明確命名,但經常被用於描述let或const宣告的變數為何在宣告之前無法被訪問。 總結 1、JavaScript程式碼是先編譯再執行的。 2、執行是按順序一段一段執行的,一段程式碼是指一個執行上下文。 3、執行上下文有三種情況:
- 全域性執行上下文
- 函式執行上下文
- eval執行上下文
作者:zhangwinwin 連結:JavaScript程式碼是怎麼執行的? 來源:github