vue响应式原理
vue 2
先通过这几道题了解一下
01-getter-setter
Implement a convert
function that:
-
takes an Object as the argument
-
converts the Object's properties in-place into getter/setters using Object.defineProperty
-
The converted object should retain original behavior, but at the same time log all the get/set operations.
expected usage:
const obj = { foo: 123 }
convert(obj)
obj.foo // should log: 'getting key "foo": 123'
obj.foo = 234 // should log: 'setting key "foo" to: 234'
obj.foo // should log: 'getting key "foo": 234'
这道题是在对对象的读取进行拦截.
前面有篇文章讲过 defineProperty 对数据的拦截,可以点击这里查看,这里就直接贴出答案.
const convert = (obj) =>
Object.keys(obj).forEach((key) => {
let internalValue = obj[key]
Object.defineProperty(obj, key, {
get() {
console.log(`getting key "${key}": ${internalValue}`)
return internalValue
},
set(newValue) {
console.log(`setting key "${key}" to: ${newValue}`)
internalValue = newValue
},
})
})
const obj = { foo: 123 }
convert(obj)
obj.foo // should log: 'getting key "foo": 123'
obj.foo = 234 // should log: 'setting key "foo" to: 234'
obj.foo // should log: 'getting key "foo": 234'
02-dependency-tracking
- Create a
Dep
class with two methods:depend
andnotify
. - Create an
autorun
function that takes an updater function. - Inside the updater function, you can explicitly depend on an instance of
Dep
by callingdep.depend()
- Later, you can trigger the updater function to run again by calling
dep.notify()
.
The full usage should look like this:
const dep = new Dep()
autorun(() => {
dep.depend()
console.log('updated')
})
// should log: "updated"
dep.notify()
// should log: "updated"
下面我们来实现下这个 Dep 类,和 autorun 函数.
Dep 类包含两个方法: depend 和 notify.此外还要有一个属性: subscribers.来收集注册的方法,这里直接用 Set.
notify 方法容易写出,调用后就会触发所有注册的方法.
因为通过dep.depend()
来显示的声明依赖,所以需要一个全局变量来记录当前正在运行的 update 函数.
调用 dep.depend()时,会将当前正在运行的 update 函数加入到 subscribers 中.所以 depend 方法就直接添加 activeUpdate(activeUpdate 全局变量方便外部访问,即添加订阅的时候用) 到 subscribers 中.
autorun 方法的实现就比较简单了,先保存下当前正在运行的 update 函数,然后调用 update 函数(update 函数内部调用了 dep.depend())
class Dep {
constructor() {
this.subscribers = new Set()
}
depend() {
if (activeUpdate) {
// register the current active update as a subscriber
this.subscribers.add(activeUpdate)
}
}
notify() {
// run all subscriber functions
this.subscribers.forEach((subscriber) => subscriber())
}
}
let activeUpdate
const autorun = (update) => {
activeUpdate = update
update()
activeUpdate = null
}