# 计算属性的实现
本质上就是一个惰性求值的观察者。回到 src/core/instance/state.js
文件中的 initState 函数,因为计算属性是在这里被初始化的
function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
//是否传了computed
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
function initComputed (vm: Component, computed: Object) {
// $flow-disable-line
const watchers = vm._computedWatchers = Object.create(null)
// computed properties are just getters during SSR
const isSSR = isServerRendering()
for (const key in computed) {
const userDef = computed[key]
const getter = typeof userDef === 'function' ? userDef : userDef.get
//计算属性有两种写法,计算属性可以是一个函数,可以是对象
if (process.env.NODE_ENV !== 'production' && getter == null) {//getter为空,警告没有设置对应的getter
warn(
`Getter is missing for computed property "${key}".`,
vm
)
}
if (!isSSR) { //服务端渲染
// create internal watcher for the computed property.
const computedWatcherOptions = { computed: true } //标识一个观察者对象是计算属性的观察者
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
}
// component-defined computed properties are already defined on the
// component prototype. We only need to define computed properties defined
// at instantiation here.
//检查计算属性的名字是否已经存在于组件实例对象中
//初始化计算属性之前已经初始化了 props、methods 和 data 选项,这些选项数据都会定义在组件实例对象上
if (!(key in vm)) {
defineComputed(vm, key, userDef) //defineComputed 定义计算属性。
} else if (process.env.NODE_ENV !== 'production') {
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)
}
}
}
}
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
export function defineComputed (
target: any,
key: string,
userDef: Object | Function
) {
const shouldCache = !isServerRendering() //非服务端渲染的情况下计算属性才会缓存值
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: userDef
sharedPropertyDefinition.set = noop
} else {
sharedPropertyDefinition.get = userDef.get//有get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: userDef.get
: noop//空函数
sharedPropertyDefinition.set = userDef.set//有set
? userDef.set
: noop
}
//没指定set拦截,然后修改报警告
if (process.env.NODE_ENV !== 'production' &&
sharedPropertyDefinition.set === noop) {
sharedPropertyDefinition.set = function () {
warn(
`Computed property "${key}" was assigned to but it has no setter.`,
this
)
}
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
# 计算属性的实现
拦截器为 haredPropertyDefinition.get
,非服务端渲染下为
function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key] //观察者对象
if (watcher.dirty) {//true
watcher.evaluate()//求值
}
if (Dep.target) {
watcher.depend()//收集依赖
}
return watcher.value
}
Watcher
的depend
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()//用来收集依赖
}
}
在watcher
的constructor
中
this.dirty = this.lazy // for lazy watchers
this.value = this.lazy
? undefined
: this.get()
evaluate () {
//手动求值
this.value = this.get()
this.dirty = false
}
# 深度观测
data () {
return {
a: {
b: 1
}
}
},
watch: {
a () {
console.log('a 改变了')
}
}
Watcher
实例对象时会读取 a 的值从而触发属性 a 的 get 拦截器函数,最终将依赖收集,没有读取 a.b 属性的值,所以对于 b 来讲是没有收集到任何观察者的,修改 a.b 的值是触发不了响应的
深度观测就是用来解决这个问题的,原理是属性 a.b 中没有收集到观察者,就主动读取一下 a.b 的值,你需要将 deep 选项参数设置为 true,主动告诉 Watcher 实例对象你现在需要的是深度观测
看下 watcher
的get()
get () {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
// traverse 在 src/core/observer/traverse.js
const seenObjects = new Set()
export function traverse (val: any) {
_traverse(val, seenObjects)
seenObjects.clear()
}
function _traverse (val: any, seen: SimpleSet) {
let i, keys
const isA = Array.isArray(val)
if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {//不是数组并且不是对象 或者 对象被冻结,或者是vnode实例 不做处理
return
}
if (val.__ob__) {//有__ob__ 如果一个响应式数据是对象或数组,那么它会包含一个叫做 __ob__ 的属性
//避免死循环
const depId = val.__ob__.dep.id
if (seen.has(depId)) {//set 里有 return
return
}
seen.add(depId)
}
if (isA) {//数组 递归调用 _traverse 函数
i = val.length
while (i--) _traverse(val[i], seen)
} else {
keys = Object.keys(val)
i = keys.length
while (i--) _traverse(val[keys[i]], seen)
}
}
# 同步执行观察者
通常情况下当数据状态发生改变时,所有 Watcher 都为异步执行,这么做的目的是出于对性能的考虑Vue 提供了 Vue.config.async 全局配置,它的默认值为 true,在 src/core/config.js
export default ({
async: true,
}: Config)
若改为false,所有观察者都将会同步执行
function queueWatcher (watcher: Watcher) {
const id = watcher.id
if (has[id] == null) {
// 省略...
// queue the flush
if (!waiting) {
waiting = true
if (process.env.NODE_ENV !== 'production' && !config.async) {//true
//单独执行 flushSchedulerQueue
flushSchedulerQueue()
return
}
nextTick(flushSchedulerQueue)
}
}
}
在dep.notify
中
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
//执行观察者对象的 update 更新方法之前就对观察者进行排序,从而保证正确的更新顺序
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
← 渲染函数的观察者 其他选项的初始化及实现 →