导航
[[深入01] 执行上下文](https://juejin.im/post/684490...)
[[深入02] 原型链](https://juejin.im/post/684490...)
[[深入03] 继承](https://juejin.im/post/684490...)
[[深入04] 事件循环](https://juejin.im/post/684490...)
[[深入05] 柯里化 偏函数 函数记忆](https://juejin.im/post/684490...)
[[深入06] 隐式转换 和 运算符](https://juejin.im/post/684490...)
[[深入07] 浏览器缓存机制(http缓存机制)](https://juejin.im/post/684490...)
[[深入08] 前端安全](https://juejin.im/post/684490...)
[[深入09] 深浅拷贝](https://juejin.im/post/684490...)
[[深入10] Debounce Throttle](https://juejin.im/post/684490...)
[[深入11] 前端路由](https://juejin.im/post/684490...)
[[深入12] 前端模块化](https://juejin.im/post/684490...)
[[深入13] 观察者模式 发布订阅模式 双向数据绑定](https://juejin.im/post/684490...)
[[深入14] canvas](https://juejin.im/post/684490...)
[[深入15] webSocket](https://juejin.im/post/684490...)
[[深入16] webpack](https://juejin.im/post/684490...)
[[深入17] http 和 https](https://juejin.im/post/684490...)
[[深入18] CSS-interview](https://juejin.im/post/684490...)
[[深入19] 手写Promise](https://juejin.im/post/684490...)
[[深入20] 手写函数](https://juejin.im/post/684490...)
[[react] Hooks](https://juejin.im/post/684490...)
[[部署01] Nginx](https://juejin.im/post/684490...)
[[部署02] Docker 部署vue项目](https://juejin.im/post/684490...)
[[部署03] gitlab-CI](https://juejin.im/post/684490...)
[[源码-webpack01-前置知识] AST抽象语法树](https://juejin.im/post/684490...)
[[源码-webpack02-前置知识] Tapable](https://juejin.im/post/684490...)
[[源码-webpack03] 手写webpack - compiler简单编译流程](https://juejin.im/post/684490...)
[[源码] Redux React-Redux01](https://juejin.im/post/684490...)
[[源码] axios ](https://juejin.im/post/684490...)
[[源码] vuex ](https://juejin.im/post/684490...)
[[源码-vue01] data响应式 和 初始化渲染 ](https://juejin.im/post/684490...)
[[源码-vue02] computed 响应式 - 初始化,访问,更新过程 ](https://juejin.im/post/684490...)
[[源码-vue03] watch 侦听属性 - 初始化和更新 ](https://juejin.im/post/684490...)
[[源码-vue04] Vue.set 和 vm.$set ](https://juejin.im/post/684490...)
[[源码-vue05] Vue.extend ](https://juejin.im/post/684490...)
[[源码-vue06] Vue.nextTick 和 vm.$nextTick ](https://juejin.im/post/684790...)
前置知识
学习目标
computed计算属性只有在computed被访问时,才会去计算
- 因为在new Watcher是computed watcher时,即lazy=true时,在构造函数中没有立即执行get()方法,而是在计算属性被访问时触发computed的响应式get后,执行的get方法中回去调用computed getter函数
computed计算属性具有缓存功能
- 通过dirty=true时,才会去执行watcher.evaluate()方法,才会执行computed wawtcher中的get()即computd 定义的函数,从新计算,计算完后,将this.dirty=false
- 下次再访问时,会先判断dirty,是false就直接返回缓存的值
- computed的依赖必须是响应式数据,不然即使依赖变化不会触发computed重新计算
- 即使computed的依赖变化了,但是computed计算的值没有变化的话,不会从新渲染
computed watcher 和 render watcher 和 dep 和 dep.subs 和 watcher.deps 之间复杂的关系
computed访问过程
- 访问computed,触发computed响应式的get函数,get函数中判断如果dirty=true,那么执行computed watchet的evalute方法,即watcher中的get()方法,然后把 Dep.target = computed watcher,执行computed getter函数就是用户指定的computed函数
- 执行computed getter函数的时,因为有依赖的响应式的data,所以又会触发data的get函数,执行dep.depend(),就是把computed watcher中的 newDeps中添加computed依赖项的 dep,同时向computed依赖项的dep的subs中添加computed watcher,然后又把Dep.target = targetStack数组中的前一个watcher,然后返回计算的结果,并且把 dirty=false
- 然后判断 Dep.taret 存在,就执行 computed watcher的depend方法,循环遍历computed watcher中的deps,取出dep,执行dep.depend
- 因为此时的 Dep.target = render watcher,所以dep.depend会向render watcher的 newDeps中添加data的dep,向data的dep中的subs中添加render watcher,那么此时的computed计算属性的依赖项的dep中的subs就是[computed watcher, render watcher]这样就保证了渲染的时候,computed先于render先执行,保证computed有值
comuted更新过程
- 上面的例子中,change的过程,是改变了computed的依赖,之后的一系列流程
- 触发computed的响应式依赖的dep.notify()方法,循环遍历该依赖的dep.subs数组中的每一个watcher.update()方法,在上面的例子中subs=[computed watcher, render watcher]所以先执行computed watcher
compued watcher
- 在update方法中,会判断lazy=true?,因为computed watcher的lazy=true,所以执行 dirty=true
render watcher
- 在update方法中, lazy不为true, 会执行 queueWatcher(this),就是调用渲染watcher重新求值和渲染
一些单词
internal:内部的
Scheduler:调度器
queue:队列
( flushSchedulerQueue : 刷新调度器队列 )
computed源码
(1) computed的初始化
Vue.prototype._init
=>initState(vm)
=>initComputed(vm, opts.computed)
(1-1) initComputed(vm, opts.computed)
主要做了以下事情
<font color=blue size=5>(1-1-1) new Watcher(vm, getter, noop, computedWatcherOptions) </font>
- new了一个 computed watcher
参数
computedWatcherOptions
- 如何知道是一个computed watcher => 主要通过第4个参数computedWatcherOptions => { lazy: true },即comoputed watcher的 lazy属性是true
getter
- 就是用户自己定义的computed对象中的函数
noop
- 是一个空函数
注意点:
在 new Watcher 计算属性watcher的构造函数中
this.value=this.lazy ? undefined : this.get()
- 因为计算属性watcher的lazy=true,所以不会立即去执行 get() 方法
<font color=blue>那什么时候去执行呢get()呢?</font>
- <font color=blue>执行时机就是在template中访问了computed</font>,因为computed又定义了响应式,访问了computed属性就会执行computed的get方法,在get方法中会执行
watcher.evaluate()
方法,在里面就是去执行get()
,从而去计算computed的结果
- <font color=blue>执行时机就是在template中访问了computed</font>,因为computed又定义了响应式,访问了computed属性就会执行computed的get方法,在get方法中会执行
<font color=blue size=5>(1-1-2) 把computd定义成响应式</font>
defineComputed(vm, key, userDef)
=>Object.defineProperty(target, key, sharedPropertyDefinition)
=>sharedPropertyDefinition.get
=>createComputedGetter(key)
=>computedGetter
- 也就是说访问computed中的
this.xxxx
就会去执行computedGetter
函数
<font color=blue size=5>(1-1-3) computedGetter - 这个方法很重要 !!!!!!!!!!!!!!!!!!!!!</font>
watcher.evaluate() 执行计算属性watcher中的evaluate方法
- 当dirty=true,并且watcher存在时,就会执行 computed watcher 的 evalute 方法
<font color=blue>evalute</font> 方法会执行 <font color=blue>get()</font> 方法,并将 <font color=blue>this.dirty</font> 改为 <font color=blue>false</font>
<font color=blue>get()</font>
<font color=blue>pushTarget(this)</font>
- <font color=blue>向 targetStack 数组中 push 一个computed watcher</font>
- <font color=blue>将 Dep.target 指定为 computed watcher</font>
执行用户在computed对象中定义的方法,即getter方法newName
- 比如
computed: {newName() {return this.name + 'new' }}
注意:
- 在这过程中又会触发 data对象的影响式,即this.name触发响应式data中的get函数,因为访问了data的name属性
- data,computed都有自己的响应式
这里data的响应式又会收集计算属性的watcher,这个放在后面的计算属性的访问流程中去梳理
主要就是
- 向 computed watcher 的newDeps中添加render watcher的dep
- 向 render watcher的依赖的属性的dep的 subs 中添加 computed watcher
- 详见下文
- 比如
watcher.depend() 执行计算属性watcher的depend方法
- 放在下面访问流程一起分析
- 源码
- Vue.prototype._init => initState(vm) => initComputed(vm, opts.computed)
initComputed - scr/core/instance/state.js
initComputed - scr/core/instance/state.js --- function initComputed (vm: Component, computed: Object) { const watchers = vm._computedWatchers = Object.create(null) // 声明 watchers 和 _computedWatchers 为一个空对象 const isSSR = isServerRendering() // 是否是ssr环境 for (const key in computed) { const userDef = computed[key] // userDef 是 computed 的 getter 函数 const getter = typeof userDef === 'function' ? userDef : userDef.get // getter // computed getter 可以是函数 或者 具有get方法的对象 // 这里一般都是函数,并且该函数需要return一个值 if (process.env.NODE_ENV !== 'production' && getter == null) { warn( `Getter is missing for computed property "${key}".`, vm ) } // 如果不是函数或者对象就报警告 if (!isSSR) { // create internal watcher for the computed property. // 非ssr环境,即浏览器环境,就新建 computed watcher // computed watcher // computedWatcherOptions = { lazy: true } // getter = 用户自定义的computed对象中的函数 watchers[key] = new Watcher( vm, getter || noop, // 用户自定义的computed对象中的函数,即 computed getter noop, computedWatcherOptions, // { lazy: true } ) } // component-defined computed properties are already defined on the // component prototype. We only need to define computed properties defined // at instantiation here. // 在 vue.extends 和 new Vue() 过程中都定义了响应式的computed if (!(key in vm)) { defineComputed(vm, key, userDef) // defineComputed 将 computed 变成响应式 } else if (process.env.NODE_ENV !== 'production') { // 处理重名的情况,在props,data,computed不能用重名的key 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 - scr/core/instance/state.js
defineComputed - scr/core/instance/state.js --- export function defineComputed ( target: any, key: string, userDef: Object | Function ) { const shouldCache = !isServerRendering() // shouldCache 如果在浏览器环境就是 true if (typeof userDef === 'function') { sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) // 定义computed被访问时,触发的get : createGetterInvoker(userDef) sharedPropertyDefinition.set = noop } else { // userDef 不是 function,我们直接忽略 sharedPropertyDefinition.get = userDef.get ? shouldCache && userDef.cache !== false ? createComputedGetter(key) : createGetterInvoker(userDef.get) : noop sharedPropertyDefinition.set = userDef.set || noop } 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) // 定义响应式 computed // 1. 当通过 this.xxxx 访问computed,就会触发sharedPropertyDefinition对象中的get // 2. get 其实就是下面createComputedGetter返回的 computedGetter函数 }
createComputedGetter - scr/core/instance/state.js
createComputedGetter - scr/core/instance/state.js --- function createComputedGetter (key) { return function computedGetter () { const watcher = this._computedWatchers && this._computedWatchers[key] // 取出每一个 computed watcher if (watcher) { if (watcher.dirty) { // watcher.dirty // 1. 默认初始化时,comoputed watcher 的 dirty=true // 2. 当 dirty=true 就会执行 watcher.evaluate() // 3. watcher.evaluate() 执行完后, dirty=false // 总结: dirty=true => watcher.evaluate() => dirty=false watcher.evaluate() // watcher.evaluate() // 1. 会去执行 computed watcher 中的 get() // pushTarget(this) // 1. 将 computed watcher 添加到 targetStack 数组中 // 2. 将 Dep.target = computed watcher // 执行 this.getter.call(vm, vm) 即用户自定义的 computed对象中的方法 // 1. 列如: computed: {newName() {return this.name + 'new' }} // 2. 因为:computed的newName方法中,依赖了data中的this.name,即访问到了this.name就会触发data响应式的get方法 // 3. 所以:ata响应式的get方法执行过程如下 // 获取到了this.name的值 // 此时,Dep.target 是computed watcher // 然后执行this.name对象的dep类的depend方法进行依赖收集 // 向 computed watcher 的newDeps中添加render watcher的dep // 向 render watcher 的 subs 中添加 computed watcher // popTarget() // 1. targetStack.pop()将 computed watcher从targetStack数组中删除 // 2. 并且将 Dep.target 指定为数组中的前一个 watcher,没有了就是undefined // 2. 将 dirty=false // evaluate () { // this.value = this.get() // this.dirty = false // } // 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 // } } if (Dep.target) { watcher.depend() // depend () { // let i = this.deps.length // while (i--) { // this.deps[i].depend() // } // } } return watcher.value } } }
Watcher - scr/core/observer/watcher.js
Watcher - scr/core/observer/watcher.js --- export default class Watcher { vm: Component; expression: string; cb: Function; id: number; deep: boolean; user: boolean; lazy: boolean; sync: boolean; dirty: boolean; active: boolean; deps: Array<Dep>; newDeps: Array<Dep>; depIds: SimpleSet; newDepIds: SimpleSet; before: ?Function; getter: Function; value: any; constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: ?Object, isRenderWatcher?: boolean ) { this.vm = vm if (isRenderWatcher) { vm._watcher = this } vm._watchers.push(this) // options if (options) { this.deep = !!options.deep this.user = !!options.user this.lazy = !!options.lazy this.sync = !!options.sync this.before = options.before } else { this.deep = this.user = this.lazy = this.sync = false } this.cb = cb this.id = ++uid // uid for batching this.active = true this.dirty = this.lazy // for lazy watchers this.deps = [] this.newDeps = [] this.depIds = new Set() this.newDepIds = new Set() this.expression = process.env.NODE_ENV !== 'production' ? expOrFn.toString() : '' // parse expression for getter if (typeof expOrFn === 'function') { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) if (!this.getter) { this.getter = noop process.env.NODE_ENV !== 'production' && warn( `Failed watching path: "${expOrFn}" ` + 'Watcher only accepts simple dot-delimited paths. ' + 'For full control, use a function instead.', vm ) } } this.value = this.lazy ? undefined : this.get() } /** * Evaluate the getter, and re-collect dependencies. */ 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 } /** * Add a dependency to this directive. */ addDep (dep: Dep) { const id = dep.id if (!this.newDepIds.has(id)) { this.newDepIds.add(id) this.newDeps.push(dep) if (!this.depIds.has(id)) { dep.addSub(this) } } } /** * Clean up for dependency collection. */ cleanupDeps () { let i = this.deps.length while (i--) { const dep = this.deps[i] if (!this.newDepIds.has(dep.id)) { dep.removeSub(this) } } let tmp = this.depIds this.depIds = this.newDepIds this.newDepIds = tmp this.newDepIds.clear() tmp = this.deps this.deps = this.newDeps this.newDeps = tmp this.newDeps.length = 0 } /** * Subscriber interface. * Will be called when a dependency changes. */ update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { queueWatcher(this) } } /** * Scheduler job interface. * Will be called by the scheduler. */ run () { if (this.active) { const value = this.get() if ( value !== this.value || // Deep watchers and watchers on Object/Arrays should fire even // when the value is the same, because the value may // have mutated. isObject(value) || this.deep ) { // set new value const oldValue = this.value this.value = value if (this.user) { try { this.cb.call(this.vm, value, oldValue) } catch (e) { handleError(e, this.vm, `callback for watcher "${this.expression}"`) } } else { this.cb.call(this.vm, value, oldValue) } } } } /** * Evaluate the value of the watcher. * This only gets called for lazy watchers. */ evaluate () { this.value = this.get() this.dirty = false } /** * Depend on all deps collected by this watcher. */ depend () { let i = this.deps.length while (i--) { this.deps[i].depend() } } /** * Remove self from all dependencies' subscriber list. */ teardown () { if (this.active) { // remove self from vm's watcher list // this is a somewhat expensive operation so we skip it // if the vm is being destroyed. if (!this.vm._isBeingDestroyed) { remove(this.vm._watchers, this) } let i = this.deps.length while (i--) { this.deps[i].removeSub(this) } this.active = false } } }
(2) computed的访问过程
案例
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="./vue.js"></script> </head> <body> <div id="root"> <div>{{newName}}</div> <button @click="change">change</button> </div> <script> new Vue({ el: '#root', data: { name: 'ssssssssssssss' }, computed: { newName() { return this.name + 'new' } }, methods: { change() { this.name = '222222222222222' } } }) </script> </body> </html>
- 在
vm._update(vm._render(), hydrating)
过程中, 当在template模板中使用了 computed 对象中的key的时候,就会触发computed响应式对象的 get 方法 - computed的响应式get方法就是computedGetter方法,在该方法中,会判断wathcer和watcher.dirty是否存在,存在证明就是computed watcher,就会执行 computed wathcer 的 watcher.evaluate() 方法
- watcher.evaluate()方法就会执行computed watcher中的get()方法,并将 this.dirty 改为 false
watcher.evaluate()
get()方法
调用 <font color=red>pushTarget(this)</font>
- <font color=red>向 targetStack 数组中 push 一个computed watcher</font>
- <font color=red>将 Dep.target 指定为 computed watcher</font>
然后调用 computed getter 方法,即用户自己定义的computed对象中的方法
- 比如:
computed: {newName() {return this.name + 'new' }}
- 因为:computed的newName方法中,依赖了data中的this.name,即访问到了this.name就会触发data响应式的get方法
所以:<font color=DarkOrChid>data响应式的get方法执行过程如下:</font>
- 获取到了this.name的值
此时,Dep.target 是 computed watcher
然后执行this.name对应的dep类的depend方法进行依赖收集
- <font color=DarkOrChid>向 computed watcher 的newDeps中添加render watcher的对应的data属性的 dep</font>
- <font color=DarkOrChid>向 render watcher对应的data属性的dep实例的 subs 中添加 computed watcher</font>
- <font color=DarkOrChid>等于说data的 this.name 和 computed Watcher 具有同一个 dep 实例</font>
执行完上面的步骤后 dep.subs 和 computed watcher.newDeps 的状态是
this.name 对应的 dep 实例中的 subs = [computedWatcher]
computed watcher 中的 newDeps = [上面的this.name对应的dep]
- 返回name的值
- 比如:
调用 <font color=red>popTarget()</font>
- <font color=red>targetStack.pop()将 computed watcher从targetStack数组中删除</font>
<font color=red>并且将 Dep.target 指定为数组中的前一个 watcher</font>
- <font color=red>Dep.target = render watcher</font>
- 在上面的例子中 targetStack 数组中在执行computed 的getter方法时一共有两个成员
- 第一个:render watcher
- 第二个:computed watcher
- pop后还剩一个render watcher
- 最后返回computed计算得到的结果值
watcher.depend()
- 当 Dep.target 存在时,才会执行 watcher.depend()
- 上面执行完,<font color=DarkOrChid>Dep.target = render watcher</font>
watcher.depend()
- 然后执行 <font color=DarkOrChid>compute watcher 中的 watcher.depend() 方法</font>
然后,<font color=DarkOrChid>从后往前,依次取出 computed watcher 中 deps 数组中的 dep,执行 dep.depend()</font>
- 注意:上面computed watcher 中的 deps 中的 dep,就是this.name对象的dep,里面的subs数组中只有一个computedWatcher
- 对比:在data对象的属性被访问的时候,也会执行data对应的属性的 dep.depend()
<font color=DarkOrChid>Dep.target.addDep(this)</font>
- <font color=DarkOrChid>此时的 Dep.targt 是 render watcher</font>,因为 popTarget() 操作pop出computed watcher后就只剩render watcher了
addDep 前
- render watcher中的 deps 是空数组
- render watcher中的 newDeps 是空数组
addDep 主要做两件事情
- <font color=DarkOrChid>向 render watcher 的 newDeps 中添加 该render watcher 对应的datad属性的 dep</font>
- <font color=DarkOrChid>向 render watcher 对应的data属性对应的dep类的 subs 中添加 render watcher</font>
添加后,data的dep.subs = [computed watcher, render watcher]
- <font color=red>这样当this.name属性修改后触发对应的set函数,就会触发dep.notify,然后循环sub中的watcher,执行watcher.update()方法</font>
- <font color=red>[computed watcher, render watcher]这样的顺序保证了在render的时候,computed一定有值</font>
(3) computed的更新过程
- 上面的例子中,change的过程,是改变了computed的依赖,之后的一系列流程
- 触发computed的响应式依赖的dep.notify()方法,循环遍历该依赖的dep.subs数组中的每一个watcher.update()方法,在上面的例子中subs=[computed watcher, render watcher]所以先执行computed watcher
compued watcher
- 在update方法中,会判断lazy=true?,因为computed watcher的lazy=true,所以执行 dirty=true
render watcher
- 在update方法中, lazy不为true, 会执行 queueWatcher(this),就是调用渲染watcher重新求值和渲染
资料
实际案例-避坑 https://juejin.im/post/684490...
详细 https://juejin.im/post/684490...
源码版 https://juejin.im/post/684490...
computed watcher https://juejin.im/post/684490...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。