Vue2.x的运行原理可以大致分为以下几步;
- 创建Vue实例,进行数据劫持以及依赖收集
- 将模板编译为render函数
- 运行render函数,生成VNode节点
- 将VNode节点渲染成实际DOM节点
- 数据变化后,比对新的VNode和旧的VNode,只更新变化的部分
setter -> Dep -> Watcher -> patch -> 视图
数据劫持
Vue2.x使用 Object.defineProperty,将初始化传入的data对象中属性全部遍历,对每个属性设置 setter 和 getter 。
// 设置getter、setterconst dep = new Dep()function defineReactive(obj,key,val) {Object.defineProperty(obj,key,{enumerable:true,configurable:true,get() {dep.addSub(Dep.target)return val},set(newVal) {if(newVal===val) return;val = newVal;// 属性发生变化dep.notify()}})}
// 监听data对象属性变化function observer(data) {if(!value || typeof value!=='object') return;Object.keys(data).forEach(key=>{if(typeof data[key]==='object') {observer(data[key])// 多层对象采用递归遍历} else {defineReactive(data,key,data[key]])}})}
依赖收集
Dep负责收集依赖,Watcher和Vue对象一对一的关系,表示该实例的对象属性变化
Dep.target = nullclass Watcher() {constructor() {Dep.target = this}update() {// 更新DOM节点}}class Vue() {constructor(data) {new Watcher()observer(data)}}class Dep() {constructor() {this.subs = []}addSub(sub) {this.subs.push(sub)}notify() {this.subs.forEach(sub=>{sub.update()})}}
在属性变化后先调用dep的notify方法,notify方法中将收集来的依赖对象遍历并调用每个依赖对象中的update方法。
模板编译
使用vue-template-compiler对SFC中的模板进行编译,生成render函数
const compiler = require('vue-template-compiler')const code = compiler.compile('<div>123</div>')console.log(code.render)// ƒ anonymous(// ) {// with(this){return _c('div',[_v("123")])}/ }
VNode对比
Vue2.x中diff算法对同层树节点进行比较,是一种比较高效的算法,时间复杂度为O(n)

批量异步更新
Vue.js在默认情况下,每次触发某个数据的 setter 方法后,对应的 Watcher 对象其实会被 push 进一个队列 queue 中,在下一个 tick 的时候将这个队列 queue 全部拿出来 run( Watcher 对象的一个方法,用来触发 patch 操作) 一遍。

Vue.js 实现了一个 nextTick 函数,传入一个 cb ,这个 cb 会被存储到一个队列中,在下一个 tick 时触发队列中的所有 cb 事件。
因为目前浏览器平台并没有实现 nextTick 方法,所以 Vue.js 源码中分别用 Promise、setTimeout、setImmediate 等方式在 microtask(或是task)中创建一个事件,目的是在当前调用栈执行完毕以后(不一定立即)才会去执行这个事件。
遇到同一个属性多次变化的情况,只需要改变一次,所以需要Watcher做去重,在每个Watcher中添加uuid,如果遇到uuid相同的就不去多次触发。
