ECMAScript 的 Dynamic Type 為其一大特性,讓我們能在 Runtime 對 Object 新增 Property;Function 亦為 Object,因此也能對 Function 新增 Property 取代 Global Variable。
Version
macOS Catalina 10.15.2
VS Code 1.41.1
Quokka 1.0.267
ECMAScript 2015
Global Variable
let data = [1, 2, 2, 3, 3, 3]
let last = 0
let count = 0
let reducer = (a, x, i, arr) => {
if (i === 0) {
last = x
count = 1
return a
}
else if (i === arr.length - 1) {
count++
return [...a, { num: last, count }]
} else if (x === last) {
count++
return a
} else {
let result = [...a, { num: last, count }]
last = x
count = 1
return result
}
}
let fn = arr => arr.reduce(reducer, [])
fn(data); // ?
第 3 行
let last = 0
let count = 0
使用 reduce()
時,reducer function 偶爾會有自己的累計值,此時我們會寫在 reducer function 外,讓每次 reducer()
執行時都可存取。
這種 global variable 搭配 side effect 雖然可行,但總有些許遺憾。
Dynamic Property
let data = [1, 2, 2, 3, 3, 3]
let reducer = (a, x, i, arr) => {
if (i === 0) {
reducer.num = x
reducer.count = 1
return a
}
else if (i === arr.length - 1) {
return [...a, {
num: reducer.num,
count: ++reducer.count
}]
} else if (x === reducer.num) {
reducer.count++
return a
} else {
let result = [...a, {
num: reducer.num,
count: reducer.count
}]
reducer.num = x
reducer.count = 1
return result
}
}
let fn = arr => arr.reduce(reducer, [])
fn(data); // ?
第 4 行
if (i === 0) {
reducer.num = x
reducer.count = 1
return a
}
別忘了 ECMAScript 是動態語言,可輕易在 runtime 對 object 新增 property。
Reducer function 亦是 object,因此也可對 reducer()
新增 num
與 count
property 模擬 global variable。
Object.assign()
let data = [1, 2, 2, 3, 3, 3]
let reducer = (a, x, i, arr) => {
let { num, count } = reducer
if (i === 0) {
Object.assign(reducer, { num: x, count: 1})
return a
}
else if (i === arr.length - 1) {
return [...a, { num , count: ++count }]
} else if (x === num) {
reducer.count++
return a
} else {
Object.assign(reducer, { num: x, count: 1})
return [...a, { num, count }]
}
}
let fn = arr => arr.reduce(reducer, [])
fn(data); // ?
使用 function 的 property 取代 global variale 雖然可行,但也有兩個缺點:
- 讀取 property 時都必須搭配
reducer.
稍嫌冗長 - 初始化 property 時需要好幾行
第 4 行
let { num, count } = reducer
reducer()
一開始就使用 object destructuring 拆解出 variable,則後續都不必使用 reducer.
。
第 7 行
Object.assign(reducer, { num: x, count: 1})
使用 object literal 將初始化 property 先組成 object,再使用 Object.assign()
將新 object attach 到 reducer()
,如此只需 1 行搞定。
Object.assign()
除了能 clone 出 object 外,其實亦能 side effect 影響到 object 本身
Array Destructuring
let data = [1, 2, 2, 3, 3, 3]
let reducer = (a, x, i, arr) => {
let { num, count } = reducer
if (i === 0) {
[reducer.num, reducer.count] = [x, 1]
return a
}
else if (i === arr.length - 1) {
return [...a, { num , count: ++count }]
} else if (x === num) {
reducer.count++
return a
} else {
[reducer.num, reducer.count] = [x, 1]
return [...a, { num, count }]
}
}
let fn = arr => arr.reduce(reducer, [])
fn(data); // ?
第 7 行
[reducer.num, reducer.count] = [x, 1]
亦可使用 array destructuring 將多行 assignment 寫成 1 行。
Reset Function
let data = [1, 2, 2, 3, 3, 3]
let reducer = (a, x, i, arr) => {
let { num, count } = reducer
let reset = () => [reducer.num, reducer.count] = [x, 1]
if (i === 0) {
reset()
return a
}
else if (i === arr.length - 1) {
return [...a, { num , count: ++count }]
} else if (x === num) {
reducer.count++
return a
} else {
reset()
return [...a, { num, count }]
}
}
let fn = arr => arr.reduce(reducer, [])
fn(data); // ?
第 5 行
let reset = () => [reducer.num, reducer.count] = [x, 1]
由於 reducer()
內有兩處都必須對 num
與 count
做 reset,可抽出 reset()
共用之。
Conclusion
- 本文主要展示透過 ECMAScript 的 dynamic property 取代 global variable,
Object.assign()
與 array destructuring 只是讓寫法更精簡而已 - 在 function 內宣告小 function 亦是 ECMAScript 常用技巧,可讓 code base 可讀性更高,也更容易維護