简单的响应
给定 a 和 b 两个参数,要求无论 a 如何变化,b 永远等于 a 的 10 倍,如何实现?
即如下效果:
let a = 3
let b = a * 10
console.log(b) // 30
a = 4
b = a * 10
console.log(b) // 40
在每次改变 a 的值之后手动更新 b 的值,这是山顶洞人的做法,我们想要的是 a 的值改变之后,自动执行 b 的改变函数。
我们可以维护一个 state 对象:
state = {
a: 10,
b: 100
}
然后每次 a 发生变化时执行:
onStateChanged(() => {
state.b = 10 * state.a
})
要实现这个很简单,我们可以每次改变 a 的值从原来的直接赋值变为调用一个函数:
setState({ a: 5})
在 setState
这个函数里做赋值操作,同时执行 onStateChanged
函数使 b 的值自动得到更新。
完整的实现如下:
let update
let state = {}
const onStateChanged = _update => {
update = _update
}
const setState = newState => {
state = newState
update()
}
onStateChanged(() => {
state.b = 10 * state.a
})
state = {
a: 10,
b: 100
}
setState({ a: 5})
console.log(state.b) // 50
setState({ a: 50})
console.log(state.b) // 500
严格来说应该把 state
,update
,onStateChanged
,setState
封装起来,当初始化值之后,只允许通过 setState
来改变变量的值。这其实已经有点像 react 了。
使用 Object.defineProperty
这里用到一个对象方法 Object.defineProperty,目的是将 data
对象里的所有属性通过这个方法来重新定义,重写属性的修改(setter
)方法。
// 初始化 data 对象
const data = {
a: 10,
b: 100
}
// 将属性 a 的值临时存放
let value = data.a
// 重写 data.a 的方法
Object.defineProperty(data, 'a', {
get() {
return value
},
set(val) {
value = val
data.b = 10 * val // 这样一来每次对 a 重新赋值时便会自动更新 b 的值
}
})
console.log(data.b) // 100
data.a = 3
console.log(data.b) // 30
vue 的响应
使用 Object.defineProperty 方法已经和 vue 的实现有点接近了,vue 实例是将 data
对象里的所有属性通过这个方法来重新定义,实现属性被访问(getter
)和修改(setter
)时通知(Notify
)变化(Watcher
调用函数)。
即实现如下功能:
const data = {
a: 0
}
// observe 函数将 data 的属性重写
observe(state)
// triggerFunction 接受一个函数作为参数,当 data 中属性值改变时自动调用 triggerFunction 里的函数
triggerFunction(() => {
console.log(state.a)
})
// 0
state.count++
// 1
具体的实现:
// 在全局定义一个 Watch 类
window.Watcher = class Watcher {
constructor () {
// 订阅所有 Watch 搜集到的属性
this.subscribers = new Set()
}
depend () {
if (activeUpdate) {
// 将属性自动更新方法添加到订阅列表中
this.subscribers.add(activeUpdate)
}
}
notify () {
// 当属性被重新赋值时触发 setter 函数通知 Watch 做出改变
this.subscribers.forEach(subscriber => subscriber())
}
}
let activeUpdate
function observe (obj) {
if (!isObject(obj)) {
throw new TypeError()
}
Object.keys(obj).forEach(key => {
let internalValue = obj[key]
let watcher = new Watcher()
Object.defineProperty(obj, key, {
get () {
watcher.depend()
return internalValue
},
set (newValue) {
const isChanged = internalValue !== newValue
if (isChanged) {
internalValue = newValue
watcher.notify()
}
}
})
})
}
function triggerFunction (update) {
function wrappedUpdate () {
activeUpdate = wrappedUpdate
update()
activeUpdate = null
}
wrappedUpdate()
}
上面的 triggerFunction
只是简单地 console.log
出 a
的值,其实可以进一步地实现 re-render
函数,使得页面的 html
元素也自动重新渲染,便是完整的 vue 响应了。