◆ プロトタイプを直接拡張しないで プロトタイプ拡張風なことをする
◆ まず 型とメソッド定義したオブジェクトを関連付ける
◆ その後に好きな値の 「_」 プロパティ経由か pp 関数を通した値でメソッド呼び出しすると型に関連付いたメソッドが実行できる

プロトタイプにメソッドを色々追加していくと便利なのですが あまりやりたくない部分でもあったりします
ライブラリの内部だけでは使いたいけど外部では使える必要もないものもありますし プロトタイプなどあちこち書き換えず与える影響を少なめにしたい場合もあります

そこで 自分で型に対して疑似の prototype っぽいものを作れるような機能を作ってみました

機能紹介

ライブラリ部分のコードは最後にして機能と使い方の紹介です

まずはプロトタイプに加えたいメソッド類を定義したオブジェクトを作ります
通常のプロトタイプ拡張だとプロトタイプオブジェクトに Object.assign するオブジェクトです

const array = {
last(){ return this[this.length - 1] }
}

通常のプロトタイプ拡張するなら
Object.assign(Array.prototype, array)
となります

疑似 prototype では Object.prototype._.registerProto を使います
Object.prototype._.registerProto(Array, array, true)

コンストラクタとプロトタイプにするオブジェクトを指定します
3 つめの引数には this を使うかどうかです
定義したメソッドが this で自身にアクセスする通常のプロトタイプの作りなら true にします
this の代わりに第一引数で自身を受け取るのであれば false にします

const string = {
split2(str, splitter){
const idx = str.indexOf(splitter)
const point = idx < 0 ? str.length : idx
return [str.substr(0, point), str.substr(point + splitter.length)]
},
before(str, keyword){
return str.substr(0, Math.max(0, str.indexOf(keyword)))
},
after(str, keyword){
const idx = str.indexOf(keyword)
return str.substr(idx <= 0 ? 0 : idx + keyword.length)
}
}

Object.prototype._.registerProto(String, string, false)

設定したメソッドを使うには 「_」 プロパティを経由します

Object.prototype._.registerProto(String, string, false)
console.log("abc/def/ghi"._.split2("/"))
// ["abc", "def/ghi"]
console.log(pp("abc+++def").before("+++"))
// abc
console.log(pp("abc+++def").after("+++"))
// def

「_」 を経由した場合でも本来のメソッドが使えます
また継承関係もちゃんと辿ります
Foo クラスを継承した Bar クラスを作り Foo クラスのプロトタイプに baz メソッドを追加します
Bar クラスのインスタンスから baz メソッドにアクセスできます
class Foo {
foo() {
console.log("foo", this)
}
}
class Bar extends Foo {
bar() {
console.log("bar", this)
}
}
Object.prototype._.registerProto(Foo, { baz() {console.log("baz", this)} }, true)

new Bar()._.foo()
// foo Bar {}
new Bar()._.bar()
// bar Bar {}
new Bar()._.baz()
// baz Bar {}

_ を使わない

「_」 プロパティを使うために 「Object.prototype._」 だけは組み込みオブジェクトのプロトタイプが拡張されています
これすら嫌という場合は ライブラリ部分全体の無名関数の引数を false にすることでなくせます

その場合はグローバルに定義される pp 関数を使います

プロトタイプの設定は pp 関数の返り値の registerProto メソッドで行います
pp().registerProto(Array, { first(){return this[0]} }, true)

プロトタイプメソッドの使用は 「_」 の代わりに pp 関数に値を渡します

pp([100, 200]).first()
// 100

コード

!function(extends_prototype){
function PseudoProto(getter){
return Object.setPrototypeOf(new Proxy({}, {get: getter}), PseudoProto.prototype)
}
PseudoProto.prototype.registered = new Map()
PseudoProto.prototype.registerProto = function(type, pp, usethis){
const data = this.registered
const pps = data.get(type.prototype) || []
pps.push({pp, usethis})
data.set(type.prototype, pps)
}

function bindOrThrough(value, bind_args){
if(typeof value === "function"){
return value.bind(...bind_args)
}else{
return value
}
}

function ppGetter(){
const context = this
return new PseudoProto(function (target, name, receiver){
const proxy_proto = Object.getPrototypeOf(receiver)
if(name in proxy_proto){
return bindOrThrough(proxy_proto[name], [receiver])
}

const data = proxy_proto.registered
for(let proto = context;proto;proto = proto.__proto__){
const pps = data.get(proto)
if(pps){
for(const {pp, usethis} of pps){
if(name in pp){
const bind_argument = usethis ? [context] : [null, context]
return bindOrThrough(pp[name], bind_argument)
}
}
}
}

const value = context[name]
return bindOrThrough(context[name], [context])
})
}

window.pp = function(value){
return ppGetter.call(value)
}
extends_prototype && Object.defineProperty(Object.prototype, "_", {get: ppGetter})
}(true)

Gist