写在前面:本文为我的在平常工做和学习中的一些总结,便于后来查漏补缺,非权威性资料,请带着本身的思考^-^。
前文连接:了解一下Vue - [Vue是怎么实现响应式的(一)]
前言:上一篇文章简单介绍了基于data的Vue响应式的实现,这篇将进行一点扩展,data变动会自动触发computed进行从新计算,从而反映到视图层面,那这个过程又是怎么作到的呢?react
顾名思义,在initComputed中主要进行的工做是对computed进行初始化,上代码:segmentfault
function initComputed (vm, computed) { const watchers = vm._computedWatchers = Object.create(null) // 用于存放computed 相关的watcher for (const key in computed) { // 遍历computed,为每个computed属性建立watcher实例,这个watcher实例的做用后面会体现 // 这里能够看出,平时咱们的computed通常都是函数形式的,但不少时候咱们也能够写成{get() {}, set() {}},这种对象形式 const userDef = computed[key] const getter = typeof userDef === 'function' ? userDef : userDef.get // create internal watcher for the computed property. watchers[key] = new Watcher( vm, getter || noop, // 此函数参数会在watcher实例的get方法中进行执行 noop, computedWatcherOptions // {dirty: true},能够翻一下以前的class Watcher代码或者找源码看一下,这个options其中的一个做用就在于控制实例化watcher的时候是否先执行一次get() 方法,这个get方法内部会对参数传进来的getter进行执行 ) if (!(key in vm)) { defineComputed(vm, key, userDef) } } } // function defineComputed function defineComputed ( // 总体来讲此函数的做用就是经过defineProperty定义getter/setter将computed中的属性代理到vm上 target: any, key: string, userDef: Object | Function ) { const shouldCache = !isServerRendering() // 非服务端渲染,则为true if (typeof userDef === 'function') { sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) // computed的计算结果会被缓存,不须要每次访问computed属性时都从新进行计算 : userDef // computed 不使用缓存的状况 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) }
实例化Vue期间,对computed的处理,作了:缓存
$mount包含了模板编译、render函数生成... 再也不赘述
和响应式相关的是在$mount中实例化了一个render Watcher,前文已经有过标注,在实例化Watcher中会执行get()函数,从而执行render函数,在render函数的执行过程当中势必会读取页面渲染使用到的computed属性,触发其getter:函数
// computed属性的getter function createComputedGetter (key) { return function computedGetter () { const watcher = this._computedWatchers && this._computedWatchers[key] // watcher就是initComputed时传入computed[key]做为getter参数实例化的watcher if (watcher) { if (watcher.dirty) { // 若是是首次读取computed[key],通常watcher.dirty为true watcher.evaluate() /** evaluate () { this.value = this.get() this.dirty = false // 执行完get以后就会将dirty置为false,这样下次读取computed[key]时就不会再从新计算 } 执行watcher.get(),在这个函数内部会作几个事情: 执行pushTarget函数,将当前的Dep.target置为当前watcher实例 执行computed[key],计算获得结果赋值给watcher.value 若是computed[key]函数内容是经过几个data计算获得值,则将会触发data的getter, 这将会把这个几个data的dep对象做为依赖添加至watcher的deps列表,同时将当前watcher添加至这些dep的subs列表中, 通俗一点说,这个过程对于当前watcher来讲就是依赖收集过程,将其依赖的项添加至deps中 对于某一个data的dep来讲,就是将当前watcher添加至其观察者列表subs中 执行完以上过程,就会将Dep.target重置为以前的值, */ } if (Dep.target) { watcher.depend() // 这一步也很重要,此处是将当前watcher的依赖也加入到Dep.target的依赖列表中 /** 为何要有这一步呢? 由于当前的Dep.target在执行完watcher.evaluate以后就被重置回了上一个Dep.target,通常来讲当前的Dep.target 就是render Watcher 设想有这种状况:某一个data的属性x并无直接用于render,那么在render执行过程的依赖收集x就不会被添加 到render Watcher的deps中,x的dep.subs中也没有render Watcher 也就是说以后若是对x进行从新赋值,则不会 通知render Watcher,此时尚未问题,但时若是x被computed用到,因为computed没有setter,则x被从新赋值 通知到computed Watcher去从新计算,可是computed并无直接通知render Watcher的方法,这个时候render就不会 从新执行,页面也就不会进行更新。。。 */ } return watcher.value } } }
总之,详见代码注释。。。oop
这里已经知道对data从新赋值,会触发其对应setter学习
// setter set: function reactiveSetter (newVal) { if (newVal === value || (newVal !== newVal && value !== value)) { return } val = newVal childOb = !shallow && observe(newVal) dep.notify() // 这里会通知到全部watcher,让它们进行update } // dep.notify notify () { // 在render阶段进行依赖收集时会将watcher加入subs列表,computed在进行计算的时候会收集依赖的data, // 与此同时会将computed的watcher对象添加至data的dep.subs列表 const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } }
render watcher和computed watcher执行update是不一样的,computed watcher的update方法只会将watcher.dirty置为true,这表明该computed依赖的data发生了更新,须要从新计算;这样在render函数再次执行的时候会读取computed,触发computed的getter,在getter中会从新计算得出computed的新值,而且将dirty置为false,表明只需计算一次,在同一个render loop中屡次引用该computed将不会从新计算。this
THE END...lua