前言:此文章主要是分析Vue的响应式原理,从new Vue开始,看源码究竟发生了什么,其中会忽略掉大量边界处理以及“不重要”的代码,最后再验证一下,建议可以自己clone源码,然后跟着文中的步骤跟着看,文中对代码不会有太多讲解,源码本身的命名结构都是很清晰的(在去除边界处理代码之后),主要是记录过程,帮助以后自己回忆,如果你是对响应式原理完全不懂的,建议可以先看看Introduction - Advanced Components | Vue Mastery或者Vue 3 Reactivity - Vue 3 Reactivity | Vue Mastery
看源码!
- src/core/index.js中声明了Vue函数
function Vue(options) { this._init(options); }
// 设置Vue.prototype._init
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
- 接着来到src/core/instance/init.js,在initMixin中我们可以找到Vue中_init函数的声明,它主要是初始化了Vue组件的声明周期、事件、Render,然后触发了beforeCreate的钩子,再接着初始化Injections、Provide,再触发了created的钩子,最后挂载组件
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function(options?: Object) {
const vm: Component = this;
// ...Normalizing options ...
vm._self = vm;
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm, 'beforeCreate');
initInjections(vm);
initState(vm);
initProvide(vm);
callHook(vm, 'created');
// ...
vm.$mount(vm,$options.el);
}
}
- 这其中我们需要关注的就是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);
}
}
可以看到,在initState中主要是初始化Props、Methods、Data、Computed(计算属性)和Watch(侦听器)
- 我们要关注的是initData,initData还是在state.js中
function initData (vm: Component) {
let data = vm.$options.data;
// ...
observe(data, true /* asRootData */);
}
- 紧接着是observe(src/core/observer/index.js)
/**
* 尝试为value创建一个observer实例,并返回这个observer
*/
export function observe(value: any, asRootData: ?boolean): Observer | void {
// ...
ob = new Observer(value);
return ob;
}
- 继续在src/core/observer/index.js找到Observer的Class声明
export class Observer {
value: any;
dep: Dep;
constructor (value: any) {
this.value = value;
// ...
if (Array.isArray(value)) {
// ...
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], obj[keys[i]]);
}
}
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[I]);
}
}
}
export function defineReactive (...) {
const dep = new Dep();
// ...
let childOb = !shallow && observe(val);
Object.defineProperty(obj, key, {
get: function reactiveGetter () { dep.depend(); },
set: function reactiveSetter (newVal) { dep.notify(); }
});
// ...
}
- 下面要看看Dep是如何定义的了,来到src/core/observer/dep.js
export default class Dep {
static target: ?Watcher;
subs: Array<Watcher>;
constructor () {
this.subs = [];
}
addSub (sub: Watcher) {
this.subs.push(sub);
}
depend () {
if (Dep.tartget) {
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 = 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()
}
- 最后就是Watcher,让我们把目光转移到src/core/observer/watcher.js
import Dep, { pushTarget, popTarget } from './dep'
export deafult class Watcher {
vm: Component;
newDepIds: ISet;
getter: Function;
value: any;
constructor (vm: Component, expOrFn: string | Function ...) {
// ...
this.vm = vm;
this.getter = expOrFn;
// ...
this.value = this.get();
}
get () {
pushTarget(this);
const vm = this.vm;
let value = this.getter.call(vm, vm);
// ...
popTarget();
return value;
}
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
}
梳理一下
总的来理一遍:
首先,new Vue会调用initState,为data创建一个Observer类的实例,同时递归为data中的Object类型的property创建一个Observer类的实例(如果这些property中还有Object类型的属性,则继续递归创建Observer),并为每一个Observer都创建了一个Dep类的实例;
然后使用了Object.defineProperty来拦截并重写property的get和set,在defineProperty中为每个property都创建了一个Dep类的实例,再在get和set中分别调用了dep.depend()和dep.notify()用于将当前的Watcher添加到dep的订阅列表中和触发订阅列表中Wacther的updat方法;而Watcher才是真正记录了使用响应式数据的函数的类
验证一下
来看一个简单的例子:
<template>
<div>
<h1>{{ obj.name }}</h1>
</div>
</template>
<script>
export default {
name: "HelloWorld",
data() {
return {
obj1: {
name: "test",
obj2: {
test: 'test'
},
},
};
},
mounted() {
console.log(this.obj);
},
};
</script>
来看看控制台的打印:
首先,data中的obj是有一个ob指向它的Observer的(data也有,但并没有直接有一个ob属性指向它的Observer)
-
然后我们也能看到obj中的obj也是有一个ob属性的,同样指向它的Observer,同时也能看到每一个Observer都对应的有一个Dep
-
接着就是确认get和set中的dep,下面是get name和set name中的dep,可以通过id确认这两个dep是同一个dep,均为defineProperty时闭包中的dep
至此响应式原理分析和验证告一段落,最后放一张图帮助理解