JavaScriptは後付けで言語組み込み機能を変えられます。例えば次です。
Array.prototype.forEach = ()=>console.log('これは書き換えられました') [1, 2, 3].forEach(item=>console.log(item))
配列内の値を総当たりする関数であるforEachをメッセージを出力する関数に書き換えました。やりたい放題です。この仕組み(詳しく知りたい人はプロトタイプとかプロトタイプチェーンとかでググるといいです)を利用してJavaScript組み込みの標準オブジェクトに機能を追加して拡張クラスとして扱う方法を紹介します。この方法には次の利点と欠点があります。
利点
- いちいちパッケージを呼び出したりしなくていい
- 都度これはパッケージかプリミティブオブジェクトか気にしなくていい
欠点
- 通常の名前と同じだが違う振る舞いを持つものを取り扱うことになるので何をやっているのかわかりにくい
- 属人化が過ぎる。他のプロジェクトメンバ、特に新規参入メンバと情報を共通化するのが手間
- 書き換えの場合、ライブラリ内などの思わぬところで意図しない挙動が起きる
要するにコーディングする人間が自分だけの趣味プロジェクトで楽するためのテクニックとして使うのが一番です。ちなみにこの手法はアンチパターンとしてMDNに取り上げられるくらい危険な手法です。複数人で行う開発ではブラウザに未実装の機能を埋める目的(つまりpolyfill)を除いて絶対に行うべきでないです。
継承とプロトタイプチェーン – JavaScript | MDN#悪い例: ネイティブのプロトタイプの拡張
プロトタイプの拡張は例えば次の様にできます。
// bootstrap.js 常に読み込むファイルに拡張機能を定義 /* eslint-disable no-extend-native */ if (!Array.prototype.unique) { // eslint-disable-next-line no-extend-native Array.prototype.unique = function() { return Array.from(new Set(this)); }; } // hoge/index.js 各所で読み込み、適宜使用 require('../bootstrap'); const uniqSortedArr = [3, 2, 3, 1] .unique() .sort(); console.log(uniqSortedArr); // [1, 2, 3]
Arrayそのままで使えて便利です。lodashの様な便利ライブラリでは安全性、可読性など様々な都合上プロトタイプ拡張が望ましくないためuniq(arr)の様になってしまいますが、この方法ならばメソッドチェーンもできます。
この方法はTypeScriptでそのまま実行すると次の様に”そのようなメソッドは存在しない”とコンパイルエラーが発生します。
error TS2339: Property 'unique' does not exist on type 'any[]'. 6 if (!Array.prototype.unique) {
~~~~~~
これの防止には標準オブジェクトの型を拡張する必要があります。これは次でできます。
/* eslint-disable no-extend-native */ interface Array<T> { // interfaceで定義すると自動で既存の宣言――グローバルオブジェクトであるArrayの型とマージされる unique(): Array<T>, } if (!Array.prototype.unique) { Array.prototype.unique = function() { return Array.from(new Set(this)); }; } const arrUniqSorted = [3, 2, 3, 1] .unique() .sort(); console.dir(arrUniqSorted);// [1, 2, 3]
こうすればTypeScript中でも標準ビルトインオブジェクトのプロトタイプ拡張ができます。後は適当に都度欲しいものを増やしていけば便利コードの出来上がりです。便利コードをGitHubで公開してリポジトリのURLを参照したnpm installないしyarn addを行うと複数プロジェクトでコードを共通できてなお扱いやすくなります。