➜ Old React website
Chung Cheuk Hang MichaelJava Web Developer
低級 task scheduling 錯誤FFmpeg 基本功能

進階 JavaScript

Table of contents

1 JavaScript 本質

Client-side 既 JavaScript 係由瀏覽器執行,所以會有 documentwindow,包含瀏覽器既 metadata、視窗大小、user agent 等等。
JavaScript 唔似其他程式語言咁有 class 概念,而 JavaScript 中既 class 只係 prototype 既 syntactical sugar(語法糖,即係只係一個方便啲既寫法,唔係咩新功能)。JavaScript 一直都係 prototype-based。Object.prototypeArray.prototypeNumber.prototypeString.prototype 等 prototypes 都可以被修改,影響所有已經建造既 object、array、number、string 等等。
Object.prototype.keys = function() { return Object.keys(this); }; ({ k: "v" }).keys(); // ["k"]

2 Variables

JavaScript 唔係 strongly typed,呢一點同 Java 完全唔同。除非用 JavaScript 既 superset TypeScript,否則 JavaScript 本身容許 variable 改變 data type。
另外,JavaScript 裡面既 function 都係 variable,可以直接 reference,呢一點唔同需要用 reflection 既 Java。

2.1 var

  • Globally scoped 或者 function-scoped
  • 如果唔係喺 function 裡面 declare 時用,個 variable 就會去左 window
  • 可以重新 declare
  • 數值可以改變
1var bar; 2var bar = 5; 3var bar = 10; 4var bar; 5bar // 5 6window.bar // 5 7 8let bar; // SyntaxError: Identifier 'bar' has already been declared 9const bar; // SyntaxError: Identifier 'bar' has already been declared 10 11function foo() { 12 var test = 123; 13} 14 15test // ReferenceError: test is not defined

2.2 let

  • Block-scoped
  • 不能重新 declare
  • 數值可以改變
1let bar; 2let bar = 5; 3bar = 10; 4 5let bar; // SyntaxError: Identifier 'bar' has already been declared 6const bar; // SyntaxError: Identifier 'bar' has already been declared 7 8function foo() { 9 let test; 10 let test; // bad: SyntaxError: Identifier 'test' has already been declared 11}

2.3 const

  • Block-scoped
  • Declare 時必須提供數值
  • 不能重新 declare
  • 數值不能改變
1const bar; // SyntaxError: Missing initializer in const declaration 2const bar = 5; 3bar = 10; // TypeError: Assignment to constant variable. 4 5var bar; // SyntaxError: Identifier 'bar' has already been declared 6let bar; // SyntaxError: Identifier 'bar' has already been declared
for...infor...of 可以用 const,但 for 就唔得。

2.4 Hoisting

JavaScript 同 Java 既一個不同之處,就係 JavaScript 既 variables 可以喺 reference 之後先至 declare,咁係因為 JavaScript engine 識得 hoist,類似將 declaration 搬到 reference 之前。
console.log(bar); // undefined var bar = 5;
以上例子係唔會導致 ReferenceError,因為佢喺被執行既時候會變成:
var bar; console.log(bar); // undefined bar = 5;
注意:hoisting variable declaration 並唔適用於 letconstletconst 既 variables 喺 declare 之前會進入 temporal dead zone(TDZ),reference 既話會得到 ReferenceError: Cannot access '<variable>' before initialization
Function declaration 都可以 hoist。呢一點 Java 一樣,可以將 method 寫喺 class body 任何地方。對 Java 黎講好正常,因為 Java 係 compiled language;但對 JavaScript 而言,因為係 interpreted language,唔 hoist 既話,到用既時候就會唔知有果樣野既存在。
foo(); function foo() {}
但係咁就唔得:
foo(); // TypeError: foo is not a function var foo = function() {};
參考資料:

3 Boolean 相關

JavaScript 同 Java 喺比較數值上有唔同,JavaScript 有 =====;而 Java 就再強大得多(或者複雜得多),有 ==Object class 既 instance method equals(obj)(同 hashCode() 一齊用)。
JavaScript 除左 Java 既 null(type:object),仲有 undefined(type:undefined)。

3.1 驗證 value 係 truthy 定 falsy

如果 value 唔係 boolean,而喺冇得用 &&(AND)、||(OR)、?(ternary/conditional operator)、if (boolean) {}while (boolean) {}(只能 take boolean 做 argument)既情況下,需要得出一個 boolean value,咁我地可以用 !(not operator)得出 boolean(相反值),然後寫兩次(!!)就可以得出正值。
JavaScript 同 Java 唔同既地方係,JavaScript 任何 variable,無論係 Number、Object 都可以驗證 truthy 或者 falsy,用 not operator ! 得出 boolean 結果(truthy 或 falsy)。
Number:
只有 0 先係 falsy,其他 numbers 都係 truthy。
1!!0 // false 2let bar = -0; 3bar // -0 你冇睇錯,JavaScript 有分正 0、負 0 4!!-0 // false 不過正 0、負 0 一樣都係 falsy 5 6!!-5 // true 負數都係 truthy,因為有野 7!!5 // true
String:
只要長度大過 0 就係 truthy,否則 falsy。
!!"" // false !!" " // true 你冇睇錯,blank string 都係 truthy !!"0" // true
Array:
!![] // true 注意,咁係 check 唔到一個 array 係咪冇 element !![].length // false !![1].length // true
Object:
!!{} // true 注意,咁係 check 唔到一個 object 係咪冇 property !!Object.keys({}).length // false !!Object.keys({ k: "v" }).length // true
There are only six falsy values in JavaScript you should be aware of:
  • false — boolean false
  • 0 — number zero
  • "" — empty string
  • null
  • undefined
  • NaN — Not A Number

3.2 比較兩個 values

== 比較 boolean、number、string 既話,左右兩邊即使唔係同一個 type,如果 type cast 完之後數值上相同,都可以得出 true
5 == "5" // true 因為 JavaScript 將 "5" 轉換成 5 0 == false // true 因為 JavaScript 將 0 轉換成 false
=== 比較 number、string 既話,左右兩邊必須要係同一個 type,而且數值上相同,先會得出 true
=== 或者 == 比較 array、object、function 既話,必須要係同一個 reference,先會得出 true
1[] == [] // false 2{} == {} // false 3(() => {}) == (() => {}) // false 左右兩邊係 arrow function 4 5[] === [] // false 6{} === {} // false 7(() => {}) === (() => {}) // false 左右兩邊係 arrow function
Double equals
When using double equals in JavaScript we are testing for loose equality. Double equals also performs type coercion.
Triple Equals
When using triple equals === in JavaScript, we are testing for strict equality. This means both the type and the value we are comparing have to be the same.

4 Array 語法

喺 JavaScript 既世界,Array 都係 Object 既一種。

4.1 Array 可以有特殊既 empty element

[ , ] // [empty] [ , , ] // [empty × 2]

4.2 Array spread operator

當我地喺一個 array 上面用 spread operator,就可以去除最外層既 [],令到 pass arguments 既時候只 pass 裡面既 elements(散開),而唔係個 array object。
1// ... 係 rest parameter,等於 Java 既 varargs 2function foo(bar, ...rest) {} 3 4foo(); 5foo(1); // bar = 1 6foo(1, 2); // bar = 1, rest = [2] 7foo(1, 2, 3); // bar = 1, rest = [2, 3] 8 9const arr = [1, 2, 3, 4]; 10foo(arr); // bar = [1, 2, 3, 4], rest = [] 11foo(...arr); // bar = 1, rest = [2, 3, 4] 呢個就係 spread
用 spread operator 黎重新建立一個新 array:
let arr = [1, 2, 3]; arr = [...arr, 4, ...arr]; arr // [1, 2, 3, 4, 1, 2, 3]
Conditional spread:
[...(true ? [1] : [])] // [1] [...(false ? [1] : [])] // []

4.3 Array destructuring assignment

const [, r = 0, g = 0, b = 0] = [0.5, 248, 187, 223]; // ARGB r // 248 g // 187 b // 223
解釋:將 array 既數值按順序按位置 assign 落左邊既 variables 同時提供 default value,亦可以用 empty 黎 skip 左唔需要既 elements。
互換數值:
let foo = 5; let bar = 10; [foo, bar] = [bar, foo]; foo // 10 bar // 5
const obj = { bar: 5 }; Object.entries(obj).forEach(([k, v]) => console.log(`${k}: ${v}`)); // bar: 5
可以用 rest parameter:
const [first, second, ...rest] = [1, 2, 3, 4]; first // 1 second // 2 rest // [3, 4]
就算乜 variable 名都冇,都唔會有問題:
const [, ,] = [1, 2, 3, 4];

4.4 生成數字 array

[...Array(10).keys()] // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

4.5 去除重複 elements

let arr = [1, 2, 3, 1, 2, 4]; arr = Array.from(new Set(arr)); arr // [1, 2, 3, 4]

5 Object 語法

5.1 創建 object

Shorthand property names:
const bar = 5; const obj = { bar }; // 等於 { bar: bar } obj // {bar: 5}
解釋:當我地 initialize object 既時候,如果喺 {} 裡面用 <variable> 既寫法而唔係 <key>: <value> 既寫法,variable 名就會成為 object property key,而 variable 既 value 就會成為 object property value。
Computed property names:
1const prefix = "bar"; 2const i = 3; 3const obj = { 4 [`${prefix}${i}`]: 5, 5}; 6obj // {bar3: 5}
解釋:[] 裡面可以填 expression,表達 object property 名(string)。

5.2 Object spread operator

當我地喺一個 object 上面用 spread operator,就可以去除最外層既 {},令到 pass arguments 既時候只 pass 裡面既 property key-value pairs(散開),而唔係個 object。
用 spread operator 黎重新建立一個新 object:
let obj = { k: "v" }; obj = { k: "vv", ...obj }; obj // {k: "v"} obj = { ...obj, k: "vv" }; obj // {k: "vv"}
注意:因為 object 既 properties 唔可以重複,所以 {} 裡面如果出現同一個 property 名(包括 spread 出黎既 properties),最後既數值會覆蓋之前既數值成為最終既數值。
Conditional spread:
{...(true && {k: 'v'})} // {k: "v"} {...(false && {k: 'v'})} // {}

5.3 Object destructuring assignment

const { k, kk = "vv" } = { k: "v" }; k // "v" kk // "vv"
解釋:喺 object 拎個 property key-value 出黎 declare 成一個 variable,而 variable 名會同 object property 名一樣。如果 object 裡面冇我地需要既 property,可以提供 default value。
Destructure 既時候仲可以同時重新命名 variable:
const { k: kk } = { k: "v" }; kk // "v"
解釋:喺 object 拎個 property key-value 出黎 declare 成一個唔同名既 variable。
可以用 rest parameter:
const { k1, k2, ...rest } = { k1: 1, k2: 2, k3: 3, k4: 4 }; k1 // 1 k2 // 2 rest // {k3: 3, k4: 4}
就算乜 variable 名都冇,都唔會有問題:
const {} = { k: "v" };

5.3.1 Nested object and array destructuring assignment

1const { 2 name, 3 // orders, // 如果需要 access orders array 4 orders: [ 5 { id: id1, status: status1 }, 6 { id: id2, status: status2 }, 7 ], 8} = { 9 name: "Mick", 10 orders: [ 11 { 12 id: 101, 13 status: "SHIPPED", 14 }, 15 { 16 id: 115, 17 status: "PAID", 18 }, 19 { 20 id: 128, 21 status: "CANCELLED", 22 }, 23 ], 24}; 25 26name // "Mick" 27id1 // 101 28id2 // 115 29status1 // "SHIPPED" 30status2 // "PAID" 31orders // ReferenceError: orders is not defined

6 String 語法

6.1 Template literal

1const bar = 5; 2const foo = () => 10; 3const str1 = `${bar}`; 4const str2 = `${foo()}`; 5const str3 = `${true ? bar : foo()}`; 6const str4 = `${false ? bar : foo()}`; 7 8str1 // "5" 9str2 // "10" 10str3 // "5" 11str4 // "10"

7 Operators

參考資料:

7.1 Logical operators

7.1.1 Logical AND operator

a && b 會喺 a 係任何 truthy value 既情況下得出 b,否則得出 a
1true && 10 // 10 25 && 10 // 5 3null && 10 // null 4undefined && 10 // undefined 50 && 10 // 0 6"" && 10 // ""

7.1.2 Logical OR operator

a || b 會喺 a 係任何 falsy value 既情況下得出 b,否則得出 a
1true || 10 // true 25 || 10 // 5 3null || 10 // 10 4undefined || 10 // 10 50 || 10 // 10 6"" || 10 // 10

7.1.3 Nullish coalescing operator

a ?? b 只會喺 anull 或者 undefined 既情況下得出 b,否則得出 a
1true ?? 10 // true 25 ?? 10 // 5 3null ?? 10 // 10 4undefined ?? 10 // 10 50 ?? 10 // 0 6"" ?? 10 // ""

7.2 Ternary/conditional operator

a ? b : c 會喺 a 係任何 truthy value 既情況下得出 b,否則得出 c
而呢個 statement 係 short circuit evaluation,bc 執唔執行視乎 a 既 value:
a ? foo() : bar()
解釋:
  • a 係 truthy 既情況下會執行 foo(),唔會執行 bar()
  • a 係 falsy 既情況下會執行 bar(),唔會執行 foo()
1let bar = 5; 2 3// ternary operator 4bar = bar > 3 ? 10 : "false"; 5 6// 相等於 7if (bar > 3) { 8 bar = 10; 9} else { 10 bar = "false"; 11}

7.3 Assignment operators

1let bar = 10; 2bar++; // 相等於 bar = bar + 1; 3bar--; // 相等於 bar = bar - 1; 4bar += 5; // 相等於 bar = bar + 5; 5bar -= 5; // 相等於 bar = bar - 5; 6bar *= 5; // 相等於 bar = bar * 5; 7bar /= 5; // 相等於 bar = bar / 5; 8bar %= 5; // 相等於 bar = bar % 5; 9 10bar = 2; 11bar **= 3; // 相等於 bar = Math.pow(bar, 3);

7.3.1 Logical AND assignment operator

a &&= b; 會喺 a 係任何 truthy value 既情況下 assign ba,否則咩都唔做。
而呢個 statement 係 short circuit evaluation——只有當 a 係 truthy,b 先會執行。
let bar = 10; bar &&= 5; // 相等於 bar && (bar = 5); bar // 5

7.3.2 Logical OR assignment operator

a ||= b; 會喺 a 係任何 falsy value 既情況下 assign ba,否則咩都唔做。
而呢個 statement 係 short circuit evaluation——只有當 a 係 falsy,b 先會執行。
let bar = 0; bar ||= 10; // 相等於 bar || (bar = 10); bar // 10

7.3.3 Logical nullish assignment operator

a ??= b; 只會喺 anull 或者 undefined 既情況下 assign ba,否則咩都唔做。
而呢個 statement 係 short circuit evaluation——只有當 anull 或者 undefinedb 先會執行。
let bar = 0; bar ??= 10; // 相等於 bar ?? (bar = 10); bar // 0

7.4 Optional chaining

當我地 call a.b.c.d 咁既 chaining 既時候,視乎情況,我地有時需要考慮到 abc 有可能係 nullish(undefined 或者 null)而加上唔少 check nullish value 既 code,否則可能會因為 nullish value 而導致 chaining 既時候出現 TypeError: Cannot read property 'x' of undefined/null
用返上面個例子,如果利用 optional chaining,就可以咁寫:a?.b?.c?.d,而最少只需要 declare a 就已經可以避免到因為 nullish value 而導致既 TypeError
另一個例子 a.b().c.d 寫成 a?.b?.()?.c?.d 就可以 handle 到 abb()c 係 nullish。不過有一點要留意,就係 optional chaining 都唔係無敵,因為 a?.b?. 後面有 (),所以 b 就必須係 nullish 或者一個 valid 既 function object,否則就會出 TypeError: a?.b is not a function
情境例子
Access 一個 object/array 既 propertyo?.po?.["p"]
Call 一個 functionf?.()
1const obj = { 2 foo: () => 123, 3 bar: null, 4}; 5const obj2 = null; 6 7obj?.foo?.() // 123 8obj?.bar?.() // undefined 9obj?.bar?.(blah) // undefined 10obj?.["bar"] // undefined 11 12obj2?.foo // undefined 13obj2?.blah() // undefined 14obj2?.a?.b?.c?.d?.e // undefined 15 16obj3 // ReferenceError
注意:Optional chaining 係用唔到喺 assignment 既左手邊,a?.b = 123 係會出 SyntaxError: Invalid left-hand side in assignment

8 Deep clone

Deep clone 同 shallow clone 既分別在於 clone 既深度。Shallow clone 指既係只會 clone 最表面果層,裡面如果有 nested objects,會用返相同既 object references,如果改左裡面既 nested object,就會影響到新舊兩個 objects;而 deep clone 就會 recursively 咁 clone 到去最入面果層,令到新既 object 同舊既 object 完全冇關係,無論點改其中一個 object,另一個 object 都唔會受到任何影響。
參考資料:

8.1 JSON

最簡單亦適用於一般既 JSON data deep clone 場景既方法就係將 object serialize 成 string,再 deserialize 成全新既 object,裡面既 nested objects 都係全新。
let obj = { k: "v", kk: [1, 2] }; obj = JSON.parse(JSON.stringify(obj)); obj // {k: "v", kk: [1, 2]}

8.2 Lodash library

先透過 <script> load Lodash,或者以 npm install + import 語句 import Lodash。
Lodash 既 utility object 係 _
let obj = { k: "v", kk: [1, 2] }; obj = _.cloneDeep(obj); obj // {k: "v", kk: [1, 2]}