YDNJS
You Don't Know JS
英文原文
中文翻譯
六個系列
Up & Going Scope
Closures this
Object Prototypes Types
Grammar Async
Performance ES6
Beyond
二、Scope & Closures
中文
英文
Scope 是變數的作用域範圍,Closures 閉包是 Outer 函式執行結束後,Outer 的資料還能被 inner 函式讀取到的那些資料
Chapter 1: What is Scope?
變數是程式中最基本的東西,變數可以用來儲存、讀取數值。儲存與尋找這些變數的規則就是 Scope。接下來就是探討 Scope 的規則是怎麼被制定的
1.1 Compiler Theory
程式語言大致可分成靜態(編譯,先把程式碼全部轉成二進制碼再執行)與動態(直譯,直接一行一行執行 code)兩種。雖然JS 偏向後者,但我們先來看看前者的運作方式
傳統的靜態語言在執行前會經過三個編譯階段
Tokenizing/Lexing:分詞階段。抓出執行句中所有最小的意義單元。
var a = 2;
就有五個 tokensParsing:解析階段。把 tokens 建立成抽象的語法樹Abstract Syntax Tree。語法結構如下
var,VariableDeclaration
a,Identifier
=,AssignmentExpression
2,NumericLiteral
Code-Generation:把語法樹轉成執行碼的階段。變數 a 會被建立,a 會被賦予 2 這個值
實際上,JS 的執行方式是混合靜態和動態兩種的。詳細可參考以下連結
1.2 Understanding Scope
了解 scope 的方法,就是去觀察以下人物的溝通過程。有哪些人呢?
The Cast
Engine: responsible for start-to-finish compilation and execution of our JavaScript program.
Compiler: one of Engine's friends; handles all the dirty work of parsing and code-generation (see previous section).
Scope: another friend of Engine; collects and maintains a look-up list of all the declared identifiers (variables), and enforces a strict set of rules as to how these are accessible to currently executing code.
用 engine 的角度去想,就能理解 JS 的工作方式
Back & Forth
Compiler 在 var a = 2;
會做兩件事情
Compiler 在 Scope 找 a 有沒有被宣告過;沒有的話,就宣告一個 a
Compiler 產生執行碼給 Engine 用來處理
a=2
的賦值。Engine 會先在目前的 Scope 找 a,有找到的話它就會被賦予2、沒找到的話就往上找。都沒找到的話就 throw out an error
Compiler Speak
多了解一些術語
LHS: 在等號左邊變數的尋找方式,先尋找變量在不在,在的話就賦予值;不在的話就建立變量,再賦予值
RHS: 在等號右邊變數的尋找方式,透過 scope 尋找值
Engine/Scope Conversation
Engine 和 scope 說了好長的一段話 ...
Quiz
LHS,
c = ..
RHS,
foo(..)
LHS,
a = 2
(a 是 parameter)LHS,
b = ..
RHS,
.. = a
RHS,
a + ..
RHS,
.. + b
1.3 Nested Scope
Engine 想找 b,一開始在 scope of foo 中沒找到,在 globe scope 才找到
Building on Metaphors
建築的隱喻。最底層是 current scope, 最頂層是 global scope。
1.4 Errors
為什麼要區分 LHS 和 RHS?因為他們的 look-up 方式不同。RHS 在 current scope 沒找到時,會繼續往上層找。沒找到的話會泡出 ReferenceError;LHS 在沒找到時,Scope 會自動創建一個並交給 Engine。ReferenceError 是 scope 解析失敗,TypeError 是 scope 解析成功,但做了一個沒被定義的動作
1.5 Review (TL;DR)
太長惹,幫你複習一下
Chapter 2: Lexical Scope
scope 是 Engine 用來 look-up variable 的規則。Scope 有兩種描述模型,一個是 Lexical scope (詞法作用域),一個是 dynamic scope (動態作用域)。
2.1 Lex-time
詞法分析時。
編譯器在編譯有三個階段,分詞、解析建立語法樹,以及產生執行碼。Lex-time 就是指第一階段,本章名稱 Lexical scope 也由此而來。
Lexical scope 是指詞法分析時被定義的作用域。nest 關係是嚴格的包含關係,而不是文氏圖那種部份香蕉
Look-ups
scope 查找變數時,inner scope 優先於 outer scope。
2.2 Cheating Lexical
eval( str )
將字串填入,可以解析其中的宣告式 這種動態生成的盡量不要使用
with( obj )
傳入一個物件 可以直接修改 key 的 value
Performance
如果使用 eval 和 with,會讓執行速度變慢 因為 lex-time 時,scope 無法完全掌握變數
Review (TL;DR)
Chapter 3: Function vs. Block Scope
除了函數,還有什麼東西能產生 scope 嗎
3.1 Scope From Functions
3.2 Hiding In Plain Scope
doSomethingElse 不會在全域中被其他人使用到
Collision Avoidance
隱藏變數的另一個好處,不會被其他相同的 identifier 干擾
Global "Namespaces"
若單純透過一個物件,命名許多函式想當 api 的用的話,可能容易跟其他 libraries 干擾
Module Management
另一種避免衝突的方式是使用 Module 管理
3.3 Functions As Scopes
加入 foo() 可以避免 a 的衝突,但 foo 名字本身會污染當前的作用域(也就是全域)
但透過這個方式就不會污染到了(立即函式),而且能立即執行
function... 函數宣告 declartion
(function foo(){...}) 函數表達 expression
Anonymous vs. Named
函數 expression 可以匿名,但函數 declaration 不能 但匿名函式不方便看,因此 inline 函數 expression 的習慣是不錯的選擇
Invoking Function Expressions Immediately
前面的括號將函式 declaration 改成函式 expression,後面的括號用來 invoke 函式
IIFE (immediately invoked function expression) 也能傳入參數
IIFE 还有另一种变种,它将事情的顺序倒了过来,要被执行的函数在调用和传递给它的参数 之后 给出。这种模式被用于 UMD(Universal Module Definition —— 统一模块定义)项目。
詭異的用法
3.4 Blocks As Scopes
除了 function 外,也有其他的 scope unit 靠北,for 迴圈的 i 會變成全域變數 orz
With
雖然 With 盡量別用,但它是 block as scope 的栗子。被創見對象的作用玉只會出現在 with d的生命週其中
try/catch
catch 也是一種塊作用域
let
用 let 宣告的變數,作用域就是在當前 {} 裡面
用 let 宣告的變數也不會被變數提昇
Garbage Collection
塊作用域的程式碼執行玩後,記憶體好像會馬上釋放出來的樣子
let Loops
前面提到的好栗子
也可以解讀成這樣
const
const 宣告的變數一樣是 block-scoped 變數。
Chapter 4: Hoisting
不管是 function scope 或是 block scope,在 scope 裡面被宣告的變量都會依附在當前 scope 底下
以下會介紹當在 scope 中的不同位置宣告變量會有什麼差別
4.1 Chicken Or The Egg?
學 JS 的人有種思維傾向,即程式是由上而下依序執行的。那在以下的程式中,什麼會先呢?是 egg(declaration) 還是 assignment(chicken)
4.2 The Compiler Strikes Again
JS 會先編譯程式(宣告,像是 var a, function foo()),再執行程式
像這個是型別錯誤
4.3 Functions First
當重複宣告時
最後被宣告的函式優先被變量提昇
函式變數宣告比一般宣告更先被提昇
在條件句中,函式變數會優先被提昇(就不會鳥條件敘述了 XD)。但這種行為可能會被修改,所以盡量不要在括號中宣告函式
Chapter 5: Scope Closure
閉包是最詭異的部份惹
5.1 Enlightenment
Understanding closures is like when Neo sees the Matrix for the first time.
閉包無所不在,直到你看見了他(作者也太詩意了)
5.2 Nitty Gritty
Chapter 5: Scope Closures Appendix A: Dynamic Scope Appendix B: Polyfilling Block Scope Appendix C: Lexical-this Appendix D: Thank You's!
this & Object Prototypes Types & Grammar Async & Performance ES6 & Beyond
Last updated