从 data 函数中获取返回值作为 data, 这就是为什么在 Vue 中 data 应当是一个返回对象的函数
检查 data 中的属性有没有和 props 重名的
将 data 中的属性全部代理到 Vue 实例上以进行访问
观察 data 对象
functioninitData(vm:Component) {
let data =vm.$options.data
data =vm._data=typeof data ==='function'?getData(data, vm)
: data || {}
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV!=='production'&&warn(
'data functions should return an object:\n'+'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
)
}
// proxy data on instance// 遍历 data 中所有的属性constkeys=Object.keys(data)
constprops=vm.$options.propsconstmethods=vm.$options.methodslet i =keys.lengthwhile (i--) {
constkey= keys[i]
if (process.env.NODE_ENV!=='production') {
if (methods &&hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
if (props &&hasOwn(props, key)) {
process.env.NODE_ENV!=='production'&&warn(
`The data property "${key}" is already declared as a prop. `+`Use prop default value instead.`,
vm
)
} elseif (!isReserved(key)) {
// 将 data 中的属性全部代理到 Vue 实例上以进行访问proxy(vm, `_data`, key)
}
}
// observe data// 使得 data 变为响应式的, 由于 asRootData 为 true, 可以想象有个 Observer 的 vmCount 会 + 1observe(data, true/* asRootData */)
}
observe Observer defineReactive Dep
observe 尝试为一个对象创建 Observer, 或者返回已有的 Observer.
exportfunctionobserve(value:any, asRootData:?boolean):Observer|void {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer |voidif (hasOwn(value, '__ob__') &&value.__ob__instanceof Observer) {
ob =value.__ob__
} elseif (
shouldObserve &&!isServerRendering() &&
(Array.isArray(value) ||isPlainObject(value)) &&Object.isExtensible(value) &&!value._isVue
) {
ob =newObserver(value)
}
// 如果作为根数据要加上 vmCountif (asRootData && ob) {
ob.vmCount++
}
return ob
}
exportclassObserver {
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $dataconstructor(value:any) {
this.value= value
this.dep=newDep() // 创建 Dep, 这个 Dep 是对象自己而非它的属性的 Depthis.vmCount=0def(value, '__ob__', this)
// 如果对象是一个数列, 用 Vue 更新后的数组方法实现响应式, 这就是为什么在 Vue 中用数组下标访问无法实现响应式的效果if (Array.isArray(value)) {
constaugment= hasProto
? protoAugment
: copyAugment
augment(value, arrayMethods, arrayKeys)
this.observeArray(value)
} else {
// 如果是一个对象, 就转化 get/setthis.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) {
constkeys=Object.keys(obj)
for (let i =0; i <keys.length; i++) {
defineReactive(obj, keys[i])
}
}
/** * Observe a list of Array items. * * 如果对象是一个数组, 就 observe 数组中的每一个元素*/observeArray(items:Array<any>) {
for (let i =0, l =items.length; i < l; i++) {
observe(items[i])
}
}
}
Dep 对象是什么? 它是一个用来记录A 对 B 的变化感兴趣数据结构, 其中 B 是某个对象或对象的某个属性, 而 A 是一个 Watcher. 当 A 需要在 B 的数据的变化时收到通知, 就会在 B 的 Dep 中注册自己, 当 B 发现数据更新的时候, 就会通知所有感兴趣的 A. 这就是观察者模式.
exportdefaultclassDep {
static target:?Watcher;
id: number;
subs:Array<Watcher>;
constructor() {
this.id= uid++this.subs= []
}
// 将会被某个 Watcher 调用, 修改自己的订阅者数组addSub(sub:Watcher) {
this.subs.push(sub)
}
removeSub(sub:Watcher) {
remove(this.subs, sub)
}
// 将会被某个 getter 调用, 收集 Dep.target 指向的 Watcherdepend() {
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify() {
// stabilize the subscriber list firstconstsubs=this.subs.slice()
for (let i =0, l =subs.length; i < l; i++) {
subs[i].update()
}
}
}
defineReactive 通过 Object.defineProperty 方法设置了某个属性的 get/set, 并在自己的作用域中创建了一个 Dep 对象. 它可以把某个属性变为响应式的, 原理就是 Object.definePropery 提供的 get 和 set. 当 Watcher 访问这个属性的时候, 首先会把自己标记为依赖收集的目标, 然后触发 get, get 会让自己闭包内保存的 Dep 进行依赖收集. 当这个属性被修改的时候, 会触发 set, set 会通知 Dep 让它去更新所有对它感兴趣的 Watcher.
// 这个方法用来对 computed 实际求值get() {
pushTarget(this) // 先将自己设置为依赖搜集的对象let value
constvm=this.vmtry {
// 这里调用了 getter 实现了依赖收集! 因为 getter 里面必然访问了某个对象的属性, 看 defineReactive
value =this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as// dependencies for deep watchingif (this.deep) {
traverse(value)
}
popTarget() // 自己依赖搜集完毕, 让出位置this.cleanupDeps()
}
return value
}
// 这个方法和 Dep 中的方法协作, 将会被一个 Dep 调用, Dep 会把自己传过来// 更新 Dep 的过程, 是记录这一次更新过程中自己需要的依赖, 与上一次更新的依赖作比较// 订阅新的依赖, 将不再需要的依赖剔除掉 (通过 cleanupDeps 方法)addDep(dep:Dep) {
constid=dep.id// 记录新的依赖if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
// 如果自己没有订阅过这个 Dep, 就订阅if (!this.depIds.has(id)) {
dep.addSub(this)
}
}
// 当 setter 被触发的时候, 就会调用 Dep 的 update, Dep 再来调用 update 方法update() {
if (this.computed) {
// A computed property watcher has two modes: lazy and activated.// It initializes as lazy by default, and only becomes activated when// it is depended on by at least one subscriber, which is typically// another computed property or a component's render function.// 如果 Watcher 作为计算属性的 Watcher, 那么它会有两种模式, 当它没有订阅者的时候就是 lazy// 模式, 仅仅将 Watcher 设置为 dirty, 然后当计算属性被访问的时候, 才会重新计算// 如果有订阅者的时候, 就是 activated 模式, 立即计算新值, 但只有在值真的发生变化的时候// 才去通知自己的订阅者if (this.dep.subs.length===0) {
// In lazy mode, we don't want to perform computations until necessary,// so we simply mark the watcher as dirty. The actual computation is// performed just-in-time in this.evaluate() when the computed property// is accessed.this.dirty=true
} else {
// In activated mode, we want to proactively perform the computation// but only notify our subscribers when the value has indeed changed.this.getAndInvoke(() => {
this.dep.notify()
})
}
} elseif (this.sync) {
// 如果是渲染函数指令中的 Watcher 且有 .sync 修饰符, 就立即更新, 以后再讲this.run()
} else {
// 否则进入更新队列, 之后在讲 Vue 的异步更新策略的时候会讲queueWatcher(this)
}
}
Vue 实现响应式的机制简单来说就是
Object.defineProperty
实现的访问拦截和观察者模式. 其他关键词包括:Observer Dep Watcher
和依赖收集. 这篇文章将会分析 Vue.js 的源码以解释这些概念, 讲解响应式原理, 还会给出一个简单的例子以在 Chrome 开发工具中验证这篇文章的内容.你可以在 lets-read-vue 中找到注释后的源码以及文末例子的源码.
Vue.js 项目的结构如下:
这篇文章相关的代码都在
src/core
底下.响应式模型
先给出一个 Vue.js 的响应式原理抽象成的模型.
接下来我们深入代码来讲解这个模型.
响应式初始化
当我们通过
new Vue({})
创建 Vue 实例时, 构造函数会调用Vue._init
方法, 其中会调用initState
, 而在这个方法会按序初始化props methods data computed watch
, 响应式初始化就发生在这里. 我们会着重讲解data
和computed
的初始化过程.computed
依赖props
或者data
, 所以是订阅者, 想要知道某个被订阅者的变化, 正好构成一个响应式关系!initData
该方法将
data
变为响应式的, 它做了以下这些事情:data
函数中获取返回值作为data
, 这就是为什么在 Vue 中data
应当是一个返回对象的函数data
中的属性有没有和props
重名的data
中的属性全部代理到 Vue 实例上以进行访问data
对象observe Observer defineReactive Dep
observe
尝试为一个对象创建 Observer, 或者返回已有的 Observer.Observer
被附加到被观察的对象上, 一旦添加, 就会尝试将该对象的属性全部转化为 get/set 以实现依赖收集和触发更新.Dep
对象是什么? 它是一个用来记录A 对 B 的变化感兴趣数据结构, 其中 B 是某个对象或对象的某个属性, 而 A 是一个 Watcher. 当 A 需要在 B 的数据的变化时收到通知, 就会在 B 的 Dep 中注册自己, 当 B 发现数据更新的时候, 就会通知所有感兴趣的 A. 这就是观察者模式.defineReactive
通过Object.defineProperty
方法设置了某个属性的 get/set, 并在自己的作用域中创建了一个Dep
对象. 它可以把某个属性变为响应式的, 原理就是Object.definePropery
提供的 get 和 set. 当 Watcher 访问这个属性的时候, 首先会把自己标记为依赖收集的目标, 然后触发 get, get 会让自己闭包内保存的 Dep 进行依赖收集. 当这个属性被修改的时候, 会触发 set, set 会通知 Dep 让它去更新所有对它感兴趣的 Watcher.到这里, 被观察的一侧 (Dep) 需要做的工作就做好了.
initComputed
这个函数主要做了如下事情:
_watchers
数组中相比于被观察者 Dep, 观察者 Watcher 要复杂得多! 所以我们就不把代码贴在这里了, 请去 lets-read-vue 中查看. 我们这里就讲对响应式来说很重要的几个方法.
例子
接下来我们根据一个非常简单的例子来串讲我们之前覆盖的内容, 代码可以在 lets-read-vue 的
playground/responsive-demo.html
中找到.在 Vue 实例初始化的时候, 先处理
data
.initData
调用observe
方法为data
对象创建Observer
, 然后Observer
调用自己的walk
方法,walk
对每一个属性调用defineReactive
把message
变成响应式的. 现在$data
和message
都有自己的一个Dep
. 然后处理computed
, 为 computed 创建了一个Watcher
并添加到_watchers
数组中.当我们在 console 访问
vm.$data.helloMessage
的时候,Watcher
的get
将会被调用, 这时候就通过触发message
的get
实现依赖收集,message
的Dep
的subs
就有了helloMessage
的Watcher
, 与之对应helloMessage
的Watcher
也会记录message
的Dep
.再次放上模型以供你温习.
当我们修改
message
的时候, 就会触发message
的set
, 此时message
的Dep
就会去更新依赖, 调用Watcher
的update
方法. 而Watcher
如果属于某个计算属性, 仅仅会把自己设置为脏值, 仅有计算属性重新被访问的时候才会去实际求值 (这一点之前没有讲).其他
当然了, 实现响应式的方式并不只有
data
到computed
, 还有模板中的表达式,computed
相互的依赖和watch
等等, 但原理都是如此, 就不再赘述了, 请自己阅读源码吧.