Skip to content

Instantly share code, notes, and snippets.

@gaogao-9
Last active May 8, 2016 15:01
Show Gist options
  • Save gaogao-9/aa731d048b89b561e7d6 to your computer and use it in GitHub Desktop.
Save gaogao-9/aa731d048b89b561e7d6 to your computer and use it in GitHub Desktop.
Symbolを利用したprivate/protectedのプロパティ及びメソッドを実装しました。Symbolを利用してるのでgetOwnPropertySymbolsでリフレクション出来ます
const classMap = new Map();
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;
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;
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;
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))]
@lv7777
Copy link

lv7777 commented May 7, 2016

読ませていただきました。とても参考になりました。すごいです。

あともしよければ解説を書いてもらえないでしょうか?AccessModifiersの動きがよくわからない・・・

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment