vue实现了响应式的双向绑定,相对于传统的dom操作,我们仅需要关心model的逻辑处理,vue将帮助我们完成view端的映射和渲染,这种模式大大提高了编程的效率。接下来的两个章节我们将一起了解下vue响应式的实现,在学习源码前,建议先阅读VUE探索第一篇--双向绑定原理了解响应式的原理。
vue的响应式采用的是观察者+发布订阅相结合的模式来实现。模型的示意图如下:
这种设计非常像军队的组织,我们来做个角色扮演。
1、observe(观察者),侦查兵,观察敌情的变化,一旦有异动,要立即报告。vue的响应设计中,利用defineProperty劫持属性数据的get,set方法,实现变化的监听。
2、dep(发布类),指挥所,所谓知己知彼,百战不殆,一方面要充分了解自己的部队部署,另一方面,要对敌情的变化快速做出决策。vue的响应设计中,发布类存储每个属性数据相关的订阅类,一旦数据有更新,通知关联的订阅类执行更新操作。
3、watcher(订阅类),野战军,根据指挥部下达的的命令,实施作战。vue的响应设计中,属性表达式封装为订阅类,接受发布类的数据变更通知,通过回调,实现视图的更新。
在第五章节,我们讲到initProvide,initProps,initData等方法时,出现了很多observer,defineReactive等方法,当时仅让大家做初步了解,那么本篇就为大家详细分析其过程。下面我们就已initData为例,详细了解下其实现原理。
function initData (vm: Component) {
...
observe(data, true /* asRootData */)
}
接下来我们以下面定义的data为例,看下observe是如何创建的。
data:{
msg:'this is msg'
items:[
{id:1},
{id:2},
{id:3}]
}
1、observe
observe该方法位于src/core/observe/index.js
export function observe (value: any, asRootData: ?boolean): Observer | void {
//1、value不是对象,或者是VNode类型,则不需要建立observe对象
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
//2、判断属性是否创建过observe,如已存在则返回对象,否则需要创建
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
) {
//3、核心代码,创建Observer对象
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
该方法的入参value就是定义的data值,asRootData表示是否是根data。
(1)、传入的属性如果不是对象(比如是常量),或者是VNode类型,则不需要创建observe;
(2)、判断属性是否创建过observe,如果已存在,则直接返回;
(3)、符合一系列的判断条件后,进入核心代码,调用new Observer创建对象。
Observer是一个class类。
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data
constructor (value: any) {
//1、初始化
this.value = value
this.dep = new Dep()
this.vmCount = 0
//2、定义属性‘__ob__’,def是defineProperty的简单封装
def(value, '__ob__', this)
//3、对于Array对象,则循环创建observer对象
if (Array.isArray(value)) {
const augment = hasProto
? protoAugment
: copyAugment
augment(value, arrayMethods, arrayKeys)
this.observeArray(value)
} else {//4、为每个属性注入getter/setter方法
this.walk(value)
}
}
/**
* Walk through each property 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) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
我们来看下构造函数。
(1)、初始化相关的变量,包括value(属性对象),dep(发布类对象),vmcount(计数器)。
(2)、在属性对象上定义一个"_obj_"属性,保存observer对象。属性是否创建observer对象,就是通过该值判断的。
(3)、属性对象如果是数组,则调用observeArray循环创建observer对象。
observeArray (items: Array) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
(4)、对于非数组,则调用walk方法为每个属性注入getter/setter方法,实现数据监听。
实例中data为非数组,进入walk执行方法。
2、walk
walk的方法比较简单,循环每个属性数据对象,调用defineReactive方法
walk (obj: Object) {
const keys = Object.keys(obj)
//循环属性对象,每个属性对象增加监听
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
如实例data中,key值分别是msg,items,进行两次循环,分别调用defineReactive。
3、defineReactive
defineReactive是实现观察者的核心类,在props和provide初始化中,就跳过了observer,而直接调用该方法。通过Object.defineProperty对数据属性的get和set方法的劫持,实现依赖收集和通知更新。
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
//1、创建属性数据发布器
const dep = new Dep()
...
///2、如果属性的值也是对象,递归为每个对象创建observe对象
let childOb = !shallow && observe(val)
//3、核心方法,利用defineProperty方法进行数据劫持。
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()
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
//通知更新
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
(1)为该属性创建发布器对象。
(2)如果属性值也是对象,则递归调用observe创建对象,如本例中的items。
(3)通过defineProperty,对get,set方法注入劫持,dep.depend实现依赖收集,dep.notify实现通知更新。
此data实例中,最终生成的属性对象与observe属性关系图如下:
接下来,我们看下是如何通过发布器实现依赖收集和通知更新的。
从上我们知道每个属性数据对象包含一个Observe对象,observe中会创建一个Dep对象。dep类位于src/core/observe/dep.js
export default class Dep {
static target: ?Watcher;//当前的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)
}
//Dep.target为当前的watcher
//将当前的watcher添加到相应的发布器Dep,进行依赖收集
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
//通知相关的watcher类更新
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
Dep.target = null
const targetStack = []
export function pushTarget (_target: ?Watcher) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
}
export function popTarget () {
Dep.target = targetStack.pop()
}
1、Dep类定义了三个属性变量,并在构造函数中进行初始化。
(1)、target,当前的watcher对象。
(2)、id,Dep对象的唯一标示,该标示自增长。
(3)、subs,Watcher订阅对象的集合。
2、定义了一系列的方法,add,remove,实现watcher集合的添加和删除。depend,将当前的watcher添加到集合中并完成依赖收集;notify,循环集合,调用每个的watcher的update方法实现更新。(如何实现收集和更新,我们下一篇详细分析)
3、定义了全局变量Dep.target,targetStack来保存当前的watcher对象。
经过dep的初始化,实例的observe与dep的关系图如下:
此时,我们还没有在对象上添加任何watcher,所以sub集合都是空集。
本章节主要通过对initdata的过程介绍了响应式的原理,并通过实例分析了observe与dep的源代码,其实对于props,provide,过程都是类似的,大家可以自行学习。接下来我们要对watcher进行分析。