CH5. 大師級函式:閉包與範圍


閉包是 JavaScript 程式語言的特色。
JavaScript 引擎會確保在執行任何函式時,可以取用到應該要取用到的變數。


雖然 JS 沒有一個閉包物件可以讓你檢視資訊,因此不容易看出這樣的結構,但這些資訊都需要保存在記憶體中,直到 JS 引擎認為不需要他,或直到頁面卸載。
因此可以使用閉包的特性來實現私有變數

function Ninja() {
  var feints = 0;
  this.getFeints = function(){
    return feints;
  };
  this.feint = function(){
    feints++;
  };
 }

 var ninja1 = new Ninja();
 ninja1.feint();

 ninja1.feints === undefined; // 無法存取 feints 變數
 ninja1.getFeints() === 1; // 可以更改私有變數

 var ninja2 = new Ninja();
 ninja2.getFeints() === 0; // 擁有自己的 feints 變數

5.3 使用執行背景空間 ( execution context ) 追蹤程式執行

無論何時執行JS、它都會在執行背景空間裡執行。
當程式被 JavaScript 執行時,每個敘述句都在一個特定的執行背景空間中執行。
每個執行背景空間,都會對應一個字彙環境。

執行背景空間在物理意義上包含:

* 正在執行的程式碼
* 撰寫的 code
* 看不見的 code -> 你寫的code其實正在被轉換、被另一個別人所寫的程式(編譯器compiler)處理

執行背景空間只有兩種,分別是:

1. 全域執行背景空間 -> 只會有一個
2. 函式執行背景空間 -> 每次呼叫函式都會建立一個新的執行背景空間

5.4 使用字彙環境 ( lexical environment ) 來追蹤識別項

字彙環境是 JavaScript 引擎的一個內部結構,用於追蹤從識別項到特定變數的對應 ( mapping )。
口語上將 lexical environment 稱作 scope。
字彙環境就是代表程式碼在程式中的位置。

[[Environment]] 是函式的內部屬性,不能被外部存取或操作。
當建立函式時,指向該函式所屬字彙環境的參照,會儲存在該函式的 [[Environment]] 的內部屬性中

    var a = 1;
    var b = 2;
    function foo() { // foo 的外部環境為 global 的 lexical environment,而外部環境的 reference 儲存在 [[Evnironment]]
        var b = 3

        function bar() { // bar 的外部環境為 foo 的 lexical environment,而外部環境的 reference 儲存在 [[Evnironment]]
            var c = 4;

            function tmp() {
                var test = 1;
            }

            tmp();
        }   

        bar();
    }

    foo();

當呼叫函式時,會建立一個函式執行背景執行空間與字彙環境,並將該函式的 [[Environment]] 作為字彙環境的外部環境參照

function foo() { 
    function bar() { 
    }
    bar(); 
  // 呼叫 bar 時,會建立 bar 的 函式執行背景執行空間 與 字彙環境,且外部參照為 bar.[[Environment]],因此外部參照就是 foo 字彙環境
}

每個 function 有一個 [[Scopes]] 屬性,而裡面的資訊就是參照的環境,而參照的環境就是從 [[Environment]] 裡面儲存的 reference 決定的。

5.5 暸解 JavaScript 的變數類型

建立字彙環境,分為兩個步驟

1. 註冊階段
2. 執行階段


全域環境的註冊過程

console.log(globalFun()); // globalFun
console.log(globalVar); // undefined
// console.log(globalLet); // Cannot access 'globalLet' before initialization
// console.log(globalConst); // Cannot access 'globalConst' before initialization

function globalFun() {
  return 'globalFun';
}

var globalVar = 'globalVar';
let globalLet = 'globalLet';
const globalConst = 'globalConst';
// console.log(canNoGet); // canNoGet is not defined 

function tmp() {
    var canNoGet = "canNoGet";
}

/**
 * 全域環境的註冊過程
 * 
 * 是否為函式環境? 否
 * 
 * 是函式或全域環境? 是
 * 掃描目前的程式碼找出所有函式宣告
 * 在其他函式之外註冊函式宣告:
 * 註冊 globalFun, tmp
 * 
 * 掃描目前的程式碼找出變數宣告
 * 在函式式之外註冊 globalVar
 * 在區塊之外註冊 globalLet, globalConst
 */

/* --------------------- */

函式環境的註冊過程

/* ------- function env -------------- */
function go(foo, bar) {
    console.log(foo, bar, arguments);
    doSomething(); // 'do something'
    console.log(tmp); // tmp is not defined
    function doSomething() {
        var tmp = 'tmp';
        let letTmp = 'letTmp';
        const constTmp = 'constTmp';
        console.log('do something');
    }
/**
 * 函式環境的註冊過程
 * 
 * 是否為函式環境? 是
 * 建立 arguments 物件與函式參數(foo, bar)
 * 
 * 是函式或全域環境? 是
 * 掃描目前的程式碼找出所有函式宣告
 * 在其他函式之外註冊函式宣告
 * 
 * 掃描目前的程式碼找出變數宣告
 * 在函式之外註冊 var
 * 在區塊之外註冊 let, const
 */
}
/* --------------------- */

區塊環境的註冊過程

/* --------- block scope env  ------------ */
console.log(valInBlock); // undefined 
// console.log(letInBlock);  // letInBlock is not defined
// console.log(constInBlock); // constInBlock is not defined
// console.log(functionInBlock()); // functionInBlock is not a function 

{
  console.log(typeof functionInBlock); // "function"
  console.log(functionInBlock()); // "functionInBlock"

  var valInBlock = 'valInBlock';
  let letInBlock = 'letInBlock';
  const constInBlock = 'constInBlock';

/**
 * 區塊環境的註冊過程
 * 
 * 是否為函式環境? 否
 * 
 * 是函式或全域環境? 否
 * 
 * 是否為區塊環境? 是
 * 掃描目前的程式碼找出 let, const 變數宣告
 * 註冊 letInBlock, constInBlock
 */
}
/* --------------------- */

在函式執行期間,會跳過函式宣告,因此 fun 的定義對 fun 識別項沒有任何影響。

console.log(typeof fun); // 'function'

var fun = 3;

console.log(typeof fun); // 'number'

function fun(){}

console.log(typeof fun); // 'number'

Ref:
javascript - Lexical environment and function scope - Stack Overflow
JavaScript 全攻略:克服 JS 的奇怪部分 | Udemy
所有的函式都是閉包:談 JS 中的作用域與 Closure
secrets-of-the-javascript-ninja

#closure #EC #scope







你可能感興趣的文章

[筆記] JavaScript: Understanding the Weird Parts - Build your own lib/framework

[筆記] JavaScript: Understanding the Weird Parts - Build your own lib/framework

[Week2] - JavaScript :邏輯 & 位元運算子

[Week2] - JavaScript :邏輯 & 位元運算子

C++ 教學(二) 輸入輸出&基本資料型態

C++ 教學(二) 輸入輸出&基本資料型態






留言討論