内容简介:源码中有这么一段:所以如果仅仅是上面函数的作用就是改写
vue中computed小秘密的发现之旅
首先我们看一段代码
<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)
会间隔的打印出来吗? -
html中去掉
{{ count }}
,再问console.log(1)
会间隔的打印出来吗? - 如果第二问没有打印出来,那么在第二问的基础上怎么修改才能再次打印出来呢?
我先来揭晓答案
watch
watch: { count: function (oldValue, newValue) { } }
请问为什么呢?
以下是我的理解,有误还请指出,共同进步
-
一句话总结就是
computed
是惰性求值,即仅仅定义computed
的话是没有进行计算属性count
的依赖收集(可以类似看成data中的数值,仅仅进行了响应式get,set
的定义,并没有触发dep.depend
,所以当值发生变化的时候,他并不知道要通知谁,也就不会执行相应的回调函数了)
源码中有这么一段:
depend () { if (this.dep && Dep.target) { //因为惰性求值,所以Dep.target为false this.dep.depend() } }
所以如果仅仅是 computed
的初始化的话并 Dep.target
就是 undefined
,所以实例化的 watch
并不会加入dep的中
看看Computed的实现
- computed初始化
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) } } }
- defineComputed 先看这个函数做了什么
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 } } }
重点看看watch
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()
watcher.depend() 早版的实现,它有什么问题
-
this.dep这个数组中元素都是Dep的实例对象,watcher所依赖的所有Dep实例化列表;
举个例子:当计算属性中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中, -
这样就会出现一个问题,当
num
发生改变,触发set
,触发其notify
方法即遍历dep.subDeps数组(subDeps中放的是各种依赖),触发依赖的update方法。这样就导致如果num,num1
发生值得变化,但其和不变,也会造成渲染,这是不合理的
新版本,发生了变化
- 第一个createComputedGetter
function createComputedGetter (key) { return function computedGetter () { const watcher = this._computedWatchers && this._computedWatchers[key] if (watcher) { watcher.depend() return watcher.evaluate() } } }
- 第二个watcher.depend()
if (this.dep && Dep.target) { this.dep.depend() } }
上面这里的dep又是哪里来的呢?在watch类中加了下面代码
if (this.computed) { this.value = undefined this.dep = new Dep() //类似一个Object对象,进行observer设置get,set响应式时会进let dep = new Dep, 来收集改值得依赖 } else { this.value = this.get() }
所以从上面的实现,完全可以把一个computed的初始化看出data中数据的初始化,只不过该值又依赖多个依赖
- 第三个evaluate
evaluate () { if (this.dirty) { this.value = this.get() this.dirty = false } return this.value }
总结
- 计算属性的观察者是惰性求值,需要手动通过get
- 怎么手动get,所以有了问题的第二问,和第三问
-
触发了get,也就是触发了
createComputedGetter
函数,就会去取值this.value = this.get()
,进行第一次渲染或取值;同时watcher.depend()
,将计算属性的依赖
添加至dep中, -
值发送变化时,输出
watch.update
,首先判断是否存在依赖,存在则只需watcher.getAndInvoke(cb)
,
相关代码如下:
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) } } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Scrum精髓
Kenneth Rubin / 姜信宝、米全喜、左洪斌、(审校)徐毅 / 清华大学出版社 / 2014-6-1 / CNY 79.00
短短几年时间,Scrum跃升为敏捷首选方法,在全球各地得以普遍应用。针对如何用好、用巧这个看似简单的框架,本书以通俗易懂的语言、条理清晰的脉络阐述和提炼出Scrum的精髓。全书共4部分23章,阐述了七大核心概念:Scrum框架,敏捷原则,冲刺,需求和用户故事,产品列表,估算与速率,技术债;三大角色:产品负责人,ScrumMaster,开发团队以及Scrum团队构成:Scrum规划原则及四大规划活动......一起来看看 《Scrum精髓》 这本书的介绍吧!