源码简单分析
Vue 源码分析
基本实现
核心功能:响应式的数据绑定、虚拟 DOM、diff 算法、patch 方法(用于更新真实 DOM)
当 new Vue() 的时候发生了什么?
我们的实现会参考源码的套路,但会大量的简化其中的细节。为了理解源码的结构,最好的突破口就是了解程序的起点 new Vue() 的背后究竟发生了什么。
简单梳理下源码的执行流:
=> 初始化生命周期
=> 初始化事件系统
=> 初始化 state,依次处理 props、data、computed …
=> 开始渲染 _mount() => _render() 返回 vdom=> _update() => patch() 更新真实 DOM
更详细的说明可以参考这篇文章,我们只会实现其中最核心的部分
第一步:将虚拟 DOM 树渲染到真实的 DOM
每一个 DOM 节点都是一个 node 对象,这个对象含有大量的属性与方法,虚拟 DOM 其实就是超轻量版的 node 对象。
我们要生成的 DOM 树看上去是这样的:
关于 data 参数的属性,请参考官方文档
随后我们会通过 createElm 方法和 createChildren 方法的相互调用,遍历整棵虚拟节点树,生成真实的 DOM 节点树,最后替换到挂载点。
第二步:修改数据,执行 diff 算法,并将变化的部分 patch 到真实 DOM
diff 算法的逻辑比较复杂,可以单独摘出来研究,由于我们的目的是理解框架的核心逻辑,因此代码实现里只考虑了最简单的情形。
第三步:对数据做响应式处理,当数据变化时,自动执行更新方法
data 中的每一个属性都会被处理为存取器属性,同时每一个属性都会在闭包中维护一个属于自己的 dep 对象,用于存放该属性的依赖项。当属性被赋予新的值时,就会触发 set 方法,并通知所有依赖项进行更新。
初始化、更新流程分析
<div id="demo">
<child :list="list"></child>
<button @click="handleAdd">add</button>
</div>
<script>
Vue.component('child', {
props: {
list: {
type: Array,
default: () => []
}
},
template: '<p>{{ list }}</p>'
})
new Vue({
el: "#demo",
data() {
return {
list: [1,2]
}
},
methods: {
handleAdd() {
this.list.push(Math.random())
}
}
})
</script>
很简单的例子,一个父组件一个子组件,子组件接受一个 list,父组件有个按钮,可以往 list 里 push 数据改变 list
初始化流程:
首先从
new Vue({el: "#app"})
开始,会执行_init
方法。function Vue(options) {
// 省略...
this._init(options);
}_init
方法的最后执行了vm.$mount
挂载实例。Vue.prototype._init = function (options) {
var vm = this;
// 省略...
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
};如果此时运行的版本是
runtime with compiler
版本,这个版本的$mount
会被进行重写,增加了把 template 模板转成 render 渲染函数的操作,但最终都会走到mountComponent
方法。Vue.prototype.$mount = function (el, hydrating) {
el = el && inBrowser ? query(el) : undefined;
return mountComponent(this, el, hydrating);
};
var mount = Vue.prototype.$mount; //缓存上一次的Vue.prototype.$mount
Vue.prototype.$mount = function (el, hydrating) {
//重写Vue.prototype.$mount
// 省略,将template转化为render渲染函数
return mount.call(this, el, hydrating);
};mountComponent
里触发了beforeMount
和mounted
生命周期,更重要的是创建了Watcher
,传入的updateComponent
就是 Watcher 的getter
。function mountComponent(vm, el, hydrating) {
// 执行生命周期函数 beforeMount
callHook(vm, "beforeMount");
var updateComponent;
//如果开发环境
if ("development" !== "production" && config.performance && mark) {
// 省略...
} else {
updateComponent = function () {
vm._update(
vm._render(), // 先执行_render,返回vnode
hydrating
);
};
}
new Watcher(
vm,
updateComponent,
noop,
null,
true // 是否渲染过得观察者
);
if (vm.$vnode == null) {
vm._isMounted = true;
// 执行生命周期函数mounted
callHook(vm, "mounted");
}
return vm;
}在创建
Watcher
时会触发get()
方法,pushTarget(this)
将Dep.target
设置为当前 Watcher 实例。function Watcher(vm, expOrFn, cb, options, isRenderWatcher) {
if (typeof expOrFn === "function") {
this.getter = expOrFn;
}
this.value = this.lazy // 这个有是组件才为真
? undefined
: this.get(); //计算getter,并重新收集依赖项。 获取值
}
Watcher.prototype.get = function get() {
pushTarget(this);
var value;
var vm = this.vm;
try {
value = this.getter.call(vm, vm);
} catch (e) {
} finally {
popTarget();
}
return value;
};Watcher
的get()
里会去读取数据,触发initData
时使用Object.defineProperty
为数据设置的get
,在这里进行依赖收集。我们知道 Vue 中每个响应式属性都有一个__ob__
属性,存放的是一个 Observe 实例,这里的childOb
就是这个__ob__
,通过childOb.dep.depend()
往这个属性的__ob__
中的 dep 里收集依赖,如下图。export function defineReactive(
obj: Object,
key: string,
val: any,
customSetter?: Function
) {
/*在闭包中定义一个dep对象*/
const dep = new Dep();
let childOb = observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
/*如果原本对象拥有getter方法则执行*/
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) {},
});
}在我们的例子中,这个 list 会收集两次依赖,所以它
__ob__
的 subs 里会有两个Watcher
,第一次是在父组件data
中的 list,第二次是在创建组件时调用createComponent
,然后又会走到_init
=>initState
=>initProps
,在initProps
内对props
传入的属性进行依赖收集。有两个 Watcher 就说明 list 改变时要通知两个地方,这很好理解。 .最后,触发
getter
,上面说过getter
就是updateComponent
,里面执行_update
更新视图。
下面来说说更新的流程:
点击按钮往数组中添加一个数字,在 Vue 中,为了监听数组变化,对数组的常用方法做了重写,所以先会走到
ob.dep.notify()
这里,ob
就是 list 的__ob__
属性,上面保存着 Observe 实例,里面的 dep 中有两个Watcher
,调用notify
去通知所有 Watcher 对象更新视图。["push", "pop", "shift", "unshift", "splice", "sort", "reverse"].forEach(
function (method) {
const original = arrayProto[method];
def(arrayMethods, method, function mutator() {
let i = arguments.length;
const args = new Array(i);
while (i--) {
args[i] = arguments[i];
}
/*调用原生的数组方法*/
const result = original.apply(this, args);
const ob = this.__ob__;
let inserted;
switch (method) {
case "push":
inserted = args;
break;
case "unshift":
inserted = args;
break;
case "splice":
inserted = args.slice(2);
break;
}
if (inserted) ob.observeArray(inserted);
/*dep通知所有注册的观察者进行响应式处理*/
ob.dep.notify();
return result;
});
}
);notify
方法里去通知所有Watcher
更新,执行Watcher
的update
方法,update
里的queueWatcher
过滤了一些重复的Watcher
, 但最终会走到Watcher
的run()
方法。Dep.prototype.notify = function notify() {
var subs = this.subs.slice();
for (var i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
};
Watcher.prototype.update = function update() {
if (this.lazy) {
this.dirty = true;
} else if (this.sync) {
this.run();
} else {
queueWatcher(this);
}
};run
方法里会调用get()
,get
方法里回去触发 Watcher 的getter
,上面说过,getter
就是updateComponent
。Watcher.prototype.run = function run() {
if (this.active) {
/* get操作在获取value本身也会执行getter从而调用update更新视图 */
const value = this.get();
}
};
updateComponent = function () {
vm._update(vm._render(), hydrating);
};最后在
_update
方法中,进行patch
操作