-
-
Save lv7777/9d71f48ad8a8ba4a2e28fec4105e104f to your computer and use it in GitHub Desktop.
Symbolを利用したprivate/protectedのプロパティ及びメソッドを実装しました。Symbolを利用してるのでgetOwnPropertySymbolsでリフレクション出来ます
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const classMap = new Map(); | |
/* | |
MapとObjectの違い。 | |
*/ | |
//ES6では新しくsymbolという型が定義された。こいつの場合は名前なしシンボルで | |
//symbolの引数はそのシンボルの説明。そのsymbol自身にアクセスはできない。 | |
const inheritPrototype = Symbol(); | |
const isAccessModifiers = Symbol(); | |
// Proxy使用時用のハンドラ | |
const proxyHandler = { | |
set(obj,name,value){ | |
// Symbol以外はセットしないようにする | |
if(typeof(value)!=="symbol") return; | |
obj[name] = value; | |
}, | |
get(obj,name){ | |
// 指定されたキーがSymbol型の場合は強制的にそれを返す | |
if(typeof(name) === "symbol") return obj[name]; | |
// 対象となるメンバが無ければ、継承元に存在しないかを見る | |
let target = obj; | |
while(target){ | |
// 対象となるシンボルメンバが存在したら、それを返す | |
if(typeof(target[name]) === "symbol"){ | |
return target[name]; | |
} | |
// 存在しなければ、prototypeチェーンを辿って | |
// 一つ前のクラスのprotectedメンバ一覧を参照する | |
target = target[inheritPrototype]; | |
} | |
// 完全にメンバが存在しなければ、Symbolを新規作成する | |
obj[name] = Symbol(name); | |
return obj[name]; | |
}, | |
}; | |
class AccessModifiers{ | |
static create(baseClass, methodList){ | |
// 省略可能引数の中身をチェックする | |
if(typeof(baseClass) !== "function"){ | |
methodList = baseClass; | |
baseClass = null; | |
} | |
// methodListが指定された場合は、それがiterableかどうかを見る | |
if((typeof(methodList) !== "undefined") && (!methodList || !methodList[Symbol.iterator])){ | |
throw new TypeError("第一引数もしくは第二引数のmethodListにはiterableを指定してください"); | |
} | |
// 登録されている基底クラスのメンバ情報を取得する | |
const baseValue = classMap.get(baseClass); | |
// 戻り値を仮定義する | |
const modifiers = [ | |
// private用の識別子オブジェクト | |
{ | |
// このオブジェクトがアクセス修飾子オブジェクトであることを示すインターフェイス | |
[isAccessModifiers]: true, | |
// privateには継承という概念がないので常にnullにする | |
[inheritPrototype]: null, | |
}, | |
// protected用の識別子オブジェクト | |
{ | |
// このオブジェクトがアクセス修飾子オブジェクトであることを示すインターフェイス | |
[isAccessModifiers]: true, | |
// 基底クラスが既に登録されていれば | |
// 基底クラスのprotectedプロパティリストをprototypeに繋ぐ | |
[inheritPrototype]: (baseValue && baseValue[1]) || null, | |
}, | |
]; | |
// methodListがある場合は、Proxyを使用せずに通常のオブジェクトとして作成する | |
if(methodList){ | |
// 無限リスト対策として、iterableをシコシコnext連打していく | |
const itr = methodList[Symbol.iterator](); | |
let value,done; | |
({value,done} = itr.next()); | |
if(done || !value || !value[Symbol.iterator]){ | |
throw new TypeError("methodListにはiterableな要素を入れてください。"); | |
} | |
for(const name of value){ | |
if((typeof(name) !== "string") && (typeof(name) !== "symbol")){ | |
throw new TypeError("methodList[0]にはstringもしくはsymbol型の要素を入れてください。"); | |
} | |
modifiers[0][name] = Symbol(name); | |
} | |
({value,done} = itr.next()); | |
if(done || !value || !value[Symbol.iterator]){ | |
throw new TypeError("methodListにはiterableな要素を入れてください。"); | |
} | |
for(const name of value){ | |
if((typeof(name) !== "string") && (typeof(name) !== "symbol")){ | |
throw new TypeError("methodList[1]にはstringもしくはsymbol型の要素を入れてください。"); | |
} | |
modifiers[1][name] = Symbol(name); | |
} | |
} | |
// methodListが存在しない場合は、ProxyでSymbolを生成するようにする | |
else{ | |
for(const [index,value] of modifiers.entries()){ | |
modifiers[index] = new Proxy(value, proxyHandler); | |
} | |
} | |
// 作成した修飾子を返す | |
return modifiers; | |
} | |
static register(modifiers, inheritClass){ | |
if(!modifiers || !modifiers[Symbol.iterator]){ | |
throw new TypeError("第一引数にはiterableを指定してください"); | |
} | |
const [_,p] = modifiers; | |
if(!_[isAccessModifiers] || !p[isAccessModifiers]){ | |
throw new TypeError("第一引数のiterableには、アクセス修飾子オブジェクトを指定してください"); | |
} | |
if(_[inheritPrototype]){ | |
throw new TypeError("第一引数のiterable[0]には、privateアクセス修飾子オブジェクトを指定してください"); | |
} | |
if(!(inheritPrototype in p)){ | |
throw new TypeError("第一引数のiterable[1]には、protectedアクセス修飾子オブジェクトを指定してください"); | |
} | |
if(typeof(inheritClass)!=="function"){ | |
throw new TypeError("第二引数にはclassもしくはfunctionを指定してください"); | |
} | |
// バリデーションを通ったら、クラスを登録する | |
classMap.set(inheritClass, modifiers); | |
} | |
} | |
export default AccessModifiers; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import AccessModifiers from "./AccessModifiers.js"; | |
import Human from "./Human.js"; | |
// 継承するクラスの場合は、第一引数に継承元クラスを指定する | |
const [_,p] = AccessModifiers.create(Human); | |
class Gao extends Human{ | |
constructor(){ | |
super(18); | |
this[_.trueAge] = 38; | |
} | |
get name(){ | |
return "がお"; | |
} | |
get trueAge(){ | |
return this[_.trueAge]; | |
} | |
say(){ | |
return this[_.createProfile](); | |
} | |
[_.createProfile](){ | |
return `${this.name}${this.name}${this.name}~~~www(${this.age})`; | |
} | |
} | |
AccessModifiers.register([_,p],Gao); | |
export default Gao; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import AccessModifiers from "./AccessModifiers.js"; | |
// createメソッドでprivate及びprotectedのSymbolを生み出す魔法の変数を生成する | |
const [_,p] = AccessModifiers.create(); | |
class Human{ | |
constructor(age){ | |
this.age = age; | |
} | |
set age(value){ | |
if(!Number.isSafeInteger(value)){ | |
throw new TypeError("ageは整数型の変数です"); | |
} | |
this[p.age] = value; | |
} | |
get age(){ | |
return this[p.age]; | |
} | |
} | |
// 最後にregisterメソッドで、たった今作成したクラスと魔法の変数を対応付ける | |
AccessModifiers.register([_,p],Human); | |
export default Human; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Gao from "./Gao.js"; | |
const gao = new Gao(); | |
console.log(gao.say()); // "がおがおがお~~~www(18)"" | |
console.log(gao.trueAge); // 38 | |
console.log(Object.getOwnPropertySymbols(gao)); // [Symbol(age), Symbol(trueAge)] | |
console.log(Object.getOwnPropertySymbols(Gao.prototype)); // [Symbol(createProfile))] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
解説を書く。