vue2系列的文章分为reactivity,compile,runtime等不同模块,从源码入手,省略掉部分代码,只给出核心逻辑,旨在阐明vue的工作原理。
一、源码阅读
vue作为一套用于构建用户界面的渐进式框架,数据的变化就能够驱动相应的视图更新,将我们从繁琐的DOM操作中解放出来。所以,理解vue的响应式原理能够使我们在开发工作中更加得心应手。
我们从vue的实例化过程new Vue ( )说起:
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'
function Vue (options) {
//...
this._init(options)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
Vue函数在src\core\instance\index.js中被定义, initMixin(Vue)在Vue的原型链上添加_init方法。
Vue实例化过程中会调用 _init 方法,下面通过 _init的调用链说明 vue2 的响应式原理。
_init(options) --> initState(vm) --> initData(vm) --> observe(data,true)
其中data是options中的data函数返回的对象。如果data不是object类型,直接返回。如果data包含 __ob__属性,说明已经存在观察者,直接将其返回。否则,返回新的观察者。
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 {
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
用传入的 data 实例化 Observer 对象,是响应式的重点逻辑( 敲黑板!!!),在src\core\observer\index.js中被定义,构造函数如下:
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
// 使用Object.defineProperty定义value __ob__属性的值,writable,configurable为true,enumerable为false
def(value, '__ob__', this)
// 如果data是数组,根据不同的浏览器环境(IE浏览器这种不存在__proto__),将push、pop、unshift、shift、splice、sort、reverse七个方法在数组原型方法的基础添加响应式代理
// 然后递归遍历数组的每一项,生成它们的观察者对象
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
}
}
如果是普通对象类型,调用walk方法深度优先遍历,对数据进行拦截。
function walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
defineReactive中通过object.defineProperty拦截数据的get和set:
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] // getter不存在,且未传参value时,定义val的值
}
let childOb = observe(val) // 深度优先递归遍历
Object.defineProperty(obj, key, { // 拦截getter和 setter属性
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
// 属性嵌套的其他属性,都做依赖收集
// 属性订阅器dep的subs数组添加Dep.target,即watcher,这样,属性变化可以通知所有订阅的watcher更新
// 同时watcher的newDeps数组也添加dep,这样,watcher销毁时,可以通过在dep的subs数组中移除,取消所有的订阅
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
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) // 拦截newVal的变化
dep.notify() // 通知所有的订阅者watcher更新
}
})
}
二、 实现一个简版的vue
Vue主要通过以下四个步骤实现数据的双向绑定:
-
实现一个解析器Compile:
解析Vue模板指令,将模板中的变量都替换成数据,然后初始化渲染视图。并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者。当数据变化时,订阅者收到通知,调用更新函数。
-
实现一个监听器Observe:
递归遍历数据对象,利用Object.defineProperty给属性添加getter和setter。数据变化时,触发setter,也就是监听到了数据变化。
-
实现一个订阅者Watcher:
Watcher是Observer和Compile之间通信的桥梁,主要的任务是订阅Observer中数据变化的消息,触发Compile中对应的更新函数。
-
实现一个订阅器Dep:
利用发布-订阅模式,收集订阅者Watcher,对监听器Observer和Watcher统一管理。
简版的vue也通过实现以上四步来实现,详细内容请参考mini-vue2。