每个组件实例都会对应一个watcher实例,在组件渲染过程中把接触过的数据property记录为依赖。当依赖项的setter触发时,会通知watcher,重新渲染组件
vue实例中的data选项,在初始化时会遍历该对象所有属性,并使用Object.defineProperty转为getter/setter。
Observer:主要是将对象转为响应式对象,在getter中,会创建Dep来收集依赖
Dep:用于存放watcher,即副作用函数effectFn
Watcher:观察者,当有数据更新时,会触发update
在src/core/instance/lifecycle.ts中的mountComponent中
export function mountComponent(vm: Component,el: Element | null | undefined,hydrating?: boolean
): Component {vm.$el = elcallHook(vm, 'beforeMount')let updateComponent/* istanbul ignore if */updateComponent = () => {vm._update(vm._render(), hydrating)}const watcherOptions: WatcherOptions = {before() {if (vm._isMounted && !vm._isDestroyed) {callHook(vm, 'beforeUpdate')}}}// we set this to vm._watcher inside the watcher's constructor// since the watcher's initial patch may call $forceUpdate (e.g. inside child// component's mounted hook), which relies on vm._watcher being already definednew Watcher(vm,updateComponent,noop,watcherOptions,true /* isRenderWatcher */)hydrating = false// flush buffer for flush: "pre" watchers queued in setup()const preWatchers = vm._preWatchersif (preWatchers) {for (let i = 0; i < preWatchers.length; i++) {preWatchers[i].run()}}// manually mounted instance, call mounted on self// mounted is called for render-created child components in its inserted hookif (vm.$vnode == null) {vm._isMounted = truecallHook(vm, 'mounted')}return vm
}
在src/core/instance/state.ts/initData中,主要是observe(data)这个
function initData(vm: Component) {let data: any = vm.$options.datadata = vm._data = isFunction(data) ? getData(data, vm) : data || {}if (!isPlainObject(data)) {data = {}}// proxy data on instanceconst keys = Object.keys(data)const props = vm.$options.propsconst methods = vm.$options.methodslet i = keys.lengthwhile (i--) {const key = keys[i]if (props && hasOwn(props, key)) {} else if (!isReserved(key)) {proxy(vm, `_data`, key)}}// observe dataconst ob = observe(data)ob && ob.vmCount++
}
Observer只追踪数据是否被修改,无法追踪新增和删除属性
Vue提供了Vue.prototype.$set和Vue.prototype.$delete,内部也是调用Observer的方法
import {set,del
} from '../observer/index'Vue.prototype.$set = set
Vue.prototype.$delete = del