克服JS 的奇怪部分 #1:前三章筆記

Published on
understanding-the-weird-part

這篇記錄從 Udemy 上學習《JavaScript: Understanding the Weird Part》的過程,筆記自己新學到、重新理解的 JavaScript 基礎知識。

CH1

◼ syntax parsers 語法解析器

電腦不會直接看懂你寫的程式碼,中間需要有一個可以轉換 JavaScript 讓電腦讀懂,例如編譯器或直譯器,會一字一字去讀你的程式碼、確認語法有效的,才會翻譯成電腦看得動的東西

  • 把大括號 { 接在函數、for loop、if () 後面是為了避免 syntax parsers 自動幫你填入分號
  • 空格自由

◼ lexical environments 詞彙環境

程式寫在哪裡是很重要的,lexical environments 指的是程式碼在開發中所處的位置,這將幫助編譯器決定、考慮它應該被放置哪個記憶體、周圍環境是什麼(寫在函式裡、被包在物件、在陣列中)

=> 影響在執行階段時,它該對應的記憶體位置

◼ execution context 執行環境

管理目前正在執行的程式碼。

程式碼執行前,先依照 lexical Environment 被解析器轉換、創造並擺到該放的記憶體位置,最後才開始執行,某些特性(例如 hoisting)就是在創造階段發生的。

而在執行環境一段正在運行的程式碼,同樣的名稱只會有一個,一個名稱只會對應到一個值

// 宣告一個變數名稱 name,它對應的值是 'zoe'
var name = 'zoe'

名稱的確可以被定義多次,但在每層 execution context 它只會擁有一個值


CH2

在執行時,JS Engine 會創造一個基礎執行環境(Base Execution Context)作為全域環境,同時也創造 Global Objectthis 變數

  • 瀏覽器的 Global Object 指向 window(視窗)
  • Node.js 裡的 Global Object => Global

this 變數則會參照 Global Object

因為是全域,代表任何地方都可以存取到,而瀏覽器每一視窗各自有一個全域物件

  • 當你在全域中創建變數和函式,就會和 Global Object 連結起來
  • 全域環境的 outer environment 會是 null => 因為它沒有更外層的執行環境可以參照

為什麼有 hoisting?

執行環境分成兩個階段:creation 和 execution

  1. 創造階段(creation)

    • 記憶體有了 Global object/Variable Objectthis 變數和 outer environment
    • 把變數和函式放進記憶體 => 有所謂的 hoisting,是因為在創造階段就為宣告的 variable、function 留好他們的記憶體位置
    • 但變數的值和類型是什麼?要一直到執行階段才知道。函式則會先設定好,例如 a: function()
  2. 執行階段(execution)

    • 逐步、一行行執行
    • 每呼叫(invoke)一個函式,就會在 stack 上堆一層 execution context
    • stack 最上層就是目前正在執行的程式碼

何謂 Scope Chain 和 Outer Environment

Scope - 可以取用到變數的地方,Chain - 對外部環境的參照連結

  • 變數會被定義在自己那層的執行環境
  • 每一個執行環境在創造階段時,除了 Variable Objectthis 變數外,還會創造它要參照的外部環境連結

在 JavaScript 中,如果你想要呼叫一個在當前環境所沒有的變數,會往它的 Outer Environment 尋找,也就是順著 Scope Chain。這個外部環境參照是怎麼決定的?

取決於「產生這個執行環境的 function 被寫在哪裡」

外部環境的參照,跟 Stack 堆疊順序無關,而是跟 function 在哪裡被定義有關,也就是第一章提過的 lexical environment(程式碼實際寫在的位置)。

在當前的執行環境找不到變數時,會到它參照的外部環境去尋找

function b() {
  console.log(myVar) // 🔸 印出 1,因為 function b 被定義在全域
}
function a() {
  var myVar = 2
  b() // 在 function a 裡面呼叫 b()
}
var myVar = 1
a()

Scope Chain 就是指在找變數的過程中,從內層找到外層,一直到最外面的 Global Environment 的順序鏈,就叫做 Scope Chain。

  • 執行環境的創造 => 和 function 被呼叫有關
  • 參照的外部環境 => 跟 function 定義的位置有關

參考資料:[筆記] JavaScript 中 Scope Chain 和 outer environment 的概念


執行非同步事件

JS Engine 是單執行緒,一次只做一件事,但它並非獨立存在於環境,在瀏覽器中還有提供其他引擎可以做事,像是處理 HTTP 請求資料、渲染畫面或觸發點擊事件等等,可以讓同一時間點有不只一件事在做,而他們也可以跟 JS Engine 溝通,來達到非同步處理的效果。

當 JS Engine 接收到外部來了一個需要被處理的事件時(asynchronous callback),會先放進事件佇列(queue),優先把 call stack 裡面的東西清空才會去處理事件佇列的東西

詳細的可以參考之前寫過的 Event Loop,了解運作機制


CH3

型別

和其他靜態型別的語言不同,JS 不需要事先說明型別,在執行時才會知道,也意味著同一變數可以在不同時間擁有不同型別

  • 基本型別就是一個純值,而不像 object 是 name-value 組合
  • 在 JavaScript 的數字只有 number 這個型別(有時候運算會和你想的不太一樣)

認識 undefined

  • undefined 代表變數只是尚未被設定、值不存在,不代表從未宣告,跟 not defined 不一樣
  • var a 會在建立執行環境時將宣告的 a 放進記憶體
  • 是一個內建的值,在創造階段時,所有變數被預設為 undefined(相當於 JS 給的初始值)
  • 📝 好的習慣:永遠不要設定變數為 undefined => 很難判斷變數是不是你設定,要就設定成 null(空值)

運算子

var a = 4 + 3 // + 就是一個運算子,函數是寫在兩個參數之間
  • 為中綴表示法,運算子放在參數中間
  • 特殊的內建函數,通常需要兩個參數來回傳一個結果

1. 優先性和相依性

var a = 4 + 3 - 2 // 5

JS 是單執行緒,不會一次處理兩個函數,會決定先執行哪一個部分,先呼叫完一個才呼叫第二個,參考 MDN 的 Precedence 可以了解到它執行的優先序。

let b = 2,
  c = 3,
  d = 4

b = c = d // 4

具有相同的 precedence,則視 Associativity 來決定從哪裡計算起,像等號就是 right-to-left。

2. 強制轉型 coercion

  • 轉換一個值的型別
  • 📝 好的習慣:永遠用 === 來做相等比較
  • 把東西放在 if 判斷式裡面,會被試著強制轉換成 Boolean 值,但要注意數字 0 是會被轉換成 false
// 可以拿來做一些判斷,像是「如果 a 存在的話就執行...、如果 a 沒有值則...」
if (a) {
  consol.log('true')
}
  • 📝 好的習慣:使用 || 來給預設值,它會回傳第一個被轉型成 true 的值,讓程式碼變得簡潔,可以用它來建立預設值(default value)📝
// 早期做法
function greet(name) {
  name = name || 'default name'
  console.log(name)
}
greet()

偶爾在框架的 source code 裡面可能會看到這行程式碼,這樣寫的目的在於檢查全域命名空間

window.libraryName = window.libraryName || 'lib 2'