ECMAScript 有個很有趣特性:Generic Method 可被其他型別透過 call()
使用,宛如自己的 Method 一般,這種 借用 Method
特性,使得該型別也擁有其他型別能力。
Version
macOS Mojave 10.14.5
VS Code 1.36.1
Quokka 1.0.238
ECMAScript 5
Ramda 0.26.1
Function.prototype.call()
let data = 'FP in JavaScript';
let usrFn = str => [].map.call(str, x => String.fromCharCode(x.charCodeAt() + 1)).join('');
usrFn(data); // ?
data
為 string,我們希望將每個 char 的 ASCII code 加 1
後再轉為 string。
若將 string 看成 array,這是很簡單的 map()
而已,但可惜 string 沒有 map()
可用。
因為 Array.prototype.map()
為 generic method,可透過 call()
被 string 借用,宛如 String.prototype.map()
一般。
至於 ASCII code 加 1
,可透過 fromCharCode()
與 charCodeAt()
實現。
最後再使用 join('')
將 array 轉回 string。
根據 ECMAScript spec,以下 method 均為 generic method,可使用 call()
被其他型別借用,因此稱為 generic
。
- Array.prototype
- concat()
- every()
- filter()
- forEach()
- indexOf()
- join()
- lastIndexOf()
- map()
- pop()
- push()
- reduce()
- reduceRight()
- reverse()
- shift()
- slice()
- some()
- sort()
- splice()
- toLocaleString()
- toString()
- unshift()
- Date.prototype
- toJSON()
- Object.prototype
- String.prototype
- charAt()
- charCodeAt()
- concat()
- indexOf()
- lastIndexOf()
- localeCompare()
- match()
- replace()
- search()
- slice()
- split()
- substring()
- toLocaleLowerCase()
- toLocaleUpperCase()
- toLowerCase()
- toUpperCase()
- trim()
Functional
import { pipe, map, join, add } from 'ramda';
let data = 'FP in JavaScript';
// toAscii :: String -> Number
let toAscii = str => str.charCodeAt();
// fromAscii :: Number -> String
let fromAscii = String.fromCharCode;
// mapFn :: String -> String
let mapFn = pipe(
toAscii,
add(1),
fromAscii
);
// usrFn :: String -> String
let usrFn = pipe(
map(mapFn),
join('')
);
usrFn(data); // ?
String 雖然如願以償得到了 map()
,但會發現 usrFn()
可讀性並不高,code 風格呈橫向發展。
原因在於 OOP 將 data 與 function 合一,function 必須掛在 data 下,而不能使用 pure function 與 pipeline 解決問題。
18 行
// usrFn :: String -> String
let usrFn = pipe(
map(mapFn),
join('')
);
其實 usrFn()
的大架構就是先用 map()
處理,最後再使用 join()
轉回 array,至於 map()
細節則由 mapFn()
處理。
11 行
// mapFn :: String -> String
let mapFn = pipe(
toAscii,
add(1),
fromAscii
);
mapFn()
流程為:
toAscii()
負責將 char 轉 ascii 值add(1)
負責將 ascii 值加1
fromAscii()
負責將 ascii 轉回 char
最後用 pipe()
整合所有 function,非常清楚。
第 5 行
// toAscii :: String -> Number
let toAscii = str => str.charCodeAt();
// fromAscii :: Number -> String
let fromAscii = String.fromCharCode;
將 charCodeAt()
與 fromCharCode()
重新以 pure function 形式改寫,適合 FP 使用 pipeline。
可以發現 FP 會不斷建立自訂 function,但卻看不到自訂 variable,因為 FP 會以 pipeline 處理,中繼 variable 都不見了
Conclusion
借用 method
是 ECMAScript 獨門概念,藉由call()
可傳入thisArg
取代this
,使得 generic method 可被其他 object 使用- OOP 強調 data 與 function 合一,因此 function 會以 method 形式掛在 data 上,若該型別 data 想使用其他型別的 method,且該 method 已經被認定是 generic method,就可使用
call()
去借用 method
,如同自己的 method 一般 - FP 並不存在
借用 method
概念,因為 FP 是 data 與 function 分離,function 並不是掛在 data 上的 method,只要 function 接受該型別 data 就可使用,比 OOP 彈性且更容易理解
Reference
Dr. Axel Rauschdayer, Array-Like Objects and Generic Methods