你不知道的JS(上卷)筆記
你不知道的 JavaScript編程
JavaScript 既是一門充滿吸引力、簡單易用的語言,又是一門具備許多複雜微妙技術的語言,即便是經驗豐富的 JavaScript 開發者,若是沒有認真學習的話也沒法真正理解它們.數組
上捲包括倆節:閉包
但願 Kyle 對 JavaScript 工做原理每個細節的批判性思 考會滲透到你的思考過程和平常工做中。知其然,也要知其因此然。編程語言
做用域是什麼?
倆個事實
- 可以儲存變量值並能在以後對這個值進行訪問和修改(幾乎全部編程語言最基本的功能之一)
- 若沒有了狀態這個概念,程序雖然也可以執行一些簡單的任務,但它會受到高度限制,作 不到很是有趣。(正是這種儲存和訪問變量的值的能力將狀態帶給了程序)
提出問題
須要一套規則來處理變量的問題,解決上述問題
須要一套設計良好的規則來存儲變量,而且以後能夠方便的找到變量,這套規則被稱爲 做用域。分佈式
新的問題
編譯原理
JavaScript是一門編譯語言。學習
傳統語言的編譯流程:優化
-
分詞/詞法分析(Tokenizing/Lexing)this
- 這個過程會將由字符組成的字符串分解成(對編程語言來講)有意義的代碼塊,這些代 碼塊被稱爲詞法單元(token)。例如,考慮程序var a = 2;。這段程序一般會被分解成 爲下面這些詞法單元:var、a、=、2 、;。空格是否會被看成詞法單元,取決於空格在 這門語言中是否具備意義。。
- 分詞(tokenizing)和詞法分析(Lexing)之間的區別主要差別在於詞法單元的識別是經過有狀態仍是無狀態的方式進行的。簡 單來講,若是詞法單元生成器在判斷 a 是一個獨立的詞法單元仍是其餘詞法單元的一部分時,調用的是有狀態的解析規則,那麼這個過程就被稱爲詞法分析。
-
解析/語法分析(Parsing)設計
- 這個過程是將詞法單元流(數組)轉換成一個由元素逐級嵌套所組成的表明了程序語法 結構的樹。這個樹被稱爲「抽象語法樹」(Abstract Syntax Tree,AST)。var a = 2; 的抽象語法樹中可能會有一個叫做 VariableDeclaration 的頂級節點,接下 來是一個叫做 Identifier(它的值是 a)的子節點,以及一個叫做 AssignmentExpression 的子節點。AssignmentExpression 節點有一個叫做 NumericLiteral(它的值是 2)的子節點。
-
代碼生成對象
- 將 AST 轉換爲可執行代碼的過程稱被稱爲代碼生成。這個過程與語言、目標平臺等息 息相關。拋開具體細節,簡單來講就是有某種方法能夠將 var a = 2; 的 AST 轉化爲一組機器指 令,用來建立一個叫做 a 的變量(包括分配內存等),並將一個值儲存在 a 中。
JavaScript 引擎不會有大量的(像其餘語言編譯器那麼多的)時間用來進行優化,因 爲與其餘語言不一樣,JavaScript 的編譯過程不是發生在構建以前的。
任何 JavaScript 代碼片斷在執行前都要進行編譯(一般就在執行前)。所以, JavaScript 編譯器首先會對 var a = 2; 這段程序進行編譯,而後作好執行它的準備,而且 一般立刻就會執行它。
理解做用域
對話形式模擬做用域的工做方式
演員表
參與到對程序 var a = 2; 進行處理的過程當中的演員們
- 引擎
從頭至尾負責整個 JavaScript 程序的編譯及執行過程。
- 編譯器
負責語法分析及代碼生成等。
- 做用域
負責收集並維護由全部聲明的標識符(變量)組成的一系列查 詢,並實施一套很是嚴格的規則,肯定當前執行的代碼對這些標識符的訪問權限。
對話
編譯器首先會將這段程序分解成詞法單元,而後將詞法單元解析成一個樹結構。可是當編 譯器開始進行代碼生成時,它對這段程序的處理方式會和預期的有所不一樣。
能夠合理地假設編譯器所產生的代碼可以用下面的僞代碼進行歸納:「爲一個變量分配內 存,將其命名爲 a,而後將值 2 保存進這個變量。」然而,這並不徹底正確。
事實上編譯器會進行以下處理。
- 遇到 var a,編譯器會詢問做用域是否已經有一個該名稱的變量存在於同一個做用域的 集合中。若是是,編譯器會忽略該聲明,繼續進行編譯;不然它會要求做用域在當前做 用域的集合中聲明一個新的變量,並命名爲 a。
- 接下來編譯器會爲引擎生成運行時所需的代碼,這些代碼被用來處理 a = 2 這個賦值 操做。引擎運行時會首先詢問做用域,在當前的做用域集合中是否存在一個叫做 a 的 變量。若是是,引擎就會使用這個變量;若是否,引擎會繼續查找該變量(查看 1.3 節)。
總結:變量的賦值操做會執行兩個動做,首先編譯器會在當前做用域中聲明一個變量(如 果以前沒有聲明過),而後在運行時引擎會在做用域中查找該變量,若是可以找到就會對 它賦值。
編譯器中的操做
異常
爲何區分 LHS 和 RHS 是一件重要的事情?
由於在變量尚未聲明(在任何做用域中都沒法找到該變量)的狀況下,這兩種查詢的行 爲是不同的。
若是 RHS 查詢在全部嵌套的做用域中遍尋不到所需的變量,引擎就會拋出 ReferenceError 異常。值得注意的是,ReferenceError 是很是重要的異常類型。
相較之下,當引擎執行 LHS 查詢時,若是在頂層(全局做用域)中也沒法找到目標變量,
全局做用域中就會建立一個具備該名稱的變量,並將其返還給引擎,前提是程序運行在非 「嚴格模式」下。
嚴格模式下,未聲明的RHS和LHS倆者行爲相同,都會是 ReferenceError。
ReferenceError 同做用域判別失敗相關,而 TypeError 則表明做用域判別成功了,可是對 結果的操做是非法或不合理的。