vue 的数据驱动渲染逻辑大家已经清楚,下面我们来研究下vue 的数据改变驱动视图重新渲染原理
记得第一节在讲vue 初使化发生了什么,提到Vue._init 然后执行了initstate,
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
重点是执行了observe(vm._data = {}, true);vm._data 是我们定义在组件data中的数据,返回的是一个对象,然后我们进入observe,这便是响应式的核心所在,
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
首先判断是否是对象,不是直接返回,这个函数最后返回了一个new Observe,来看下Observe的定义
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
}
}
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
这里判断了observe的对象如果是个数组的话,调用observeArray,如果是对象的话,调用defineReactive ,根据对象属性的数量,传入对象和属性名称,我们进入defineReactive
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
我们可以看到耳熟能详的,响应式核心,Object.defineProperty 函数在这之前还执行了observe(val)
这里是对对象的属性值进行循环observe,如果对象值仍然是个对象,那么把子对象的属性的get set 也进行劫持,有关于Object.defineProperty 这个函数大家可以自行查阅,是属于es5的一个函数,定义访问器属性,当对属性值赋值时,会调用对他定义的set方法,当取这个属性值时,会调用对他定义的get方法,我们来看对劫持属性get set方法的定义。get函数中,主要执行了dep.depend方法,下面的childOb是对调用$set 方法时提供的依赖收集,这里先不说,dep 时defineReactive开头new Dep()得来的,我们来看Dep的定义
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
addSub (sub: Watcher) {
this.subs.push(sub)
}
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
我们找到 depend方法,可以看到,调用了Dep.target.addDep方法,而Dep.target又是在哪定义的呢,我们先回顾下,组建的渲染过程,mountComponent,new Watcher,render,_update,patch, 我们定义的data中对象值的get函数,是在执行render过程中被调用的,而当我们在执行new Wathcer过程中,会执行pushTarget,把当前Watcher 赋值给Dep.target,,所以,执行Dep.target.addDep也就是传入的Watcher的addDep方法,我们进入Watcher中定义的addDep方法,
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this)
}
}
}
执行了传入参数的addSub方法,我们知道,我们来看这个参数是什么
调用的地方
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
就是调用depend方法那个dep,所以说,绕了一大圈,最后其实就是把当前组件的Watcher存入到dep的subs中去,最后调用的是dep.addSub,而传的参数是当前活跃的watcher也就是此劫持属性所处组件的Watcher,也就是,get方法的劫持就是把当前组件的Watcher存放到此属性值定义的时候初始化的dep的subs数组内,,然后我们来看set的劫持,最后执行了这个dep的notify。我们来看notify
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
对subs数组中所存放的所有Watcher对象执行update方法,也就是执行每个组件的Watcher的update方法,也就是页面重新渲染的具体逻辑,这里有一些优化合并的逻辑,我们下一节来讲
网友评论