# YDNJS

## You Don't Know JS

英文原文

* [getify/You-Dont-Know-JS: A book series on JavaScript. @YDKJS on twitter.](https://github.com/getify/You-Dont-Know-JS)

中文翻譯

* [JoeHetfield/You-Dont-Know-JS: A book series on JavaScript. @YDKJS on twitter.](https://github.com/JoeHetfield/You-Dont-Know-JS)

六個系列

* Up & Going Scope&#x20;
* Closures this&#x20;
* Object Prototypes Types&#x20;
* Grammar Async&#x20;
* Performance ES6&#x20;
* Beyond

## 二、Scope & Closures

中文

* [You-Dont-Know-JS/ch2.md at master · JoeHetfield/You-Dont-Know-JS](https://github.com/JoeHetfield/You-Dont-Know-JS/blob/master/scope%20%26%20closures/ch2.md)

英文

* [You-Dont-Know-JS/ch2.md at master · getify/You-Dont-Know-JS](https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20%26%20closures/ch2.md)

> Scope 是變數的作用域範圍，Closures 閉包是 Outer 函式執行結束後，Outer 的資料還能被 inner 函式讀取到的那些資料

### Chapter 1: What is Scope?

變數是程式中最基本的東西，變數可以用來儲存、讀取數值。儲存與尋找這些變數的規則就是 Scope。接下來就是探討 Scope 的規則是怎麼被制定的

#### 1.1 Compiler Theory

程式語言大致可分成靜態（編譯，先把程式碼全部轉成二進制碼再執行）與動態（直譯，直接一行一行執行 code）兩種。雖然JS 偏向後者，但我們先來看看前者的運作方式

傳統的靜態語言在執行前會經過三個編譯階段

1. Tokenizing/Lexing：分詞階段。抓出執行句中所有最小的意義單元。`var a = 2;` 就有五個 tokens
2. Parsing：解析階段。把 tokens 建立成抽象的語法樹Abstract Syntax Tree。語法結構如下
   * var，VariableDeclaration
     * a，Identifier&#x20;
     * \=，AssignmentExpression&#x20;
       * 2，NumericLiteral&#x20;
3. Code-Generation：把語法樹轉成執行碼的階段。變數 a 會被建立，a 會被賦予 2 這個值

實際上，JS 的執行方式是混合靜態和動態兩種的。詳細可參考以下連結

* [WebAssembly 系列（二）JavaScript Just-in-time (JIT) 工作原理 - 知乎专栏](https://zhuanlan.zhihu.com/p/25669120)

#### 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;` 會做兩件事情

1. Compiler 在 Scope 找 a 有沒有被宣告過；沒有的話，就宣告一個 a
2. Compiler 產生執行碼給 Engine 用來處理 `a=2` 的賦值。Engine 會先在目前的 Scope 找 a，有找到的話它就會被賦予2、沒找到的話就往上找。都沒找到的話就 throw out an error

**Compiler Speak**

多了解一些術語

```javascript
var b = 2;
var a = b;
```

* LHS: 在等號左邊變數的尋找方式，先尋找變量在不在，在的話就賦予值；不在的話就建立變量，再賦予值
* RHS: 在等號右邊變數的尋找方式，透過 scope 尋找值
* [Javascript中的LHS和RHS - iNiL0119](https://my.oschina.net/iNiL0119/blog/685140)
* [JavaScript 的赋值表达式和左表达式:Left-Hand-Side Expressions - 知乎专栏](https://zhuanlan.zhihu.com/p/25428739)
* [从LHS与RHS角度浅谈Js变量声明与赋值 · Issue #12 · MrErHu/blog](https://github.com/MrErHu/blog/issues/12)
* [studyFiles/电子书 at master · threerocks/studyFiles](https://github.com/threerocks/studyFiles/tree/master/%E7%94%B5%E5%AD%90%E4%B9%A6)
* [JavaScript中的变量查找 - 简书](http://www.jianshu.com/p/2f5839edc63f)

**Engine/Scope Conversation**

```javascript
function foo(a) {
    console.log( a ); // 2
}

foo( 2 );
```

Engine 和 scope 說了好長的一段話 ...

**Quiz**

```javascript
function foo(a) {
    var b = a;
    return a + b;
}

var c = foo( 2 );
```

1. LHS，`c = ..`
2. RHS，`foo(..)`
3. LHS，`a = 2` (a 是 parameter)
4. LHS，`b = ..`
5. RHS，`.. = a`
6. RHS，`a + ..`
7. RHS，`.. + b`

#### 1.3 Nested Scope

```javascript
function foo(a) {
    console.log( a + b );
}

var b = 2;

foo( 2 ); // 4
```

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 關係是嚴格的包含關係，而不是文氏圖那種部份香蕉

```javascript
function foo(a) {

    var b = a * 2;

    function bar(c) {
        console.log( a, b, c );
    }

    bar(b * 3);
}

foo( 2 ); // 2 4 12
```

**Look-ups**

scope 查找變數時，inner scope 優先於 outer scope。

#### 2.2 Cheating Lexical

**eval( str )**

將字串填入，可以解析其中的宣告式 這種動態生成的盡量不要使用

```javascript
function foo(str, a) {
    eval( str ); // cheating!
    console.log( a, b );
}

var b = 2;

foo( "var b = 3;", 1 ); // 1 3
```

**with( obj )**

傳入一個物件 可以直接修改 key 的 value

```javascript
function foo(obj) {
    with (obj) {
        a = 2;
    }
}

var o1 = {
    a: 3
};

var o2 = {
    b: 3
};

foo( o1 );
console.log( o1.a ); // 2

foo( o2 );
console.log( o2.a ); // undefined
console.log( a ); // 2 -- 哦，全局作用域被泄漏了！
```

**Performance**

如果使用 eval 和 with，會讓執行速度變慢 因為 lex-time 時，scope 無法完全掌握變數

#### Review (TL;DR)

### Chapter 3: Function vs. Block Scope

除了函數，還有什麼東西能產生 scope 嗎

#### 3.1 Scope From Functions

```javascript
function foo(a) {
    var b = 2;

    // some code

    function bar() {
        // ...
    }

    // more code

    var c = 3;
}
```

#### 3.2 Hiding In Plain Scope

doSomethingElse 不會在全域中被其他人使用到

```javascript
function doSomething(a) {
    function doSomethingElse(a) {
        return a - 1;
    }

    var b;

    b = a + doSomethingElse( a * 2 );

    console.log( b * 3 );
}

doSomething( 2 ); // 15
```

**Collision Avoidance**

隱藏變數的另一個好處，不會被其他相同的 identifier 干擾

```javascript
function foo() {
    function bar(a) {
        i = 3; // changing the `i` in the enclosing scope's for-loop
        console.log( a + i );
    }

    for (var i=0; i<10; i++) {
        bar( i * 2 ); // oops, infinite loop ahead!
    }
}

foo();
```

**Global "Namespaces"**

若單純透過一個物件，命名許多函式想當 api 的用的話，可能容易跟其他 libraries 干擾

```javascript
var MyReallyCoolLibrary = {
    awesome: "stuff",
    doSomething: function() {
        // ...
    },
    doAnotherThing: function() {
        // ...
    }
};
```

**Module Management**

另一種避免衝突的方式是使用 Module 管理

#### 3.3 Functions As Scopes

加入 foo() 可以避免 a 的衝突，但 foo 名字本身會污染當前的作用域（也就是全域）

```javascript
var a = 2;

function foo() { // <-- insert this

    var a = 3;
    console.log( a ); // 3

} // <-- and this
foo(); // <-- and this

console.log( a ); // 2
```

但透過這個方式就不會污染到了（立即函式），而且能立即執行

```javascript
var a = 2;

(function foo(){ // <-- insert this

    var a = 3;
    console.log( a ); // 3

})(); // <-- and this

console.log( a ); // 2
```

* function... 函數宣告 declartion
* (function foo(){...}) 函數表達 expression&#x20;

**Anonymous vs. Named**

函數 expression 可以匿名，但函數 declaration 不能 但匿名函式不方便看，因此 inline 函數 expression 的習慣是不錯的選擇

```javascript
setTimeout( function timeoutHandler(){ // <-- Look, I have a name!
    console.log( "I waited 1 second!" );
}, 1000 );
```

**Invoking Function Expressions Immediately**

前面的括號將函式 declaration 改成函式 expression，後面的括號用來 invoke 函式

```javascript
var a = 2;

(function IIFE(){

    var a = 3;
    console.log( a ); // 3

})();

console.log( a ); // 2
```

IIFE (immediately invoked function expression) 也能傳入參數

```javascript
var a = 2;

(function IIFE( global ){

    var a = 3;
    console.log( a ); // 3
    console.log( global.a ); // 2

})( window );

console.log( a ); // 2
```

> IIFE 还有另一种变种，它将事情的顺序倒了过来，要被执行的函数在调用和传递给它的参数 之后 给出。这种模式被用于 UMD（Universal Module Definition —— 统一模块定义）项目。

詭異的用法

```javascript
// 原始
var a = 2;

(function IIFE( def ){
    def( window );
})(function def( global ){

    var a = 3;
    console.log( a ); // 3
    console.log( global.a ); // 2

});


// 接近於先定義 def 函式
// 將整個 def 宣告當參數傳入 IIFE
// 然後再用 window 當參數傳入 def 函式

var a = 2;

function def(global){
    var a = 3;
    console.log(a);
    console.log(global.a)
}

(function IIFE(def){
    def(window);
})(def);
```

#### 3.4 Blocks As Scopes

除了 function 外，也有其他的 scope unit 靠北，for 迴圈的 i 會變成全域變數 orz

```javascript
for (var i=0; i<10; i++) {
    console.log( i );
}

console.log(i); // 10
```

**With**

雖然 With 盡量別用，但它是 block as scope 的栗子。被創見對象的作用玉只會出現在 with d的生命週其中

**try/catch**

catch 也是一種塊作用域

```javascript
try {
    undefined(); //用非法的操作強制産生一個異常！
}
catch (err) {
    console.log( err ); // 好用！
}

console.log( err ); // ReferenceError: `err` not found
```

**let**

用 let 宣告的變數，作用域就是在當前 {} 裡面

```javascript
var foo = true;

if (foo) {
    let bar = foo * 2;
    console.log( bar );
}

console.log( bar ); // ReferenceError
```

用 let 宣告的變數也不會被變數提昇

```javascript
{
   console.log( bar ); // ReferenceError!
   let bar = 2;
}
```

**Garbage Collection**

塊作用域的程式碼執行玩後，記憶體好像會馬上釋放出來的樣子

```javascript
// 這段程式碼的 object 可能佔用大量記憶體
function process(data) {
    // do something interesting
}

var someReallyBigData = { .. };
process( someReallyBigData );

var btn = document.getElementById( "my_button" );
btn.addEventListener( "click", function click(evt){
    console.log("button clicked");
}, /*capturingPhase=*/false );


// 但加上尖括號就不會了
function process(data) {
    // do something interesting
}

// anything declared inside this block can go away after!
{
    let someReallyBigData = { .. };
    process( someReallyBigData );
}

var btn = document.getElementById( "my_button" );
btn.addEventListener( "click", function click(evt){
    console.log("button clicked");
}, /*capturingPhase=*/false );
```

**let Loops**

前面提到的好栗子

```javascript
for (let i=0; i<10; i++) {
    console.log( i );
}

console.log( i ); // ReferenceError
```

也可以解讀成這樣

```javascript
{
    let j;
    for (j=0; j<10; j++) {
        let i = j; // 每次叠代都重新綁定
        console.log( i );
    }
}
```

**const**

const 宣告的變數一樣是 block-scoped 變數。

```javascript
var foo = true;

if (foo) {
    var a = 2;
    const b = 3; // block-scoped to the containing `if`

    a = 3; // just fine!
    b = 4; // error!
}

console.log( a ); // 3
console.log( b ); // ReferenceError!
```

### Chapter 4: Hoisting

不管是 function scope 或是 block scope，在 scope 裡面被宣告的變量都會依附在當前 scope 底下

以下會介紹當在 scope 中的不同位置宣告變量會有什麼差別

#### 4.1 Chicken Or The Egg?

學 JS 的人有種思維傾向，即程式是由上而下依序執行的。那在以下的程式中，什麼會先呢？是 egg(declaration) 還是 assignment(chicken)

```javascript
// 這會印出 undefined, 還是 2
a = 2;
var a;
console.log( a );

// 這會印出 undefined, 2, 還是 ReferenceError
console.log( a );
var a = 2;
```

#### 4.2 The Compiler Strikes Again

JS 會先編譯程式（宣告，像是 var a, function foo()），再執行程式

```javascript
//剛剛第一個程式等同於
var a;
a = 2;
console.log( a );

// 第二個程式等同於
var a;
console.log( a );
a = 2;
```

像這個是型別錯誤

```javascript
// var foo;

foo(); // not ReferenceError, but TypeError
bar(); // ReferenceError

var foo = function bar() {
    // ...
};
```

### 4.3 Functions First

當重複宣告時

* 最後被宣告的函式優先被變量提昇
* 函式變數宣告比一般宣告更先被提昇

```javascript
foo(); // 3

function foo() {
    console.log( 1 );
}

var foo = function() {
    console.log( 2 );
};

function foo() {
    console.log( 3 );
}
```

在條件句中，函式變數會優先被提昇(就不會鳥條件敘述了 XD)。但這種行為可能會被修改，所以盡量不要在括號中宣告函式

```javascript
foo(); // "b"

var a = true;
if (a) {
   function foo() { console.log( "a" ); }
}
else {
   function foo() { console.log( "b" ); }
}
```

### Chapter 5: Scope Closure

閉包是最詭異的部份惹

### 5.1 Enlightenment

Understanding closures is like when Neo sees the Matrix for the first time.

閉包無所不在，直到你看見了他(作者也太詩意了)

### 5.2 Nitty Gritty

* [https://github.com/getify/You-Dont-Know-JS/blob/master/scope %26 closures/ch5.md](https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20%26%20closures/ch5.md)
* [https://github.com/JoeHetfield/You-Dont-Know-JS/blob/master/scope %26 closures/ch5.md](https://github.com/JoeHetfield/You-Dont-Know-JS/blob/master/scope%20%26%20closures/ch5.md)

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
