點燈坊

失くすものさえない今が強くなるチャンスよ

使用 Dynamic Property 取代 Global Variable

Sam Xiao's Avatar 2019-12-27

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 雖然可行,但總有些許遺憾。

global000

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() 新增 numcount property 模擬 global variable。

global001

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 雖然可行,但也有兩個缺點:

  1. 讀取 property 時都必須搭配 reducer. 稍嫌冗長
  2. 初始化 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 本身

global002

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 行。

global003

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() 內有兩處都必須對 numcount 做 reset,可抽出 reset() 共用之。

global004

Conclusion

  • 本文主要展示透過 ECMAScript 的 dynamic property 取代 global variable,Object.assign() 與 array destructuring 只是讓寫法更精簡而已
  • 在 function 內宣告小 function 亦是 ECMAScript 常用技巧,可讓 code base 可讀性更高,也更容易維護