/*初始化props、methods、data、computed与watch*/
export function initState(vm: Component) {
vm._watchers = []
const opts = vm.$options
/*初始化props*/
if (opts.props) initProps(vm, opts.props)
/*初始化方法*/
if (opts.methods) initMethods(vm, opts.methods)
/*初始化data*/
if (opts.data) {
initData(vm)
} else {
/*该组件没有data的时候绑定一个空对象*/
observe(vm._data = {}, true /* asRootData */ )
}
/*初始化computed*/
if (opts.computed) initComputed(vm, opts.computed)
/*初始化watchers*/
if (opts.watch) initWatch(vm, opts.watch)
}
现在我们就来看看 initComputed方法
const computedWatcherOptions = {
lazy: true
}
function initComputed(vm: Component, computed: Object) {
const watchers = vm._computedWatchers = Object.create(null)
for (const key in computed) { // 遍历computed 对象里的元素
const userDef = computed[key]
/*
计算属性可以是 `function`,也可以是有 `get/set` 属性的对象。
*/
let getter = typeof userDef === 'function' ? userDef : userDef.get // 判断对应的计算属性值是否是函数还是设置了 `get/set` 属性的对象,并获取函数
if (process.env.NODE_ENV !== 'production') {
if (getter === undefined) {
warn(
`No getter function has been defined for computed property "${key}".`,
vm
)
getter = noop // function noop(){}
}
}
// create internal watcher for the computed property.
/*
为计算属性创建一个内部的监视器Watcher,保存在vm实例的_computedWatchers中
这里的computedWatcherOptions参数传递了一个lazy为true,会使得watch实例的dirty为true
*/
// 这里我们先跳到后面看看 new Watcher
watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions) // 这里返回了 watcher 实例,保存在 `watchers = vm._computedWatchers` 中
// component-defined computed properties are already defined on the
// component prototype. We only need to define computed properties defined
// at instantiation here.
// 组件正在定义的计算属性已经定义在现有组件的原型上则不会进行重复定义
if (!(key in vm)) { // 此时 key (`reversedMessage`) 不在 vm 实例上,所以就定义计算属性,来到下面的 `defineComputed`
// 定义计算属性
defineComputed(vm, key, userDef)
} else if (process.env.NODE_ENV !== 'production') {
// 如果计算属性与已定义的data或者props中的名称冲突则发出warning
if (key in vm.$data) {
warn(`The computed property "${key}" is already defined in data.`, vm)
} else if (vm.$options.props && key in vm.$options.props) {
warn(`The computed property "${key}" is already defined as a prop.`, vm)
}
}
}
}
Watcher
class Watcher {
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: Object
) {
this.vm = vm
/*_watchers存放订阅者实例*/
vm._watchers.push(this)
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy // true
this.sync = !!options.sync
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.lazy // for lazy watchers => true
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// parse expression for getter
/*把表达式expOrFn解析成getter*/
if (typeof expOrFn === 'function') {
this.getter = expOrFn // 这里就是开头的 `reversedMessage`对应的函数
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = function () {}
process.env.NODE_ENV !== 'production' && warn(
`Failed watching path: "${expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
this.value = this.lazy // true ,所以为undefined
? undefined
: this.get()
}
get () {
/*将自身watcher观察者实例设置给Dep.target,用以依赖收集。*/
/* Dep.target = null
const targetStack = []
// 将watcher观察者实例设置给Dep.target,用以依赖收集。同时将该实例存入target栈中
export function pushTarget(_target: Watcher) {
// 这里如果 Dep.target 已经存在,就先暂存在栈中,先将 Dep.target 赋值为 此时的 watcher,
// 到最后 popTarget 中再把之前的 Dep.target 给重新恢复
if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
}
// 将观察者实例从target栈中取出并设置给Dep.target
export function popTarget() {
Dep.target = targetStack.pop()
} */
pushTarget(this) // 现将全局的 Dep.target 设置为当前这个 计算属性对应的 watcher ,此时并不入栈
let value
const vm = this.vm
// 这里执行了 getter 方法,也就是 reversedMessage 对应的 函数
// 在执行的过程中会获取 this.message 的值,这里就会触发 this.message 的 `get` 方法,
// 因为 data 里的每个属性都已被 observe 了,重写了[get/set](https://github.com/vuejs/vue/blob/v2.3.0/src/core/observer/index.js#L152)
// 并且此时 Dep.target 是当前这个 计算属性对应的 watcher,所以 this.message 收集的依赖中就会有该 watcher 了,
// 从而当 this.message 发生改变时,执行 set ,触发 dep.notify() 通知所有的观察者,
// 然后执行 watcher 的 update 方法,这样 reversedMessage 就和 this.message 联系在了一起
// 这里似乎看上去即使你 this.message 改变了 notify -> update 的时候因为 lazy = true,仅仅改变了dirty = true
// 但是这里并没有联系上 compile 的时候,即 `<p>Reversed message: "{{ reversedMessage() }}"</p>` 的情况,
// 所以 当改变 this.message 的时候是会触发试图的更新的,即该计算属性不仅仅只有这一个 watcher
if (this.user) {
try {
value = this.getter.call(vm, vm)
} catch (e) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
}
} else {
value = this.getter.call(vm, vm)
}
// "touch" every property so they are all tracked as
// dependencies for deep watching
//如果存在deep,则触发每个深层对象的依赖,追踪其变化
if (this.deep) { // 此时为 false ,所以不触发这里
//递归每一个对象或者数组,触发它们的getter,使得对象或数组的每一个成员都被依赖收集,形成一个“深(deep)”依赖关系
traverse(value)
}
//将观察者实例从target栈中取出并设置给Dep.target
popTarget() // 此时Dep.target为null
this.cleanupDeps()
return value
}
evaluate () { // 当使用计算属性时,就会执行 `this.get()` 方法,
this.value = this.get() // 执行函数,并获取返回值
this.dirty = false
}
update () {
/* istanbul ignore else */
// 在计算属性时,因为各个计算属性都是lazy= true
// 这里当重新访问计算属性时,createComputedGetter里面调用 watcher.evaluate() 会将 this.dirty = false
// 这样,再次获取计算属性的时候,就会取之前的值,只有当计算属性依赖的data 值发生改变通知watcher 时
// 才会将dirty 置为true,从而使再次访问计算属性时,会获取最新的值
if (this.lazy) { // 此时为 true
this.dirty = true
} else if (this.sync) {
/*同步则执行run直接渲染视图*/
this.run()
} else {
/*异步推送到观察者队列中,下一个tick时调用。*/
queueWatcher(this)
}
}
cleanupDeps () {
/*移除所有观察者对象*/
let i = this.deps.length
while (i--) {
const dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)
}
}
let tmp = this.depIds
this.depIds = this.newDepIds
this.newDepIds = tmp
this.newDepIds.clear()
tmp = this.deps
this.deps = this.newDeps
this.newDeps = tmp
this.newDeps.length = 0
}
computed
是 Vue 中常用的一个属性,具体见计算属性和侦听器现在我们就以官网里的例子来进行源码探秘
首先
computed
的初始化是在state.js
里的initState
方法中,该方法不仅初始化了computed
,还初始化了props、data、methods、watch
等现在我们就来看看
initComputed
方法Watcher
defineComputed
定义计算属性
createComputedGetter
创建计算属性的getter
参考
learnVue
上面仅仅是浅析了一部分简单情况下的
computed
代码,如有不当,欢迎指出^_^
Vue
很精彩,我们继续^_^