# All About JavaScript ## Execution Contexts & Lexical Environment *Syntax Parser*(語法解析器) > 讀取程式碼並確認語法是否正確,並將其轉譯成電腦理解的語言。 *Lexical Environment*(詞彙環境) > 程式碼在程式中的<u>**實際所在位置**</u>,會影響其對應的記憶體位置與其他變數或函式的互動。 *Execution Context*(EC,執行語境) > JS 引擎模組化直譯程式碼時的區塊環境。 - Global Execution Context(GEC,全域執行語境) > 在執行任何程式之前,預設建立的 EC,會建立 global object(全域物件,以 web browser 來說就是 `window`)和 `this`,此時 `window` = `this`。所有在 GEC 下 `window` 物件的屬性,都可以直接指稱而不需要使用 dot-notation。![GEC](https://hackmd.io/_uploads/B16TmC243.png) - Function Execution Context(FEC,函式執行語境) > 在函式執行時候會各別為函式建立的專屬 EC,故可能同時存在多個 FEC。 *Hoisting*(提升) > 在 Creation Phase(創造階段)時,替變數(設為 `undefined`)和函式<u>**註冊記憶體空間**</u>。 > [我知道你懂 hoisting,可是你了解到多深?](https://blog.huli.tw/2018/11/10/javascript-hoisting-and-tdz/) *Single Thread*(單線程) > 一次執行一件事情。 *Asynchronous*(非同步) > 當執行一件事情時,可以同時執行其他事情。 > [JavaScript 中的同步與非同步(上):先成為 callback 大師吧!](https://blog.huli.tw/2019/10/04/javascript-async-sync-and-callback/) *Scope*(作用域) > 作用域是一個<u>**變數的生存範圍**</u>。一旦出了這個範圍,就無法存取到這個變數。 > > 透過 Outer Environment(外部環境)由 inner EC 往 outer EC 尋找,最後就形成 Scope Chain(作用域鏈)。 ## Types and Operators *Dynamic Typing*(動態型別) > Runtime(執行環境)才會進行型別檢查、變數能任意更換型別,且程式撰寫時不用明確的型別宣告。 *Primitive Type*(原始型別)v.s. *Object*(物件) > "Primitive types are manipulated <u>**by value**</u> and object types are manipulated <u>**by reference**</u>." > [來數數 JavaScript 的所有資料型別](https://blog.huli.tw/2022/02/25/javascript-how-many-types/) > [使用 JavaScript 的數字時的常見錯誤](https://blog.huli.tw/2022/03/14/javascript-number/) *Operator*(運算子) > 一種語法特殊的<u>**函式**</u>,一般來說會傳入兩個參數並回傳一個結果。 > > 運算子優先順序和相依性表請見:[Operator precedence](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Operators/Operator_precedence)。 *Coercion*(強制轉型) > 將一個值的型別轉換成另一種型別。 > > 相等比較表請見:[Equality comparisons and sameness](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness)。 > > Falsy Value(假值)請見:[Falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy)。 > [從「為什麼不能用這個函式」談執行環境(runtime)](https://blog.huli.tw/2022/02/09/javascript-runtime/) > [如何不用英文字母與數字寫出 console.log(1)?](https://blog.huli.tw/2020/12/01/write-conosle-log-1-without-alphanumeric/) ## Objects and Functions *Namespace*(命名空間) > 一個裝著變數與函式的容器,避免相同名稱的變數或函式互相干擾。 > > JS 並沒有 namespace,所以可以用物件作為 Faking Namespace(偽命名空間)。 *JSON*(JavaScript Object Notation) > 一種由 JS Object Literal(物件實字)衍生出的資料交換格式,較 XML(Extensible Markup Language)易懂且資料體積小。通常 API 和 config 皆使用 JSON。 > > key 需用<u>**雙引號**</u>包住,且型別只能為 `string`。 > > 常見方法:JS -> `JSON.stringify()` -> JSON -> `JSON.parse()` -> JS *First-Class Function*(一級函式) > <u>**可被視作變數**</u>的函式(一種特殊的物件),包含指定為變數、作為參數傳遞、可被回傳等行為。JS 的函式皆為 first-class function。 > > 當函式傳遞時特性與物件相同(因為本來就是物件),當要執行時,用括號去 invoke 函式中的 code。 *High-Order Function*(HOF,高階函式) > 當一個函式能夠<u>**接受函式作為參數**</u>或者<u>**回傳函式**</u>時,就被稱為 HOF。HOF 是抽象函式的一種,不包含執行細節,而是在 high-level 的角度描述解決方法。 *Function Statement / Declaration*(函式陳述式) > 執行後不會回傳任何值,且在執行前會將整個函式都存進記憶體,所以會 hoisting。 ```javascript functionStatement() function functionStatement() { console.log(`This is function statement`) } ``` *Function Expression*(函式表達式) > 執行後<u>**會回傳一個值**</u>,所以可以存成一個變數或作為參數傳遞。由於只有變數會被存取,所以不會 hoisting。 > > 函式表達式的函式不需要名稱,此時此函式稱為 Anonymous Function(匿名函式)。 ```javascript const functionExpression = function() { return `This is function expression` } functionExpression() ``` > [覺得 JavaScript function 很有趣的我是不是很奇怪](https://blog.huli.tw/2020/04/18/javascript-function-is-awesome/) *By Value v.s. By Reference* > 當建立一個 primitive type 變數 a 且指定另一個變數 b = a 時,b 會建立另一個獨立的記憶體位置並將 a 的值存於此。故彼此之間不會互相影響,此為 by value。 ![By Value](https://hackmd.io/_uploads/B1hxXLcE3.png) > 當建立一個 object 變數 c 且指定另一個變數 d = c 時,d 會將其指定到 c 的記憶體位置。故彼此之間會互相影響,此為 by reference。 ![By Reference](https://hackmd.io/_uploads/SkFW7IcV2.png) > 但若用 object literal 的方式指定 object 的值,則會產生 by value 的行為,建立一個新的記憶體位置。 > [深入探討 JavaScript 中的參數傳遞:call by value 還是 reference?](https://blog.huli.tw/2018/06/23/javascript-call-by-value-or-reference/) *`this`* > `this` 的值跟作用域跟程式碼的位置在哪裡完全無關,只跟<u>**你如何呼叫**</u>有關。 > 當某個函式是放在某一個物件裡面時,那麼該函式裡面的 `this` 指稱的就是該<u>**物件本身**</u>。而當脫離物件,在嚴格模式下的預設值是 `undefined`,非嚴格模式下是全域物件(瀏覽器底下是 `window`,node.js 底下是 `global`)。 > 當位於 Arrow Function(箭頭函式)內時,在<u>**宣告此函式**</u>的地方的 `this` 是什麼,它的 `this` 就是什麼。 > [淺談 JavaScript 頭號難題 this:絕對不完整,但保證好懂](https://blog.huli.tw/2019/02/23/javascript-what-is-this/) *Immediately Invoked Function Expression*(IIFE,立即執行函式運算式) > 若要建立一個 anonymous function 且不設為變數,要用括號將函式包起來,因若 syntax parser 開頭讀到 `function` (會以為是 function statement)又未讀到函式名稱,便會拋出錯誤。因此,使用 function expression 加上括號並直接 invoke,便可以做出 IIFE。 > 透過 IIFE 可以 executing code on the fly(直接執行程式),且<u>**在 IIFE 內的變數不會影響到函式外部**</u>。 ```javascript (function(concept) { return `This is ${concept}!` })('IIFE') ``` *Closure*(閉包) > <u>**當一個 function 執行完畢且其 EC 已經離開 execution stack,這個 EC 仍會存在於當下的記憶體位置。**</u>因此,JS engine 可以透過 scope chain 去取得 outer environment reference,也就是存在 EC 內的變數。此時,因為現在執行的 EC 跟之前存在的變數 close 在一起,故稱為閉包。 > 由於 `var` 是 function scope,所以要避免污染可以透過 block scope 的 `let` / `const` ,或者是 IIFE 來達成。 ```javascript function buildFunctions(n) { var arr = [] // -------------------- // let for (let i = 0; i < n; i++) { arr.push( function() { return i } ) } // -------------------- // IIFE for (var i = 0; i < n; i++) { arr.push( (function(j) { return function() { return j } })(i) ) } // -------------------- return arr } var fs = buildFunctions(3) fs[0]() // 0 fs[1]() // 1 fs[2]() // 2 ``` > [所有的函式都是閉包:談 JS 中的作用域與 Closure](https://blog.huli.tw/2018/12/08/javascript-closure/) *Callback Function*(回呼函式) > 將一個函式作為參數傳入另一個函式,且在傳入之函式執行結束時執行。 *`bind()` / `call()` / `apply()`* > 當想要<u>**動態調整函式中 `this` 所指稱的對象**</u>時可用,為 JS 內建在函式中的方法。 > `bind()` 是回傳綁定 `this` 後的原函式(並未執行),當被綁定後其 `this` 便不能再被修改。而 `apply()` 和 `call()` 則是回傳函式執行結果。 ```javascript var person = { firstname: 'Len', lastname: 'C.', getFullName: function() { return `${this.firstname} ${this.lastname}` } } var logNameWithLocation = function(location) { return `${this.getFullName()} at ${location}` } // bind logNameWithLocation.bind(person)('Taiwan') // call logNameWithLocation.call(person, 'Taiwan') // apply logNameWithLocation.apply(person, ['Taiwan']) ``` > function borrowing 是借用特定物件中的方法,而 function carrying 是複製特定函式並賦予預設參數值,常用於算數上。 ```javascript // function borrowing var tank = { name: 'King Tiger II', fire: function() { return 'boom!' } } var car = { name: 'Lightning McQueen' } tank.fire.bind(car)() tank.fire.call(car) tank.fire.apply(car) // function currying function multiply(a, b) { return a * b } var multiplyByTwo = multiply.bind(this, 2) multiplyByTwo(3) // 6 var multiplyByThree = multiply.bind(this, 3) multiplyByTwo(3) // 9 ``` *Functional Programming*(FP,函式語言程式設計) > [Functional Programming in JS](https://ithelp.ithome.com.tw/users/20106426/ironman/3024) ## Object-Oriented Javascript, Prototypal Inheritance and Building Objects *Inheritance*(繼承) > 一個物件可以使用其他物件的 property(屬性)或 method(方法)。繼承分為兩種,一種是 classical inheritance(類別繼承),用於 C# 或 JAVA 等;另一種則是 prototypal inheritance(原型繼承),用於 JS。 *Prototype*(原型) > 所有 JS 對象皆預設包含 prototype 屬性(名稱為 `__proto__`),而沿著 prototype chain 找到最後的 prototype 皆為物件 -> 「萬物皆物件」。 > ![object prototype chain](https://hackmd.io/_uploads/HJmVm0nEn.png) ![function prototype chain](https://hackmd.io/_uploads/HkpSXChEh.png) ![array prototype chain](https://hackmd.io/_uploads/S168XC3Eh.png) ![string prototype chain](https://hackmd.io/_uploads/B1YwQC2Vn.png) ![number prototype chain](https://hackmd.io/_uploads/BkLd703N3.png) ![boolean prototype chain](https://hackmd.io/_uploads/SJkFQAn43.png) *Prototype Chain*(原型鏈) > 當尋找指定對象的 property 或 method 但對象本身並未擁有時,會往其 prototype 去找,故以 prototype 構成的鏈便稱為 prototype chain。可以用 `instanceof` 去確認對象是否在 prototype chain 中。 > [該來理解 JavaScript 的原型鍊了](https://blog.huli.tw/2017/08/27/the-javascripts-prototype-chain/) *Reflection*(反射) > 一個可以觀測、列出並改變自身 property 或 method 的物件,可以藉由 reflection 來實現 Extend(擴展)。 ```javascript // source code of 'extend' from Underscore.js var extend = createAssigner(allKeys) function createAssigner(keysFunc, defaults) { return function(obj) { var length = arguments.length if (defaults) obj = Object(obj) if (length < 2 || obj == null) return obj for (var index = 1; index < length; index++) { var source = arguments[index], keys = keysFunc(source), l = keys.length for (var i = 0; i < l; i++) { var key = keys[i] if (!defaults || obj[key] === void 0) obj[key] = source[key] } } return obj } } ``` *Function Constructor*(函式建構式) > 能夠藉由 `new` operator 建構出物件(此時稱為 Instance(實例))的函式,其中的 <u>**`this` 會指向 instance**</u>。若此 constructor 並未指定回傳為其他物件,此 instance 會自動被回傳。要注意的是,不管是用 `new String()` 還是 `new Number()` 等方式,建構出來的都是物件! ```javascript 0 === Number(0) 0 !== new Number(0) '' === String('') '' !== new String('') typeof Number(0) // number typeof String('') // string typeof new Number(0) // object typeof new String('') // object ``` > > <u>**constructor 的 prototype(名稱為 `.prototype`)屬性是所有透過這個 constructor 所建立出來的 instance 的 prototype。**</u>因此可以將 method 放在 prototype 中(注意不要 overwrite 原本 prototype 的 method),這樣每一個 instance 都不需要複製一份 method 的 copy,而是可以藉由 prototype chain 來取得此 method 以節省記憶體空間。 ```javascript function Vehicle(name) { this.name = name } Vehicle.prototype.start = function() { return `${this.name} start!` } var car = new Vehicle('car') car.start() // 'car start!' ``` *class*(類別) > 只是一種建立物件的 syntactic sugar(語法糖),其本質還是 prototypal inheritance。 > > 可以用 `extends` 來建立子類別,使其 prototype 指向父類別;用 `super` 呼叫父類別的 constructor 並傳遞相應的參數。 ```javascript class RacingCar { constructor(name) { this.name = name } start() { return `${this.name} start!` } } class GPX extends RacingCar { start() { return `push ${this.name} start button!` } originalStart() { return super.start() } } var asurada = new GPX('Asurada') asurada.start() // 'push Asurada start button!' asurada.originalStart() // 'Asurada start!' ``` ## ES6 *`let` / `const`* > [從 V8 bytecode 探討 let 與 var 的效能問題](https://blog.huli.tw/2020/02/20/let-vs-var-bytecode/) *`Promise`* > `Promise` 會等待某項非同步作業完成(`fulfilled` / `rejected`),並執行此作業完成後要執行的任務。 *`Async` / `Await`* > `async` / `await` 是 `Promise` 的 syntactic sugar。當 syntax parser 在 `async function` 中讀到 `await` 時,會先將 `async function` 從 execution stack 中「暫停並放到一旁」(其他 execution stack 中的函式不受影響)。當 `await` 等待的 `Promise` `fulfilled` 或 `rejected` 後,`async function` 中剩下的程式會繼續被執行。 ## Tricky Knowledge - 函式的 length 就是 parameter(參數)的數量[(ref)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/length) - `Array.fill()` 裡面放的值,會引用到同一個 reference[(ref)](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Array/fill) - `Object.create(null)` !== `{}`[(ref)](https://stackoverflow.com/questions/15518328/is-creating-js-object-with-object-createnull-the-same-as) - `for...in` 也會讀到 prototype 上新增的 property,所以建議用 `for...of` 或 `for loop`[(ref)](https://stackoverflow.com/questions/29285897/what-is-the-difference-between-for-in-and-for-of-statements) ## Additional Information - Course - [CS50](https://pll.harvard.edu/course/cs50-introduction-computer-science?delta=0) - [JavaScript: Understanding the Weird Parts](https://www.udemy.com/course/understand-javascript/) - Practice - [JS30](https://javascript30.com) - [30 Days of LC JavaScript Challenge](https://leetcode.com/discuss/study-guide/3458761/Open-to-Registration!-30-Days-of-LC-JavaScript-Challenge) - Source Code - [Underscore Source Code](https://underscorejs.org/docs/underscore-esm.html) - [Read the jQuery source like a book like a boss](http://robflaherty.github.io/jquery-annotated-source/) - Others - [ES6 Features](https://github.com/lukehoban/es6features#arrows)