本系列为vue 2.5 的原理解析,分为几个方面,这是这系列的第二篇
- 数据的响应式原理
- Vue的计算属性和初次渲染原理
- Vue的异步更新机制
- Vue的nextTick的原理
- 手动实现响应式原理和虚拟dom,异步更新
1. computed的作用
1.1 computed的创建
vue源码中只有三个地方使用了new Watcher来创建watch,computed的使用就是一个地方
-
使用
computed: { reversedMessage: function () { return this.message.split('').reverse().join('') } } -
初始化computed
export function initState (vm: Component) { if (opts.computed) initComputed(vm, opts.computed) } -
利用watcher来创建computed (传递了{lazy: true})
for (const key in computed) { const userDef = computed[key]; const getter = typeof userDef === 'function' ? userDef : userDef.get vm._computedWatchers = watchers[key] = new Watcher( vm, getter || noop, noop, {lazy: true} ) // 如果计算属性不在vm实例中,定义 if (!(key in vm)) { defineComputed(vm, key, userDef) } } // 由于传递了lazy:true 所以不会触发watcher的get()方法去获取值,触发依赖收集 class Watcher { constructor() { this.dirty = this.lazy // for lazy watchers this.value = this.lazy ? undefined : this.get() } } -
defineComputed给vm实例挂载属性function defineComputed (target,key,userDef) { if (typeof userDef === 'function') { sharedPropertyDefinition.get = createComputedGetter(key) sharedPropertyDefinition.set = function() {} } } // 获取计算属性的值的时候,会触发对应的get,然后触发这个函数 function createComputedGetter (key) { return function computedGetter () { const watcher = this._computedWatchers && this._computedWatchers[key] if (watcher) { // 计算属性的时候,会为true直接获取到值 if (watcher.dirty) { watcher.evaluate() } if (Dep.target) { watcher.depend() } return watcher.value } } }
2. 初始化渲染
具体例子
<section class="todoapp">
<h1>{{c}}</h1>
</section>
new Vue({
el: '.todoapp',
data() {
return {
a: 1,
b: 2,
};
},
mounted() {
setTimeout(() => {
this.a = this.a + 1;
}, 2000);
},
computed: {
c() {
console.log('computed_C');
return this.a + this.b;
}
}
-
初始化的工作
Vue.prototype._init = function (options?: Object) { const vm: Component = this // a uid vm._uid = uid++ // 初始化parent, root children等数据 initLifecycle(vm) // 初始化父类的监听事件 initEvents(vm) // 定义vm.$createElement和vm._c initRender(vm) callHook(vm, 'beforeCreate') // inject的使用 initInjections(vm) // resolve injections before data/props // 数据的响应式原理 initState(vm) // provide的使用 initProvide(vm) // resolve provide after data/props callHook(vm, 'created') /* 挂载 */ if (vm.$options.el) { vm.$mount(vm.$options.el) } } -
初始化
computed- 包装computed的get函数,然后绑定在实例上
- 由于lazy懒求值,初始化的值为
undefined
-
执行挂载顺序
vm.$mount->Vue.prototype.$mount->mountComponentVue.prototype.$mount = function (el) { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el) } function mountComponent (vm, el) { vm.$el = el callHook(vm, 'beforeMount') let updateComponent = () => { vm._update(vm._render(), hydrating) } // 创建 渲染watcher vm._watcher = new Watcher(vm, updateComponent, noop) // 初次渲染的时候触发mounted钩子函数 if (vm.$vnode == null) { vm._isMounted = true callHook(vm, 'mounted') } return vm } -
在
mountComponent执行是进入Watcher,然后创建渲染watcher- 渲染watcher会触发虚拟dom生成(vm._render)和视图的更新(vm._update)
- 虚拟dom的渲染的时候,模板中值的获取会触发相应的get
class Watcher { constructor(vm,expOrFn,cb,options) { // getter函数式渲染视图和触发依赖收集 this.getter = function updateComponent { vm._update(vm._render(), hydrating) } this.cb = function() {} // 触发watcher的get函数,Dep 和 watch的相互收集 this.value = this.lazy ? undefined : this.get() } get() { // 是Dep.target = this -> 绑定渲染watcher pushTarget(this) let value const vm = this.vm try { // 触发虚拟dom的生成和新旧dom的对比,然后渲染到视图 value = this.getter.call(vm, vm) } catch (e) { if (this.user) { handleError(e, vm, `getter for watcher "${this.expression}"`) } else { throw e } } finally { if (this.deep) { traverse(value) } // Dep.target = 上一个watcher popTarget() this.cleanupDeps() } return value } } -
在
vm._render()中会触发依赖收集- 如果存在计算属性并在模板中使用的话,此时会触发computed在初始化的时候,绑定的
computedGetter - 然后进入计算属性的
watcher去获取到对应的值
function computedGetter () { const watcher = this._computedWatchers && this._computedWatchers[key] if (watcher) { // 计算属性的时候,会为true直接获取到值 if (watcher.dirty) { watcher.evaluate() } if (Dep.target) { watcher.depend() } return watcher.value } } // computedGetter 返回了数字3给渲染使用 // Watcher 部分 // 获取值,然后给computedGetter evaluate () { this.value = this.get() this.dirty = false } get () { // 把Dep.target 设置为计算属性的watcher pushTarget(this) let value const vm = this.vm /** 会触发计算属性定义时候的函数,然后触发a,b的依赖收集 function () { console.log('computed_C'); return this.a + this.b; } **/ value = this.getter.call(vm, vm) // 把Dep.target重新复制为渲染watcher popTarget() return value } - 如果存在计算属性并在模板中使用的话,此时会触发computed在初始化的时候,绑定的
-
执行完渲染后,返回到渲染watcher的函数执行尾部
-
此时页面已经渲染,做一些清除工作
finally { if (this.deep) { traverse(value) } // Dep.target = 上一个watcher popTarget() this.cleanupDeps() }
-
-
渲染watcher执行完毕后,返回
mountComponent- 触发mounted钩子函数
- 返回实例本身
function mountComponent (vm, el) { vm.$el = el callHook(vm, 'beforeMount') let updateComponent = () => { vm._update(vm._render(), hydrating) } // 创建 渲染watcher 执行完毕 vm._watcher = new Watcher(vm, updateComponent, noop) // 初次渲染的时候触发mounted钩子函数 if (vm.$vnode == null) { vm._isMounted = true callHook(vm, 'mounted') } return vm }