Methoの導入:JSに超大国を安全に追加する



TLドクター
Methoを使用すると、任意のオブジェクトに動的プロパティの形式でメソッドを簡単かつ安全に追加できます.退屈に聞こえるが、ネイティブ型を拡張するのに使われるならば、それはいくぶんユニークな構文でJS表現の建設を考慮に入れます:
// Add a range syntax to numbers
1[to(9)]  // [1, 2, 3, 4, 5, 6, 7, 8, 9]

// Give numbers properties
13[isOdd]  // true
99[isEven]  // false
45.3[floor]  // 45
254[hex]  // 'fe'

// Repeat stuff
5[times(myFunction)]  // run myFunction 5 times

// Use with JSX
10[of(<div>Hello</div>)]  // 10 divs

// Go nuts!
'hello!'[titleCase][reverse][chunk(2)]  // ['!o', 'll', 'eH']

動機/インスピレーション
私は最近「ネイティブ」の範囲構文/メソッドを作成することについて同様のポストを読みました:


それはいくつかの興味深いアイデアを持っていたが、それは非常によく読まなかったし、安全ではない(猿のネイティブオブジェクトをパッチ)構文を使用しました.私はいくつかの他の可能な構文のためのいくつかのアイデアを持っていたが、彼らが動作するかどうか、または可能であるかどうかわからなかった.私はいくつかの実験をしました、そして、それがわかるように、彼らは働きました、そして、安全な方法で実行されることができました.使用されるテクニックはまた、多くの興味深い構文を可能にする柔軟なツールに一般化されることができました.

……いったいどうやってこの仕事をしますか.
確かに、上記の例は、有効なJavaScriptのようにさえ見えません-しかし、彼らはそうです!JSの数、文字列、および他の型は基本的に単なるオブジェクトであり、オブジェクトにはプロトタイプ、メソッドなどがあります.ネイティブ型は、新しい機能を与えることができます.
しかし、これらのネイティブ型を変更することは、あなたの変更が他のライブラリ、またはJS自体への将来の変更と衝突しないことが保証されていないので、良い考えではありません.そこで、我々は、提案された構文を使用して、ネイティブ型に機能を追加する機能があるが、安全な方法で何かを構築するにはどうすればよいですか?

ステップ1:'安全'モンキーパッチ
どのような方法でオブジェクトにメソッドを追加することができれば、既存のメソッドと競合しないようにするか、将来のメソッドを追加することができます.まあ、使用することができますSymbol これらは、JSに比較的新しい追加ですが、非常に便利です.本質的にSymbol 完全にユニークな値です-他の何もそれに等しい、またはそれに匹敵することができます.これらは次のようにして作成されます.
const mySymbol = Symbol('My symbol description')
それだ!あなたは全くユニークな値を作成しました.シンボルに与えられた説明は完全に任意ですが、デバッグにおいて有用です.
どのように、我々は利益を得ますか?まあ.Symbol sはオブジェクトキーとして使用できます-完全にユニークな'名前'でメソッドを作成する能力を与えます.これはどのように安全にモンキーパッチすることができます.

ステップ2 :括弧を使用せずにメソッドを呼び出す
初期の例では、おそらく、呼び出し元のメソッドが見つからなかった場合には、通常は、あなたが期待している括弧が見つからないことに気がつきました.
13[isEven]  // false
これはどうやって成し遂げたの?プロパティの取得
私たちはObject.defineProperty 不活発でないオブジェクトのプロパティを定義するには' getter '関数の結果を返します.したがって、括弧を使用せずに独自のメソッドの一つを呼び出して、Symbol と' getter '関数があります.

ステップ3 :パッシングパラメータ
残念なことに、プロパティゲッターを使用することで、私たち自身が問題を起こしました.我々が許容しようとしている構文
1[to(8)]  // [1, 2, 3, 4, 5, 6, 7, 8]
私たちが以前持っていた場所の関数呼び出しを持っていますSymbol . 私たちは効果的にパラメータを「getter」関数に渡したいです.
私はこの点でほとんどあきらめたが、

“What if to was a function that returned a symbol that was the 'name' of a method that had been dynamically created as a property getter on the target object by to, for our purposes? Then, this getter would immediately be called automatically because the returned symbol appears as the key in a bracket notation property-access of the object”


(はい、パーティーではホントです)
ビンゴ!うまくいった.我々は単に😛 動的に生成された関数(既に渡されたパラメータを持つ)をラップして別の関数としてラップするSymbol を返します.Symbol . 動的に作成されたメソッドも、呼び出されたときに削除されます.ラッパー機能は、我々のものになりますto '方法」.
フィル!もしそれを理解しているなら、おそらくMethoのコードに興味を持っています.
function addWithParams(target, method) {
  return(function(...args) {
    const s = Symbol()
    Object.defineProperty(target, s, {
      configurable: true,
      get: function() {
        delete target[s]
        return method.apply(this, args)
      }
    })
    return s
  })
}
これは明らかにこの構文を使用するメソッドを呼び出すときに追加のオーバーヘッドを作成するので、パフォーマンスが問題であるならば、それは規則的な特性(Methoで可能である何か)として保存されるメソッドのために素晴らしい構文を犠牲にするのがよりよいかもしれません.の場合to - 次のようになります.
1[to](3)  // [1, 2, 3]

を使う
私は上記のメカニズムを抽象化するためにmethoを書きました、そして、メソッドコードを書くことに集中するのは簡単にします.'範囲'の例は次のように実装できます.
import * as Metho from 'metho'

const to = Metho.add(
  Number.prototype,
  function(end, {step} = {step: this<=end?1:-1}) {
    let arr = [], i, d = end>this
    for (i=+this; d?(i<=end):(i>=end); i+=step) arr.push(i)
    return arr
  }
)

console.log(1[to(3)])  // [1, 2, 3]
console.log(7[to(4)])  // [7, 6, 5, 4]
console.log(2[to(10, {step: 2})])  // [2, 4, 6, 8, 10]
これは速くて汚い例です-そして、おそらく範囲機能の最高の実装ではなく、あなたは考えを得ます.
同様に、数値の単純な' hex 'プロパティを実装することができます.
const hex = Metho.add(
  Number.prototype,
  function() { return this.toString(16) }
)

console.log(65535[hex]) // 'ffff'

次は何ですか.
次の論理ステップは、ネイティブのJavaScriptタイプの役に立つ拡張子のライブラリを構築することです.私は、持っている素晴らしい機能のリストをコンパイルしようとしています.
アイデア歓迎!🚀

jonrandy / metho
新しい方法