ディスクリプタって?
たとえば、
let obj = { name: 'taro' }
Object.defineProperty(obj, 'age', {
value: 100,
writable: false
})
この第三引数をdescriptorという。
プロパティを細かく制御することができ、その制御モードを指示するオブジェクトのことをさす。
discriptorには2つタイプがある。
1. データディスクリプタ
value(, writable)をもつdiscriptor
{
value: 123,
writable: false
enumerable: false,
configurable: false
}
2. アクセサディスクリプタ
get, setをともにもつdiscriptor
{
get: () => { return hoge },
set: (val) => { hoge = val },
enumerable: false,
configurable: false
}
enumerableとconfigurableはともに持つことができる。
一方例えばvalueをもつときにget or setをもつとエラー。逆もしかり。
普通のattributes代入とどう違うか
obj.hoge = 120
はこれのショートハンド。
この場合は {value: 120, writable: true, configurable: true, enumerable: true}
でセットされたことになる
何が嬉しいか
- imutableな属性を作るなど堅牢性を増すことができる。
- setterを使ってobservableなオブジェクトを作れる
では作ってみる
堅牢性をましたオブジェクトを作る
シンプルにwritableをfalseにする。
let obj = {}
Object.defineProperty(obj, 'age', {
value: 100,
writable: false,
enumerable: false, // false: for (let prop in obj) で age を列挙対象にしない
configurable: false // false: ディスクリプタを変更可能としない
})
console.log(obj.age) // 100
obj.age = 1000
console.log(obj.age) // 100 ... 変わらない
setterいじってもいいかもしれないが、 writable: false
が楽
observableなオブジェクトを作る
(通知しすぎない(changedの検知), バリデーションなどは省略しました)
let obj = {}
let _age = 0
Object.defineProperty(obj, 'age', {
get () { return _age },
set (newValue) {
_age = newValue
this.noticeObservers() // setしたらリスナーに通知
},
enumerable: false,
configurable: false
})
// observerの登録と通知
let listenerA = {}
listenerA.update = (observable) => {
// 通知を受けたらなにかする
console.log(`listerA received notification $(observable.age) from!`)
}
obj.observers = [listernA]
obj.noticeObservers = () => {
this.observers.forEach((listener, _idx) => {
listener.update(obj)
})
}
関連
- Object.defineProperty / Object.defineProperties
- Object.create
- Proxy
このProxyを使うとより柔軟にオブジェクトに対する処理に、任意の処理を挟み込める。
ref
なぜか definePropertyのページにdiscriptorの説明が載っている。
このあたりをより発展させたProxyについては https://aloerina01.github.io/blog/2017-03-14-1 こちらが詳しくて助かった。