简单学习下Vue从初始化到实现数据响应式的实现过程,这是vue的响应式核心所在,过程中会附上核心源码。
1.new Vue()初始化
src/core/instance/index.js
function Vue (options) {
...
this._init(options)
}
initMixin(Vue)
...
new Vue()初始化是通过构造函数function Vue进行的- 执行构造函数的过程中调用
_init()方法进行初始化 _init方法是在initMixin方法中定义的
2. 初始化 data
2.1 initMixin
src/core/instance/init.js
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
...
initState(vm)
...
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
- 在
Vue的原型对象上定义了一个_init方法 - 执行初始化
data函数initState
2.2 initState
src/core/instance/state.js
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)
}
}
- 初始化
props methods data computed watch - 初始化
props时,会将props中的属性挂在到vm上,这就是为什么this.propsKey可以访问到props上的值 - 初始化
methods上的方法,最终得到vm[methodsKey] = methods[key] - 初始化
data时,如果存在data属性则对其执行initData进行初始化,如果不存在data属性,则直接把其作为一个{}进行响应式处理 - 初始化
computed,这里是处理初始化组件配置中的computed属性 - 初始化
watch,这里是处理初始化组件配置中的watch属性 - 在初始化时会对
props methods data computed watch进行判断重复的处理,判重的优先级为props > methods > data > computed
2.3 initData
src/core/instance/state.js
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
...
// proxy data on instance
const keys = Object.keys(data)
let i = keys.length
while (i--) {
...
proxy(vm, `_data`, key)
...
}
// observe data
observe(data, true /* asRootData */)
}
- 这里会对
data的不同类型进行处理,类型为函数时,需要执函数getData获取其返回值对象;类型为对象时,就直接赋值即可;处理完成之后挂载在vm._data中 - 通过
proxy函数进行代理,使得可以使用this.[dataKey]的形式访问到data属性 - 最后是通过
observe函数,对数据进行响应式处理
3.数据响应式处理
3.1 observe
src/core/observer/index.js
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)
}
...
return ob
}
observe会先判断该值是否为基本数据类型或者虚拟DOM对象,如果是则说明该值不需要进行处理,直接返回即可- 判断是否已经进行过响应式处理,如果已经进行响应式处理则返回
value.__ob__ - 这里的
__ob__属性是在new Observer的时候添加上去的 - 以上都不满足,则直接调用
new Observer创建响应式对象
3.2 Observer 类
src/core/observer/index.js
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 (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
...
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
...
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
return value
},
set: function reactiveSetter (newVal) {
...
dep.notify()
}
})
}
Observer函数初始化时,并且给属性加上__ob__属性,同时也用来判断是否已经进行过响应式处理- 判断该数据是否为数组,数组和对象的处理思路不同
value为数组时,由于数据是挂载在data下面的,而data属性是一个对象,所以依赖的收集依旧在set中进行- 由于
Object.defineProperty无法捕获到数组中值的变化,所以数组的依赖更新需要单独处理,详见3.3 数组响应式处理 value为对象时,执行walk方法,循环对象获取每一个值,对其执行defineReactive方法,此方法中中使用Object.defineProperty对数据进行拦截,并且在set中收集依赖,在get中通知依赖更新defineReactive中则是Object.defineProperty对数据的具体拦截,并且使用了de.depend()进行依赖收集,使用dep.notify()进行依赖更新。
3.3 数组响应式处理
Observer 类中
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
// 将方法添加到 __proto__ 上
function protoAugment (target, src: Object, keys: any) {
target.__proto__ = src
}
// 将数组方法添加到 value 上
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])
}
}
// src/core/observer/array.js
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args)
const ob = this.__ob__
...
ob.dep.notify()
return result
})
})
methodsToPatch中定义了需要拦截的方法- 先进行判断是否支持
__proto__,如果支持,函数protoAugment会将拦截器函数赋值在value的__proto__上,当对数组的操作时,拦截器会对这些方法进行拦截从而进行依赖更新 - 如果不支持,则循环这几个方法,函数
copyAugment会将其通过Object.defineProperty加入到value上,这样当数组通过这些方法进行操作时,就能通过这些方法进行依赖更新了
4. 依赖的收集与更新
4.1 依赖收集 Dep 类
src/core/observer/dep.js
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 () {
...
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
- 依赖的收集与更新其实是通过
Dep类实现的 - 根据定义可以看出,收集的依赖其实就是一个
Watcher实例 - 初始化
Dep,并且在原型上定义相关的方法,subs用来存放收集的依赖数组;depend收集依赖;notify负责依赖更新 notify执行更新时,相当于把存放的依赖数组subs循环,然后调用Watcher的update方法
4.2 监听器 Watcher 更新
src/core/observer/watcher.js
export default class Watcher {
...
update () {
...
queueWatcher(this)
}
}
update方法中会执行queueWatch函数,此函数会将需要更新的watcher放入到watcher队列中- 执行队列会按照浏览器事件循环的机制进行执行,完成更新操作