Table of contents
1 JavaScript 本質
Client-side 既 JavaScript 係由瀏覽器執行,所以會有 document
同 window
,包含瀏覽器既 metadata、視窗大小、user agent 等等。
JavaScript 唔似其他程式語言咁有 class 概念,而 JavaScript 中既 class
只係 prototype
既 syntactical sugar(語法糖,即係只係一個方便啲既寫法,唔係咩新功能)。JavaScript 一直都係 prototype-based。Object.prototype
、Array.prototype
、Number.prototype
、String.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...in
、for...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 並唔適用於 let
同 const
,let
同 const
既 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
只會喺 a
係 null
或者 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,b
、c
執唔執行視乎 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 b
去 a
,否則咩都唔做。
而呢個 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 b
去 a
,否則咩都唔做。
而呢個 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;
只會喺 a
係 null
或者 undefined
既情況下 assign b
去 a
,否則咩都唔做。
而呢個 statement 係 short circuit evaluation——只有當 a
係 null
或者 undefined
,b
先會執行。
let bar = 0;
bar ??= 10; // 相等於 bar ?? (bar = 10);
bar // 0
7.4 Optional chaining
當我地 call a.b.c.d
咁既 chaining 既時候,視乎情況,我地有時需要考慮到 a
、b
、c
有可能係 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 到 a
、b
、b()
、c
係 nullish。不過有一點要留意,就係 optional chaining 都唔係無敵,因為 a?.b?.
後面有 ()
,所以 b
就必須係 nullish 或者一個 valid 既 function object,否則就會出 TypeError: a?.b is not a function
。
情境 | 例子 |
---|
Access 一個 object/array 既 property | o?.p 、o?.["p"] |
Call 一個 function | f?.() |
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]}