内容简介:源码中有这么一段:所以如果仅仅是上面函数的作用就是改写
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)
}
}
}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
UNIX环境高级编程
W.Richard Stevens Stephen A.Rago、Stephen A. Rago / 人民邮电出版社 / 2006-2 / 99.00元
本书是被誉为UNIX编程“圣经”的Advanced Programming in the UNIX Environment一书的更新版。在本书第一版出版后的十几年中,UNIX行业已经有了巨大的变化,特别是影响UNIX编程接口的有关标准变化很大。本书在保持了前一版的风格的基础上,根据最新的标准对内容进行了修订和增补,反映了最新的技术发展。书中除了介绍UNIX文件和目录、标准I/O库、系统数据文件和信......一起来看看 《UNIX环境高级编程》 这本书的介绍吧!