<body> <div id="app"> {{ count }} </div> </body> <script> new Vue({ el: '#app', data () { return { num: 66 } }, computed: { count () { console.log(1) return this.num } }, methods: { add () { setInterval(() => { this.num ++ }, 1000) } }, created () { this.add() } }) </script>
console.log(1)
会间隔的打印出来吗?{{ count }}
,再问console.log(1)
会间隔的打印出来吗?watch
监听count,来打印`console.log(1)watch: { count: function (oldValue, newValue) { } }
如下是个人理解,有误还请指出,共同进步html
computed
是惰性求值,在new watcher时是计算属性时,this.value=undefined
因此一开始不会触发get进行依赖收集即仅仅定义computed
的话是没有进行计算属性count
的依赖收集(能够相似当作data中的数值,仅仅进行了响应式get,set
的定义,并无触发dep.depend
,因此当值发生变化的时候,他并不知道要通知谁,也就不会执行相应的回调函数了)源码中有这么一段:vue
depend () { if (this.dep && Dep.target) { //由于惰性求值,因此Dep.target为false this.dep.depend() } }
因此若是仅仅是computed
的初始化的话并Dep.target
就是undefined
,因此实例化的watch
并不会加入dep的中express
function initComputed (vm: Component, computed: Object) { const watchers = vm._computedWatchers = Object.create(null) //(标记1)新建一个没有原型链的对象,用来存`computed`对象每一个值的watch实例对象 const isSSR = isServerRendering() //与服务端渲染有关,暂时忽略 for (const key in computed) { const userDef = computed[key] //取key的值,该值大部分是function类型 //下面主要做用就是在非生产环境中没有getter,保警告 const getter = typeof userDef === 'function' ? userDef : userDef.get if (process.env.NODE_ENV !== 'production' && getter == null) { warn( `Getter is missing for computed property "${key}".`, vm ) } } if (!isSSR) { //computed中不一样的key,也就是计算属性生成watch实例, //watch做用:简单看就是当值发生变化时会触通知到watch,触发更新,执行回调函数 watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions ) } if (!(key in vm)) { //做用是将{key: userDef}变成响应式,重写其get和set defineComputed(vm, key, userDef) } 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 ? shouldCache && userDef.cache !== false ? createComputedGetter(key) : userDef.get : noop sharedPropertyDefinition.set = userDef.set ? userDef.set : noop } Object.defineProperty(target, key, sharedPropertyDefinition) }
上面函数的做用就是改写get与set
,关键就是这个createComputedGetter
在作什么?
早版本createComputedGetter
的实现是:数组
function createComputedGetter(){ return function computedGetter () { //这个就是以前用来收集watch实例的一个对象,可看注释:标记1 const watcher = this._computedWatchers && this._computedWatchers[key] if(watcher) { if(watcher.dirty) { watcher.evaluate() } if(Dep.target){ //这里也能够看出Dep.target为false时是不会触发depend,即添加依赖 watcher.depend() } return watcher.value } } }
export default class Watcher { constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: ?Object, isRenderWatcher?: boolean ) { //进行初始化的定义,忽略无关代码 if(options) { this.lazy = !!options.lazy }else { this.lazy = false } this.getter = parsePath(expOrFn) //返回一个取data值得函数 this.dirty = this.lazy //true this.value = this.lazy ? undefined : this.get() //undefined,当不会执行get时也就不会触发get实例方法中的depend的了 } get () { // 伪代码 Dep.target = this //取值也就是访问触发属性的get,get中又触发dep.depend(),而dep.depend内部触发的是Dep.target.addDep(this),这里的this实际上是Dep实例 let value = this.getter.call(vm, vm) Dep.target = undefined } addDep (dep: Dep) { //伪代码 const id = dep.id if(!this.depIds.has(id)) { this.depIds.add(id) this.deps.push(dep) dep.addSub(this) //this是watch实例对象 } } update () { // 省略... } getAndInvoke (cb: Function) { // 省略... } evaluate () { this.value = this.get() this.dirty = false } depend () { let i = this.deps.length while(i --) { this.deps[i].depend() } } ... }
总结: 1.watcher.dirty
默认为true,执行watcher.evaluate()
因此computed第一次默认会渲染,与watch不一样;2.当默认渲染,触发了get,Dep.target
就不是false,就会执行watcher.depend()
app
return this.num + this.num1
,当读取计算属性时会分别触发num与num1
的get,get中又触发dep.depend(),而dep.depend内部触发的是Dep.target.addDep(this),这里的this实际上是Dep实例,这样就会分别将不一样编号的num与num1
的dep,加入到deps中,最后将计算属性的依赖加入到num,num1
的Dep中,this.deps[i].depend()
也会加,但以前已加入改id因此猜想会直接return掉num
发生改变,触发set
,触发其notify
方法即遍历dep.subDeps数组(subDeps中放的是各类依赖),触发依赖的update方法。但以前的update方法看了一下update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { queueWatcher(this) } } }
能够看出直接走queueWatcher(this)
因此就算内容没有变化,也会走渲染流程,这就形成了浪费函数
function createComputedGetter (key) { return function computedGetter () { const watcher = this._computedWatchers && this._computedWatchers[key] if (watcher) { watcher.depend() return watcher.evaluate() } } }
if (this.dep && Dep.target) { this.dep.depend() } }
上面这里的dep又是哪里来的呢?在watch类中加了下面代码oop
if (this.computed) { this.value = undefined this.dep = new Dep() //相似一个Object对象,进行observer设置get,set响应式时会进let dep = new Dep, 来收集改值得依赖 } else { this.value = this.get() }
因此从上面的实现能够看出,对当前计算属性自身也生成一个dep列表进行收集;彻底能够把一个computed的初始化看出data中数据的初始化,只不过该值又依赖多个依赖this
evaluate () { if (this.dirty) { this.value = this.get() this.dirty = false } return this.value }
update () { /* istanbul ignore else */ if (this.computed) { if (this.dep.subs.length === 0) { this.dirty = true } else { this.getAndInvoke(() => { this.dep.notify() }) } } else if (this.sync) { this.run() } else { queueWatcher(this) } }, //当计算属性的值发生变化时,改触发回调函数或者进行渲染,而不是经过以前值(例如num改变)变化就触发回调 getAndInvoke (cb: Function) { const value = this.get() if ( value !== this.value || isObject(value) || this.deep ) { const oldValue = this.value this.value = value this.dirty = false if (this.user) { try { cb.call(this.vm, value, oldValue) } catch (e) { handleError(e, this.vm, `callback for watcher "${this.expression}"`) } } else { cb.call(this.vm, value, oldValue) } } }
当触发update时首先经过getAndInvoke
函数进行值得比较,看是否发生变化,即只有在变化时才会执行,执行的是this.dep.notify()
,而这边打的this
是当前watch实例对象;由于以前就添加了依赖this.dep.depend()
因此接着触发cb.call(this.vm, value, oldValue)
cb是:this.dep.notify()
但this指向了vm
用来触发渲染更新lua
createComputedGetter
函数,就会去取值this.value = this.get()
,进行第一次渲染或取值;同时watcher.depend()
,将计算属性的依赖
添加至dep中,watch.update
,首先判断是否存在依赖,存在则只需watcher.getAndInvoke(cb)
,