TIL coding
  • Initial page
  • 排版
  • Flexbox
  • Grid
  • jQuery
  • Untitled
  • JavaScript
    • An Introduction to JavaScript
    • Hello, world!
    • Code structure
    • The modern mode, "use strict"
    • Variables
    • Data types
    • Type Conversions
    • Operators
    • Comparisons
    • Interaction: alert, prompt, confirm
    • Conditional operators: if, '?'
    • Logical operators
    • Loops: while and for
    • The "switch" statement
    • Functions
    • Function expressions and arrows
    • JavaScript specials
    • Comments
    • Ninja code
    • Automated testing with mocha
    • Polyfills
    • Objects
    • Garbage collection
    • Symbol type
    • Object methods, "this"
    • Object to primitive conversion
    • Constructor, operator "new"
    • Methods of primitives
    • Numbers
    • Strings
    • Arrays
    • Array methods
    • Iterables
    • Map, Set, WeakMap and WeakSet
    • Object.keys, values, entries
    • Destructuring assignment
    • Date and time
    • JSON methods, toJSON
    • Recursion and stack
    • Rest parameters and spread operator
    • Closure
    • The old "var"
    • Global object
    • Function object, NFE
    • The "new Function" syntax
    • Scheduling: setTimeout and setInterval
    • Decorators and forwarding, call/apply
    • Function binding
    • Currying and partials
    • Arrow functions revisited
    • Property flags and descriptors
    • Property getters and setters
    • Prototypal inheritance
    • F.prototype
    • Native prototypes
    • Prototype methods, objects without __proto__
    • The “class” syntax
    • Class inheritance
    • Static properties and methods
    • Private and protected properties and methods
    • Extending built-in classes
    • Class checking: "instanceof"
    • Mixins
    • Error handling, "try..catch"
    • Custom errors, extending Error
    • Introduction: callbacks
    • Promise
    • Promises chaining
    • Error handling with promises
    • Promise API
  • Bootstrap
    • Navbar
Powered by GitBook
On this page
  • A couple of questions
  • Lexical Environment
  • Function Declaration
  • Inner and outer Lexical Environment
  • Nested functions
  • Environments in detail
  • Code blocks and loops, IIFE
  • if
  • for
  • Code blocks
  • IIFE
  • Garbage collection
  • Real-life optimizations

Was this helpful?

  1. JavaScript

Closure

閉包可以保存函式內的變數,無法存函式外改變變數的值,並應用到巢狀函式內。

A couple of questions

// 函式使用外部變數,會使用最新的值嗎?
let name = "John";
function sayHi() {
  alert("Hi, " + name);
}
name = "Pete";
sayHi(); // what will it show: "John" or "Pete"?

// 函式會使用內部變數還是外部變數?
function makeWorker() {
  let name = "Pete";

  return function() {
    alert(name);
  };
}

let name = "John";

// create a function
let work = makeWorker();

// call it
work(); // what will it show? "Pete" (name where created) or "John" (name where called)?

Lexical Environment

在 JavaScript 裡,每個函式、{...}、整個程式碼都有內部物件叫 Lexical Environment。有 2 個部分

  • Environment Record 一個物件儲存所有區域變數當作屬性

  • 指向外部 lexical environment

變數只是一個 Environment Record 的屬性,取得或改變變數意思是取得或改變物件的屬性。

Function Declaration

函式在 Lexical Environment 被創造的時候就已經被宣告,變數是在執行到時才被創造。這是為什麼可以在函式宣告前使用。

Inner and outer Lexical Environment

當使用函式時,會創造 2 個 Lexical Environment, inner Lexical Environment 的屬性有帶入該函式的參數,outer Lexical Environment 有全域變數包含函式本身及用到的全域變數。當函式使用變數時,會先在內部尋找,然後尋找外部,然後是全域變數。在 "use strict" 下,變數未定億會出現錯誤,非 "use strict",會指定變數值為 undefined。

// 回答第 1 個問題,變數會使用最新的值,
let name = "John";

function sayHi() {
  alert("Hi, " + name);
}

name = "Pete"; // (*)

sayHi(); // Pete

Nested functions

function sayHiBye(firstName, lastName) {

  // helper nested function to use below
  function getFullName() {
    return firstName + " " + lastName;
  }

  alert( "Hello, " + getFullName() );
  alert( "Bye, " + getFullName() );

}

// 巢狀函式可以被返回作為新物件的屬性或直接作為結果。

// 巢狀函式作為新物件屬性
// constructor function returns a new object
function User(name) {

  // the object method is created as a nested function
  this.sayHi = function() {
    alert(name);
  };
}

let user = new User("John");
user.sayHi(); // the method "sayHi" code has access to the outer "name"

// 巢狀函式作為結果
function makeCounter() {
  let count = 0;

  return function() {
    return count++; // has access to the outer "count"
  };
}

let counter = makeCounter();

alert( counter() ); // 0
alert( counter() ); // 1
alert( counter() ); // 2
// 可以在 makeCounter() 外改變裡面的變數 count 嗎?
// 不行,count 是區域變數,無法從外部得到他的值
// 當執行 makeCounter() 多次,每次執行結果使獨立的嗎? count 相同嗎?
// 每次執行 makeCounter() 會產生新的 Lexical Environment,所以每次執行的結果互為獨立。

function makeCounter() {
  let count = 0;
  return function() {
    return count++;
  };
}

let counter1 = makeCounter();
let counter2 = makeCounter();

alert( counter1() ); // 0
alert( counter1() ); // 1

alert( counter2() ); // 0 (independent)

Environments in detail

1. 腳本開始時只有全域 Lexical Environment,裡面只有宣告 makeCounter(),所有函式宣告的時候,必然有一個隱藏的屬性 [[Environment]] 指向函式,

2. 呼叫函式的時候,創造 Lexical Environment,Environment Record 儲存區域變數,指向外部 lexical reference 被設定成 [[Environment]] 屬性的函式。

3. 執行到巢狀函式時,創造 [[Environment]] 屬性外部指向 makeCounter()。

4. makeCounter() 執行完畢,結果儲存在全域變數 counter,當 counter 被呼叫執行 return count++ 這行程式碼。

5. 雖然 makeCounter() 執行完畢,但仍有 [[Environment]] 屬性指向他,因此呼叫 counter() 時,會先創造一個空的 Lexical Environment 因為沒有參數,再來指向外部的 [[Environment]] 。

6. 呼叫 counter(),不只返回 count 值,也增加他的值。

開頭問題的解答:

Code blocks and loops, IIFE

閉包的特性可以應用在任何 {...} 上。

if

for

for (let i = 0; i < 10; i++) {
  // Each loop has its own Lexical Environment
  // {i: value}
}

alert(i); // Error, no such variable

Code blocks

// 當 2 個腳本有相同痊癒變數會出現問題,用 {...} 可以解決這樣的問題。
{
  // do some job with local variables that should not be seen outside

  let message = "Hello";

  alert(message); // Hello
}

alert(message); // Error: message is not defined

IIFE

// 在過去沒有 lexical environment,因此發明 immediately-invoked function expressions,
// 函式有區域變數並立即執行,

(function() {

  let message = "Hello";

  alert(message); // Hello

})();

// 沒有函式名稱
// Try to declare and immediately call a function
function() { // <-- Error: Unexpected token (

  let message = "Hello";

  alert(message); // Hello

}();

// 不能宣告同時執行
// syntax error because of parentheses below
function go() {

}(); // <-- can't call Function Declaration immediately

// Ways to create IIFE

(function() {
  alert("Parentheses around the function");
})();

(function() {
  alert("Parentheses around the whole thing");
}());

!function() {
  alert("Bitwise NOT operator starts the expression");
}();

+function() {
  alert("Unary plus starts the expression");
}();

Garbage collection

// 函式執行完,會被記憶體清除
function f() {
  let value1 = 123;
  let value2 = 456;
}

f();

// 用閉包的特性,函式執行完仍在記憶體內,[[Environment] 指向函式。
function f() {
  let value = 123;

  function g() { alert(value); }

  return g;
}

let g = f(); // g is reachable, and keeps the outer lexical environment in memory

// 函式被呼叫多次,相對應的 lexical environment,也會被儲存在記憶體。
function f() {
  let value = Math.random();

  return function() { alert(value); };
}

// 3 functions in array, every one of them links to Lexical Environment (LE for short)
// from the corresponding f() run
//         LE   LE   LE
let arr = [f(), f(), f()];

// 當全域變數被清除,函式消失
function f() {
  let value = 123;

  function g() { alert(value); }

  return g;
}

let g = f(); // while g is alive
// there corresponding Lexical Environment lives

g = null; // ...and now the memory is cleaned up

Real-life optimizations

// 實際狀況 JavaScript 引擎會進行優化,未作用的變數會被清除。
// v8 在 debug 時無法使用區域變數
function f() {
  let value = Math.random();

  function g() {
    debugger; // in console: type alert( value ); No such variable!
  }

  return g;
}

let g = f();
g();

// 會返回全域變數
let value = "Surprise!";

function f() {
  let value = "the closest value";

  function g() {
    debugger; // in console: type alert( value ); Surprise!
  }

  return g;
}

let g = f();
g();

PreviousRest parameters and spread operatorNextThe old "var"

Last updated 5 years ago

Was this helpful?