下面开始实现代码
import Dep from './dep'
import { isObject } from '../utils'// 将对象定义为响应式
export default function reactive(data) {
if (isObject(data)) {
Object.keys(data).forEach(key => {
defineReactive(data, key)
})
}
return data
}
function defineReactive(data, key) {
let val = data[key] // 收集依赖
const dep = new Dep()
Object.defineProperty(data, key, {
get() {
dep.depend()
return val
},
set(newVal) {
val = newVal
dep.notify()
}
})
if (isObject(val)) {
reactive(val)
}
}
代码很简单,就是去遍历data的key,在defineReactive函数中对每个key进行get和set的劫持,Dep是一个新的概念,它主要用来做上面所说的dep.depend()去收集当前正在运行的渲染函数和dep.notify() 触发渲染函数重新执行。
可以把dep看成一个收集依赖的小筐,每当运行渲染函数读取到data的某个key的时候,就把这个渲染函数丢到这个key自己的小筐中,在这个key的值发生改变的时候,去key的筐中找到所有的渲染函数再执行一遍。
Dep
export default class Dep {
constructor() {
this.deps = new Set()
} depend() {
if (Dep.target) {
this.deps.add(Dep.target)
}
}
notify() {
this.deps.forEach(watcher => watcher.update())
}
}
// 正在运行的watcher
Dep.target = null
这个类很简单,利用Set去做存储,在depend的时候把Dep.target加入到deps集合里,在notify的时候遍历deps,触发每个watcher的update。
没错Dep.target这个概念也是Vue中所引入的,它是一个挂在Dep类上的全局变量,js是单线程运行的,所以在渲染函数如:
document.getElementById('app').innerHTML = `msg is ${data.msg}`运行之前,先把全局的Dep.target设置为存储了这个渲染函数的watcher,也就是:
new Watcher(() => {
document.getElementById('app').innerHTML = `msg is ${data.msg}`
})
这样在运行途中data.msg就可以通过Dep.target找到当前是哪个渲染函数的watcher正在运行,这样也就可以把自身对应的依赖所收集起来了。
这里划重点:Dep.target一定是一个Watcher的实例。
又因为渲染函数可以是嵌套运行的,比如在Vue中每个组件都会有自己用来存放渲染函数的一个watcher,那么在下面这种组件嵌套组件的情况下:
// Parent组件
<template>
<div>
<Son组件 />
</div>
</template>










