存取器技术
存取器技术现在是热点。因为需要 ES5 getter/ setter 功能而未受广泛支持,但是它因语法优雅而弥补了这个弱点。
语法
定义提供者需要特殊的语法。简单的提供者对象必须被传入 observable
函数,这个函数可以用来把提供者对象转化为一个被监听的对象。
const provider = observable({
greeting: 'Hello',
subject: 'World'
})
const consumer = document.createElement('p')
简单的 handler()
映射语法弥补了这种小的不便。使用脏检查,我们不得不如下显式地定义每个被监听的属性。
observe(provider, 'greeting', greeting => {
consumer.innerHTML = greeting + ' ' + provider.subject
})
observe(provider, 'subject', subject => {
consumer.innerHTML = provider.greeting + ' ' + subject
})
这显得很啰嗦和笨拙。访问器技术可以自动检测在 handler()
函数中使用过的提供者属性,这样允许我们简化以上代码。
observe(() => {
consumer.innerHTML = provider.greeting + ' ' + provider.subject
})
observe()
的实现和脏检查的不同。它只是执行了传入的 handler()
函数,并且当handler()
运行的时候把函数标识为目前激活状态。
let activeHandler
function observe(handler) {
activeHandler = handler
handler()
activeHandler = undefined
}
注意,我们现在利用了 JavaScript 的单线程特性,使用唯一的 activeHandler
变量来记录目前运行的 handler()
的函数。
监听变化
这就是存取器技术名字的由来。提供者使用 getters/setters 来扩展,这两个方法在后台进行复杂的工作。思路是以如下方式拦截提供者属性的存取操作。
- get: 如果有一个
activeHandler
在运行,保存(provider, property) -> activeHandler
映射以备后用。 - set: 运行所有由
(provide, property)
映射的handler()
函数。
以下代码展示了一个提供者的属性变化的简单的实现过程。
function observableProp(provider, prop) {
const value = provider[prop]
Object.defineProperty(provider, prop, {
get () {
if (activeHandler) {
provider._handlers[prop] = activeHandler
}
return value
},
set (newValue) {
value = newValue
const handler = obj._handlers[prop]
if (handler) {
activeHandler = handler
handler()
activeHandler = undefined
}
}
})
}
在前面一节提到的 observable()
函数递归遍历提供者的属性,并且使用 observableProp()
函数来转化所有属性为可监测。
function observable (provider) {
for (let prop in provider) {
observableProp(provider, prop)
if (typeof provider[prop] === 'object') {
observable(provider[prop])
}
}
}
这是一个非常简单的实现,但是对于比较这两种技术已经足够了。