本文是一篇vue.js源码学习笔记,适合对vue的数据响应实现有必定了解的同窗,文中有表述不许确的地方还望指出。那么开始了vue
提示:文中会看到( 知识点:xxx )这样的标记表示在源码的相应位置打上标记, 方便你们阅读git
/src/core/instance/state.jsgithub
// initState会在new Vue()时执行
export function initState (vm: Component) {
/*
other
*/
// 若是咱们定义了comouted属性则执行initComputed
if (opts.computed) initComputed(vm, opts.computed)
/*
other
*/
}
复制代码
在同一个文件中找到initComputed的定义缓存
function initComputed (vm, computed) {
// 往组件实例上添加一个_computedWatchers属性,保存全部的computed watcher
const watchers = vm._computedWatchers = Object.create(null)
// 对全部的computed属性遍历处理
for (const key in computed) {
// 将咱们定义的computed属性值用userDef保存
const userDef = computed[key]
// 咱们在定义computed时能够是一个函数,也能够是一个对象{get:function(){}, set:function(){}}
const getter = typeof userDef === 'function' ? userDef : userDef.get
// 数据响应过程当中的watcher(注意第二个参数是咱们刚才拿到的getter,记住了)
watchers[key] = new Watcher(
vm,
getter || noop, // 注意这里,注意这里,注意这里,(****知识点getter)
noop,
computedWatcherOptions
)
if (!(key in vm)) {
defineComputed(vm, key, userDef)
}
}
}
复制代码
接下来咱们仍是在这个文件中找到defineComputed的实现bash
export function defineComputed (target, key, userDef) {
/* other */
// 这里我对源码进行了简化
// sharedPropertyDefinition是一个全局对象
// 拿到一个get函数
sharedPropertyDefinition.get = createComputedGetter(key)
/* other */
// 这个函数的主要功能是computed属性的get进行了重写
Object.defineProperty(target, key, sharedPropertyDefinition)
}
复制代码
仍是继续看createComputedGetter很重要,很重要,很重要函数
function createComputedGetter (key) {
// 返回一个函数,也就是咱们在上一个函数中那个get函数
return function computedGetter () {
// 拿到咱们在initComputed函数中添加到vm上面的_computedWatchers
const watcher = this._computedWatchers && this._computedWatchers[key]
// 若是咱们有定义computed属性,watcher一定存在
if (watcher) {
// 注意,注意,注意,只要在模板中使用了这个computed属性,以后每次页面更新就是循环知识点1到知识点5这个过程
// 第二节主要就是在讲这一块,在理解下面的步骤时能够对照的看一下
if (watcher.dirty) { // ****标记:知识点1
watcher.evaluate() // ****标记:知识点2
}
if (Dep.target) { // ****标记:知识点3
watcher.depend() // ****标记:知识点4
}
return watcher.value // ****标记:知识点5
}
}
}
复制代码
仍是在这个文件中咱们能够找到这个对象oop
const computedWatcherOptions = { lazy: true }
复制代码
回到initComputed函数中new Watcher时学习
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions // 看这里,看这里,看这里(传入{lazy: true})
)
复制代码
Watcher的源码ui
constructor (vm, expOrFn, cb, options, isRenderWatcher) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
/* ... */
if (options) {
this.lazy = !!options.lazy // this.lazy = true
}
this.getter = expOrFn
this.dirty = this.lazy // 初始化this.dirty = true
/* ... */
// 注意了,注意了,注意了
// new时this.lazy为true因此this.value = 'undefined'
this.value = this.lazy ? undefined : this.get()
}
复制代码
主要流程在createComputedGetter函数里面this
new Watcher
时 this.dirty = this.lazy
因此知识点1:watcher.dirty = truewatcher.evaluate()
会将this.dirty = false
接着会执行Watcher的this.get()
最终其实就是执行知识点getter:this.getter()
Dep.target
永远为真对Watcher的实现这里就说这么多了,本文是创建在你们对Wathcer类有必定了解的基础上讲解的。若是你们有须要能够留言后期会给你们详细梳理下数据响应的实现过程,网上好像已经有不少相关的文章了
new Vue({
data(){
return {
dataA: 'a',
dataB: 'b'
}
},
template: '<div>{{computedA}}-{{dataB}}</div>',
computed: {
computedA() {
return 'computed ' + this.dataA
}
},
method: {
changeA(){
this.dataA = 'change dataA'
},
changeB(){
this.dataA = 'change dataB'
}
}
})
复制代码
看在createComputedGetter函数
{{computedA}}
执行computedA.get()
转到函数createComputedGetter中this.dirty = true
watcher.evaluate()
执行,将this.dirty = false
,watcher内部执行知识点getter:this.getter()
this.getter = computedA = function(){
return 'computed' + this.dataA // 看这里,看这里,看这里,知识点update
}
复制代码
获得了wacher.value 等于 computed a
watcher.depend()
从新收集依赖<div>computed a-b</div>
this.changA()
改变dataA,调用dataA中的dep.notify()
,会执行dataA的全部watcher对象wathcer.update()
,由于computed所属watcher的lazy永远为true,知识点1: this.dirty = true{{computedA}}
再次调用computedA.get()
,循环第1步经过第二节3段中computed的响应过程咱们知道,computedA会监听dataA的改变去改变知识点1: this.dirty = true才最终执行了知识点getter
假设:
this.changeB()
,改变了dataB值,wathcer.update()
,{{computedA}}
再次调用computedA.get()
转到函数createComputedGetter中。watcher.update()
知识点1:this.dirty = false,最终 并不会执行到知识点:getterreturn watcher.value
由于这篇主要是讲computed的知识,对于数据响应的知识没有详细说明,可能看起来有点懵逼,后面有须要会详细分析下vue数据响应的实现过程。