[31] 範疇 - 編譯三步驟、巢狀範疇、錯誤


keywords:Tokenizing,Parsing,Code-Generation,scope,LHS,RHS,ReferenceError,TypeError
撰寫程式碼時我們經常用到變數存取資料,但那些變數存在於何處?以及我們程式是如何在需要的時候找到它們的 🤔

範疇 ( scope )

以一組定義良好的規則來將變數儲存在某些位置,以便之後找回那些變數。這組規則就稱作 範疇 ( scope )

編譯器理論

JavaScript 一般被歸類為動態的 ( dynamic ) 或直譯式 ( interpreted ) 語言,但實際上是一種編譯式語言 ( compiled language ) ,與傳統編譯式語言不同的是,它並非事先就先編譯好,幾乎就是編譯後即刻執行。

傳統的編譯式語言在執行之前通常會經歷三個步驟,大略稱為『 編譯 』:

  1. Tokenizing 語法基本單元化 & Lexing 語彙分析 :將一串字元拆解成有意義的組塊,這些組塊叫做『 語法基本單元化 tokens 或稱語彙單元 』
    tokens
  2. Parsing 剖析或語法分析:接受由 語法基本單元( tokens )所構成的串流或陣列,並將之轉為一種元素內嵌的樹狀結構,整個陣列代表了程式的文法結構,這種樹狀結構稱為『 AST , abstract syntax tree 抽象語法樹 』
    syntax
  3. Code-Generation 產生目的程式碼:接受一個 AST 並將之轉為可執行程式碼的過程。這部分會隨著語言及目標平台等因素的不同而有所變化,簡單的說就是有一種方式可以把 AST 轉為一組機器指令,實際建立出一個叫做 a 的變數( 包括保留記憶體等步驟 ),並將一個儲存到 a 中。

Esprima 是可以看到一段程式碼編譯過程的 Tokens and Syntax,上方例子連結在此

JavaScript 引擎所進行的工作比傳統的三個單純的步驟複雜很多,對 JavaScript 來說,編譯過程在程式碼被執行前通常僅以微秒來計算。

範疇及它的好朋友們

  • Engine 引擎:負責從開始到結束的編譯程序,並執行 JavaScript 程式
  • Compiler 編譯器:處理剖析與程式碼產生的所有苦工
  • Scope 範疇:負責收集及維護所有已宣告的識別字( 即變數 )所構成的查找清單 ( look-up list),並強制施加一組嚴格的規則,規範這些變數對於目前正在執行的程式碼,是否可以取用
  • 查找動作種類,當 Engine 要對一個變數進行查找時,有兩種方法:

    1. LHS ( lefthand side ):取得指定的目標
    2. RHS ( righthand side ):取回它的來源值

       let a = 9;
      
       let b = a;
      

      這邊總共有 2 個 LHS 以及 1 個 RHS
      LHS:取得指定的目標,我們有兩個地方需要 Engine 幫我找到指定的目標,就是在指定 a = 9; b = a;
      RHS:取回它的來源值,當 b = a; 時,Engine 需要找到 a 裡面放了什麼

Engine 與 Scope 的對話

    function f1(x) {

        let y = x;

        return x + y + z;
    }

    let z = 1;

    f1( 9 );

Engine:嘿 Scope 我有一個對 z 的 LHS ,請問你知道它嗎?
Scope:I Know. Compiler 剛剛才宣告它,它是一個變數
Engine:Thx. 我現在要把 1 指定給 z

Engine:嘿 Scope 我有一個對 f1 的 RHS ,請問你知道它嗎?
Scope:I Know ok. Compiler 剛才宣告它,它是一個函式請拿去用吧
Engine:感謝感謝,我正在執行 f1 呢

Engine:拍謝 Scope 我有一個 x 的 LHS 參考,你知道嗎?
function f1(x) 其實會隱含地指定 9 給 x ( x = 9 ),進行 LHS 查找動作
Scope:Of Course, Compiler 最近把它宣告為 f1 的一個形式參數,拿去唄
Engine:非常感謝,現在該把 9 指定給 x 了

Engine:Yo Scope 我有一個對 y 的 LHS 及 x 的 RHS ,請問你知道它們嗎?
Scope:I always Know. Compiler 剛才宣告 y ,它是一個變數,x 就是跟剛剛同一個變數,拿去吧
Engine:Thank you. 我現在要把 x 的值指定給 y

Engine:Hi f1 裡面的 Scope ,有聽過 z 嗎?我需要它的一個 RHS 參考
f1's Scope:I don't know sorry.
Engine:It's ok.
( Engine 只好跑去問 f1 外面的 Scope )

Engine:Yo Scope 請問你聽過 z 嗎? 我需要它的一個 RHS 參考
Scope:有啊,我有拿去用吧, By the way 我叫作全域範疇

巢狀範疇

Engine 會從正在執行程式碼的範疇中開始尋找目標變數,如果沒找到就會持續往上一層找,直到全域範疇,不管有沒有找到搜尋動作就會停止。
一個比喻範疇尋找目標的情境:假設你有一棟透天厝,你的朋友在附近想找你吃個熱炒喝兩杯,他跟你說他要去接你而他人就快到你家的巷子口了,叫你趕快準備準備。
你匆匆忙忙的從 4 樓的房間穿好衣服跑到 1 樓,準備要把門鎖上時發現,OMG 我錢包在房間啊!於是你開始從 1 樓的客廳開始找有沒有平時放在客廳桌上的紙鈔,如果 1 樓找不到就得去 2 樓的工作房找,如果最後 1~3 樓都找不到錢,那只好回 4 樓房間拿了。

  • 範疇就是你家的每一層樓,變數就是紙鈔

錯誤

為何要區分 RHS 及 LHS?
先來看兩個例子:

    function f1(x) {

        console.log( x + y);

        y = x;

    }

    f1(9);  //  ReferenceError: y is not defined

    ----------------------------------------------

    function f2(x) {

        y = x;

    }

    f2(9);

    y; // 9
  • 當 RHS 查找動作在巢狀 scopes 中找到一個變數,它找不到 y,引擎就會擲出一個 ReferenceError
  • 當 LHS 查找動作到達全域範疇後,依然找不到目標變數,在不是 Strict Mode 中執行時,全域範疇就會在全域變數中自動創建 那個找不到的變數名稱為新變數,並把它交給 Engine
  • 如果在 Strict Mode 中 LHS 查找動作到達全域範疇後,依然找不到目標變數,就會擲出一個 ReferenceError

    • ReferenceError 與範疇解析動作失敗有關

    • TypeError 則是範疇的解析動作成功了,但試圖對結果進行非法或不可能的動作,像是調用一個非函式值,或是在 null 及 undefined 值上參考一個特性

        let x = 9;
      
        x(); // TypeError: x is not a function
      
        null.a; // TypeError: Cannot read property 'a' of null
      
#Tokenizing #Parsing #Code-Generation #scope #LHS #RHS #ReferenceError #TypeError







你可能感興趣的文章

超讚 Deep Learning on 3D object detection 相關教學影片彙整

超讚 Deep Learning on 3D object detection 相關教學影片彙整

如何使用 Python Tkinter 製作 GUI 應用程式入門教學

如何使用 Python Tkinter 製作 GUI 應用程式入門教學

圖片縮放效果 補

圖片縮放效果 補






留言討論