前篇
上面文章讲到了Vue的声明、Vue的Init和Vue的InitState,本篇将对Vue核心的Observe进行讲解和分析,Observe分为Dep和Watcher两部分,入口文件为src/core/observe/index.js。我们一步一步来看下源码。
正文
入口observe
Observe对外只暴露了一个函数observe,Observer类虽然给了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
}
上面就是observe函数的源代码;
- 首先检测了传进来的value,不是对象或者是VNode(虚拟dom)对象,则直接return;
- 然后判断了一下,当前value是否是已经进行了Observe处理的对象;
- 上面步骤为false时,进行判断是否需要进行监听,并且不是服务端渲染,并且是可监听对象,可扩展对象,不是Vue对象,则对value进行Observer初始化;
- vmCount是ob的一个属性,初始值为0,当asRootData为true且ob不为空的时候,vmCount + 1;
- 返回ob对象; 此函数作为监听的入口文件,对数据进行拦截判断,返回一个Observer的实例。
类Observer
Observer是一个class,有三个私有属性:value、dep、vmCount;一起来看下其构造函数:
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)
}
}
构造函数干了四件事情:
- 赋值,为私有变量赋值,value为传进来的参数value;dep为Dep的实例,后面讲Dep;vmCount默认值为0;
- 定义__ob__,为当前value定义__ob__属性,此属性指向Observer的当前实例-this;
- 如果value为数组,支持__proto__属性则执行protoAugment,否则执行copyAugment;最后调用observeArray;
- 不为数组,则执行walk;
上面讲到的【支持__proto__属性】,现在主流浏览器都支持的,除了IE;
数组处理
protoAugment和copyAugment方法的参数,参数差别就是最后一个参数arrayKeys;
- 第一个参数为当前value;
- 第二个参数为arrayMethods
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
methodsToPatch.forEach(function (method) {
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
// notify change
ob.dep.notify()
return result
})
})
上面会对arrayMethods里面的值进行重新定义,也就是重写会引起数组变化的方法,以达到对数组进行监听的目的。
- 第三个参数,其实也就是当前浏览器所支持的所有的数组的方法。
const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
接下来,咱们分别看下两个方法(protoAugment和copyAugment)的实现:
function protoAugment (target, src: Object) {
target.__proto__ = src
}
function copyAugment (target: Object, src: Object, keys: Array<string>) {
for (let i = 0, l = keys.length; i < l; i++) {
const key = keys[i]
def(target, key, src[key])
}
}
- protoAugment方法直接把数组的__proto__指向了src,这样通过array调用arrayMethods里面的方法的时候,就是调用的重写后的方法,也就达到了对数组进行监听的目的; copyAugment方法,因为不支持__proto__的缘故,则需要在数组上面覆盖原生的arrayMethods里面的方法,也就达到了对数组进行监听的目的。
数组走observeArray
observeArray非常简单:
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
循环调用上面文章开头介绍的observe方法;最终都会走到下面要说的walk方法。
非数组走walk
walk函数也是非常简单:
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
获取到当前对象的keys后,对keys进行遍历,遍历调用defineReactive,传递两个参数,参数1为当前对象,参数2为当前遍历到的key。
defineReactive
接下来是重头戏,这才是Vue真正的核心之一。来看下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
}
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 () {
.....
},
set: function reactiveSetter (newVal) {
......
}
})
}
先说下调用defineReactive的地方:
- initInjections,对依赖进行处理的时候,会对inject的key进行响应化调用;
- initRender,对listeners对象进行浅式响应化调用;
- initState里面的initProps,会对props进行响应化调用;
- 上面说到的walk里面会调用;
- set函数里面会调用,包括Vue.set和原型对象上的$set里面;
接着说下defineReactive函数的参数:
- 第一个就是要进行响应式处理的对象,obj;
- 第二个为当前对象下面的一个属性,key;
- 第三个为默认值,val;
- 第四个为customSetter,是一个函数,用户设置的set函数时的回调,不过这个函数只有非线上环境才会调用;
- 第五个参数为是否是浅式响应化,如果是浅式则不会对子对象进行监听。
不用看着defineReactive很长,其实就干了三件事情,一一来看 defineReactive源码解读:
- 首先声明了一个dep,后面研究Dep是干啥的;
- 接着判断了当前对象的当前属性是否是可改变,不可改变,直接返回;其实个人觉得,上面的dep声明,,可以放到这后面来,有点浪费;
- 判断了下是否是无getter或者只有setter,且只有两个参数的时候,会把默认值val设置为obj[key];
- 最后调用Object.defineProperty,重新声明obj对key的处理方式。
然后咱们接下来看下重新声明后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
}
上面代码是重新定义的get方法,先是调用原生getter方法获取到value,然后判断是否有Dep.target,Dep.target是一个Watcher对象,然后调用收集依赖的函数dep.depend(),然后依次判断childOb,收集依赖;每次调用defineReactive,都有一个唯一的Dep实例与当前value一一对应。
function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
上面代码是重新定义的set方法,先是调用原生getter方法获取到value,然后对newVal进行判断,如果未发生变化则直接返回; 如果只有getter没有setter则直接返回;然后调用原生setter进行赋值,后面调用dep.notify进行通知更新;notify会调用dep对象下面所有的依赖watcher对象下面的update方法进行更新操作;下面咱们会讲到Dep。
Dep
上面讲到了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 () {
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
上面代码就是去除生产环境后的代码。
可以看到Dep对象有三个变量:一个是target,target对象是一个Watcher对象,同时是一个static的对象;id为一个数字的变量;subs则为一个Watcher对象的数组;
- 构造函数为id和subs赋值;
- addSub为依赖收集的函数;
- removeSub为删除依赖的函数;
- depend为依赖收集的函数,此处会调用Watcher实例的addDep函数(会有去重操作);
- notify函数则是通知函数,此处会循环所有的依赖(Watcher实例),然后调用实例的update方法。 额外要讲的是,除了Dep的声明外,还有Dep.target这个static类型的变量的处理:
Dep.target = null
const targetStack = []
export function pushTarget (target: ?Watcher) {
targetStack.push(target)
Dep.target = target
}
export function popTarget () {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}
此处会对Dep.target进行赋值等操作,pushTarget和popTarget是成对出现的,有一个pushTarget则必然有一个popTarget;target则依旧是一个Watcher;暴露给外部调用,收集Watcher所使用,下次咱们会讲到Watcher。
结言
本章沿着observe函数进行了一步步的探索,从Observer到Dep,下一篇文章会进行Watcher的讲解。