Scope === 規範變數在程式碼中的取用規則,Scope 主要分成兩種類型: - Lexical Scope ( 語彙範疇 ) - Dynamic Scope ( 動態範疇 ) JavaScript 與大多數語言採用 Lexiacal Scope。 Lexical Scope (語彙範疇) --- 代表區塊間的包裹關係,被包裹在內層的區塊可以保護自己的變數不被外層取用,相反的,外層區塊的變數仍能被內層區塊使用。 ```jsx let outer = '此處是外層'; function innerFun() { var inner = '此處是內層'; console.log(outer); // "此處是外層" console.log(inner); // "此處是內層" } console.log(outer); // "此處是外層" console.log(inner); // Uncaught ReferenceError: inner is not defined ``` - 函式參數屬於內層 Scope,只有在函式內能夠使用  - 巢狀 Scope,規則相同:外層 Scope 無法取用內層變數,但內層 Scope 可以取用外層變數  全域作用域 Global Level Scope --- 每個執行 JavaScript 的環境,都有一個全域物件(Global Object): - 在 HTML 裡,全域物件是 `window` object。 - 在 Node.js 裡,全域物件是 `global` object。 存放在全域物件裡的變數,無論在哪裡宣告,效力遍及整個程式。因此不應該隨便產生不需要的全域變數,造成額外的程式風險,以及變數控管上的困擾。 使用 var 才會是 Global Level Scope,若使用 let、const 不全然是 Global Level Scope。詳細見[此](https://hackmd.io/9jqUIElFRcavjEriaCBDCg?view#%E4%B8%8D%E6%9C%83%E7%94%A2%E7%94%9F-Global-Scope-%E8%AE%8A%E6%95%B8)。 ### 賦值給未宣告的變數 賦值給未宣告的變數,自動產生全域變數,一般應該避免這種寫法。 ```jsx function myFunc(){ n1 = "OneJar"; // 賦值給未宣告的變數,自動產生全域變數 console.log(n1); // OneJar console.log(this.n1); // OneJar console.log(window.n1); // OneJar } myFunc(); console.log(n1); // OneJar ``` 函式作用域 Function Level Scope --- > 同時也是 Block Level Scope,只是為了方便區分,才特此分出來。 只有在限定範圍內有效果,形成 Scope 的基本單位: - Function JavaScipt 不是為每個 `{}` 產生 Scope,因此 `if() {}`、`for() {}` 內層的變數仍能被外層取用。 區塊作用域 Block Level Scope --- 只有在限定範圍內有效果,形成 Scope 的基本單位: - try/catch 中的 `catch`、`with` - ES6:`let`、`const` ### ES6:`let`、`const` 在 ES5 以前,用 var 關鍵字宣告的變數,只會有 Global Level 和 Function Level 兩種等級的作用域,因此`{}` 不會產生 Scope,只有 Function 才會。 ES6 導入新的變數宣告關鍵字:let 和 const,增加了 Block Scope 的用途。使用 let、const 進行變數宣告,將會在 `{}` 產生 Scope。 ```jsx { let x = 2; { let x = 10; console.log(x); // 10 } console.log(x); // 2 } ``` > [詳細的變數運用](https://hackmd.io/9jqUIElFRcavjEriaCBDCg?view) Scope Lookup(查找) --- 由當下的 Scope 往外層 Scope 查找,一旦找到就停止。若是一直沒有找到變數,JavaScript 就會由當下的 Scope 一路查找到最外層的 Global Scope。 此規則下,可能衍生的兩種問題: - ### 直到 Global Scope 都沒有找到該變數 再細分情境,查找這個變數,是為了要「取值」or「賦值」。 - 在取值情境,會發出 Reference Error:`Uncaught ReferenceError: XXX is not defined`。 - 在賦值情境,會新建變數 or 發出錯誤: - 通常情況,JavaScript 會一路查找到 Global Scope,發現連 Global Scope 都沒有時,便會建立新的 Global 屬性。 - 然而在 `'use strict'`([JavaScript 的嚴格模式](https://hackmd.io/FZI5Jq1SRBWwJGWMqN-cHA?view))時,在賦值時便會報錯。在 `'use strict'`,JavaScript 不接受對不存在變數賦值。 ```jsx 'use strict'; function assignToNonExist() { notDeclaredVar = 'I am is not Declared'; // Uncaught ReferenceError: notDeclaredVar is not defined } assignToNonExist(); ``` - ### Shadowing (遮蔽) 如果同時有兩個同名變數宣告在不同層的 Scope,內層 Scope 的變數會 Shadow(遮蔽)外層的同名變數。  由內層 Scope 往外層 Scope 找,馬上找到自己的 Scope 內有一個 myString,於是停止查找,而外層的 myString 理所當然被 Shadow(遮蔽)。 ```jsx var myString = "hello global"; function testShadowing(){ var myString = "hello scope"; console.log(myString); // "hello global" console.log(window.myString); // "hello global" } ``` 然而,如果在內層透過如 `window.n1` 的方式,明確表明「我要取的是 Global 變數的 n1,而不是 Local 的 n1」,就會是 Global 變數發揮效用。 實作建議 --- - 雖然有 Shadowing 這個特性,仍盡量不要重複宣告同名變數。 - 宣告同名變數,會造成語意不清,難以維護。 - 屬性存取是藉由屬性存取規則(Prototype)去查找的,並不是 Scope 的工作。 - 可以使用 [eval](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval) 脫離 Lexical Scope,但是盡量別用。 - eval 可以輸入字串,編譯器會在執行時期將它轉成程式碼,也就是脫離 Lexical Scope 在編譯時期就定好 Scope 的概念。 - 使用 eval 程式碼難以 Debug,也會造成效能低落,因為編譯器無法預先知道執行的程式是什麼,無法最佳化 eval 述句與相關程式碼的效能。
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up