Navigation
[[ thorough 01] Execution context ](https://juejin.im/post/684490...)
[[ thorough 02] Prototype chain ](https://juejin.im/post/684490...)
[[ thorough 03] Inherit ](https://juejin.im/post/684490...)
[[ thorough 04] The event loop ](https://juejin.im/post/684490...)
[[ thorough 05] currying Partial function Function memory ](https://juejin.im/post/684490...)
[[ thorough 06] Implicit conversion and Operator ](https://juejin.im/post/684490...)
[[ thorough 07] Browser caching mechanism (http Caching mechanisms )](https://juejin.im/post/684490...)
[[ thorough 08] Front end security ](https://juejin.im/post/684490...)
[[ thorough 09] Depth copy ](https://juejin.im/post/684490...)
[[ thorough 10] Debounce Throttle](https://juejin.im/post/684490...)
[[ thorough 11] Front-end routing ](https://juejin.im/post/684490...)
[[ thorough 12] Front-end modularization ](https://juejin.im/post/684490...)
[[ thorough 13] Observer mode Publish subscribe mode Two way data binding ](https://juejin.im/post/684490...)
[[ thorough 14] canvas](https://juejin.im/post/684490...)
[[ thorough 15] webSocket](https://juejin.im/post/684490...)
[[ thorough 16] webpack](https://juejin.im/post/684490...)
[[ thorough 17] http and https](https://juejin.im/post/684490...)
[[ thorough 18] CSS-interview](https://juejin.im/post/684490...)
[[ thorough 19] Handwriting Promise](https://juejin.im/post/684490...)
[[ thorough 20] Handwritten functions ](https://juejin.im/post/684490...)
[[react] Hooks](https://juejin.im/post/684490...)
[[ Deploy 01] Nginx](https://juejin.im/post/684490...)
[[ Deploy 02] Docker Deploy vue project ](https://juejin.im/post/684490...)
[[ Deploy 03] gitlab-CI](https://juejin.im/post/684490...)
[[ Source code -webpack01- Pre knowledge ] AST Abstract syntax tree ](https://juejin.im/post/684490...)
[[ Source code -webpack02- Pre knowledge ] Tapable](https://juejin.im/post/684490...)
[[ Source code -webpack03] Handwriting webpack - compiler Simple compilation process ](https://juejin.im/post/684490...)
[[ Source code ] Redux React-Redux01](https://juejin.im/post/684490...)
[[ Source code ] axios ](https://juejin.im/post/684490...)
[[ Source code ] vuex ](https://juejin.im/post/684490...)
[[ Source code -vue01] data Response type and Initialize rendering ](https://juejin.im/post/684490...)
[[ Source code -vue02] computed Response type - initialization , visit , The update process ](https://juejin.im/post/684490...)
[[ Source code -vue03] watch Listening properties - Initialization and update ](https://juejin.im/post/684490...)
[[ Source code -vue04] Vue.set and vm.$set ](https://juejin.im/post/684490...)
[[ Source code -vue05] Vue.extend ](https://juejin.im/post/684490...)
[[ Source code -vue06] Vue.nextTick and vm.$nextTick ](https://juejin.im/post/684790...)
Pre knowledge
Learning goals
computed Calculation properties are only available in computed When interviewed , To calculate
- Because in new Watcher yes computed watcher when , namely lazy=true when , There is no immediate execution in the constructor get() Method , Instead, it is triggered when the calculated property is accessed computed In response get after , Executive get Method computed getter function
computed The calculated attribute has caching function
- adopt dirty=true when , To execute watcher.evaluate() Method , Will execute computed wawtcher Medium get() namely computd Defined function , Calculate again , After calculation , take this.dirty=false
- On the next visit , Will judge first dirty, yes false Just return the cached value directly
- computed The dependency must be responsive data , Otherwise, even dependency changes will not trigger computed Recalculate
- Even if computed Our dependence has changed , however computed If the calculated value does not change , No new rendering
computed watcher and render watcher and dep and dep.subs and watcher.deps A complex relationship between
computed Access to the process
- visit computed, Trigger computed Responsive get function ,get Function to determine if dirty=true, Then perform computed watchet Of evalute Method , namely watcher Medium get() Method , And then put Dep.target = computed watcher, perform computed getter The function is user specified computed function
- perform computed getter The time of the function , Because there are dependent, responsive data, So it triggers data Of get function , perform dep.depend(), Is to put computed watcher Medium newDeps Add computed Dependent dep, Simultaneous direction computed Dependent dep Of subs Add computed watcher, Then I put Dep.target = targetStack The previous in the array watcher, Then return the result of the calculation , And the dirty=false
- And then determine Dep.taret There is , Is executed computed watcher Of depend Method , Loop traversal computed watcher Medium deps, Take out dep, perform dep.depend
- Because the Dep.target = render watcher, therefore dep.depend Will send to render watcher Of newDeps Add data Of dep, towards data Of dep Medium subs Add render watcher, So the computed Calculate the dependency of the property dep Medium subs Namely [computed watcher, render watcher] This ensures that the rendering time ,computed Precede render Execute first , Guarantee computed Valuable
comuted The update process
- In the example above ,change The process of , It's a change computed Dependence , Then a series of processes
- Trigger computed Responsive dependent dep.notify() Method , Loop through the dependent dep.subs Every one of the arrays watcher.update() Method , In the example above subs=[computed watcher, render watcher] So first execute computed watcher
compued watcher
- stay update In the method , Will judge lazy=true?, because computed watcher Of lazy=true, So execute dirty=true
render watcher
- stay update In the method , lazy Not for true, Will execute queueWatcher(this), Is to call rendering watcher Re evaluate and render
Some words
internal: Inside
Scheduler: Scheduler
queue: queue
( flushSchedulerQueue : Refresh scheduler queue )
computed Source code
(1) computed The initialization
Vue.prototype._init
=>initState(vm)
=>initComputed(vm, opts.computed)
(1-1) initComputed(vm, opts.computed)
I have mainly done the following things
<font color=blue size=5>(1-1-1) new Watcher(vm, getter, noop, computedWatcherOptions) </font>
- new One. computed watcher
Parameters
computedWatcherOptions
- How to know is a computed watcher => Mainly through article 4 Parameters computedWatcherOptions => { lazy: true }, namely comoputed watcher Of lazy The attribute is true
getter
- It is defined by the user computed Function in object
noop
- It's a null function
Be careful :
stay new Watcher Compute properties watcher In the constructor of
this.value=this.lazy ? undefined : this.get()
- Because computing properties watcher Of lazy=true, So it won't be executed immediately get() Method
<font color=blue> When will it be implemented get() Well ?</font>
- <font color=blue> The timing of execution is template I visited computed</font>, because computed The response expression is also defined , Visited computed Property will execute computed Of get Method , stay get Method
watcher.evaluate()
Method , In it is to performget()
, To calculate computed Result
- <font color=blue> The timing of execution is template I visited computed</font>, because computed The response expression is also defined , Visited computed Property will execute computed Of get Method , stay get Method
<font color=blue size=5>(1-1-2) hold computd Defined as responsive </font>
defineComputed(vm, key, userDef)
=>Object.defineProperty(target, key, sharedPropertyDefinition)
=>sharedPropertyDefinition.get
=>createComputedGetter(key)
=>computedGetter
- That is to say, visit computed Medium
this.xxxx
They will executecomputedGetter
function
<font color=blue size=5>(1-1-3) computedGetter - This method is very important !!!!!!!!!!!!!!!!!!!!!</font>
watcher.evaluate() Perform calculation of properties watcher Medium evaluate Method
- When dirty=true, also watcher In existence , Will execute computed watcher Of evalute Method
<font color=blue>evalute</font> Method will execute <font color=blue>get()</font> Method , And will <font color=blue>this.dirty</font> Change it to <font color=blue>false</font>
<font color=blue>get()</font>
<font color=blue>pushTarget(this)</font>
- <font color=blue> towards targetStack Array push One computed watcher</font>
- <font color=blue> take Dep.target Designated as computed watcher</font>
Execute user in computed Methods defined in objects , namely getter Method newName
- such as
computed: {newName() {return this.name + 'new' }}
Be careful :
- In the process, it will trigger data The influence formula of the object , namely this.name Trigger response data Medium get function , Because I visited data Of name attribute
- data,computed Each has its own response
here data The response will collect the information of the calculated attributes watcher, This will be sorted out in the later access process of calculation properties
Mainly
- towards computed watcher Of newDeps Add render watcher Of dep
- towards render watcher Of dependent properties dep Of subs Add computed watcher
- See below
- such as
watcher.depend() Perform calculation of properties watcher Of depend Method
- Put it below and analyze it together with the access process
- Source code
- 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) // Statement watchers and _computedWatchers For an empty object const isSSR = isServerRendering() // Whether it is ssr Environmental Science for (const key in computed) { const userDef = computed[key] // userDef yes computed Of getter function const getter = typeof userDef === 'function' ? userDef : userDef.get // getter // computed getter It could be a function perhaps have get Object of method // This is generally a function , And the function needs return A value if (process.env.NODE_ENV !== 'production' && getter == null) { warn( `Getter is missing for computed property "${key}".`, vm ) } // If it is not a function or object, it will report a warning if (!isSSR) { // create internal watcher for the computed property. // Not ssr Environmental Science , Browser environment , Just create a new one computed watcher // computed watcher // computedWatcherOptions = { lazy: true } // getter = User defined computed Function in object watchers[key] = new Watcher( vm, getter || noop, // User defined computed Function in object , namely 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. // stay vue.extends and new Vue() In the process, the responsive computed if (!(key in vm)) { defineComputed(vm, key, userDef) // defineComputed take computed Become responsive } else if (process.env.NODE_ENV !== 'production') { // Deal with duplicate names , stay props,data,computed You can't use the same name 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 If in the browser environment, it is true if (typeof userDef === 'function') { sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) // Definition computed When interviewed , The trigger get : createGetterInvoker(userDef) sharedPropertyDefinition.set = noop } else { // userDef No function, We just ignore 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) // Define responsive computed // 1. When passed this.xxxx visit computed, It will trigger sharedPropertyDefinition Object get // 2. get It's actually below createComputedGetter Back to computedGetter function }
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] // Take out each computed watcher if (watcher) { if (watcher.dirty) { // watcher.dirty // 1. Default initialization ,comoputed watcher Of dirty=true // 2. When dirty=true Will execute watcher.evaluate() // 3. watcher.evaluate() After the execution , dirty=false // summary : dirty=true => watcher.evaluate() => dirty=false watcher.evaluate() // watcher.evaluate() // 1. Will execute computed watcher Medium get() // pushTarget(this) // 1. take computed watcher Add to targetStack Array // 2. take Dep.target = computed watcher // perform this.getter.call(vm, vm) That is, user-defined computed Methods in objects // 1. Columns such as : computed: {newName() {return this.name + 'new' }} // 2. because :computed Of newName In the method , Rely on data Medium this.name, Access to this.name It will trigger data Responsive get Method // 3. therefore :ata Responsive get Method execution process is as follows // Got it this.name Value // here ,Dep.target yes computed watcher // And then execute this.name Object's dep Class depend Method for dependency collection // towards computed watcher Of newDeps Add render watcher Of dep // towards render watcher Of subs Add computed watcher // popTarget() // 1. targetStack.pop() take computed watcher from targetStack Delete in array // 2. And will Dep.target Specify as the previous... In the array watcher, No, just undefined // 2. take 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 Access process
Case study
<!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>
- stay
vm._update(vm._render(), hydrating)
In the process , When in template The template uses computed Object key When , It will trigger computed Of responsive objects get Method - computed In response get The way is computedGetter Method , In this method , Will judge wathcer and watcher.dirty Whether there is , The proof of existence is computed watcher, Will execute computed wathcer Of watcher.evaluate() Method
- watcher.evaluate() Method will be executed computed watcher Medium get() Method , And will this.dirty Change it to false
watcher.evaluate()
get() Method
call <font color=red>pushTarget(this)</font>
- <font color=red> towards targetStack Array push One computed watcher</font>
- <font color=red> take Dep.target Designated as computed watcher</font>
And then call computed getter Method , That is, user-defined computed Methods in objects
- such as :
computed: {newName() {return this.name + 'new' }}
- because :computed Of newName In the method , Rely on data Medium this.name, Access to this.name It will trigger data Responsive get Method
therefore :<font color=DarkOrChid>data Responsive get Method execution process is as follows :</font>
- Got it this.name Value
here ,Dep.target yes computed watcher
And then execute this.name Corresponding dep Class depend Method for dependency collection
- <font color=DarkOrChid> towards computed watcher Of newDeps Add render watcher It's the same as data Attribute dep</font>
- <font color=DarkOrChid> towards render watcher Corresponding data Attribute dep Example of subs Add computed watcher</font>
- <font color=DarkOrChid> Equal to say data Of this.name and computed Watcher Have the same dep example </font>
After performing the above steps dep.subs and computed watcher.newDeps The state of is
this.name Corresponding dep In the instance subs = [computedWatcher]
computed watcher Medium newDeps = [ above this.name Corresponding dep]
- return name Value
- such as :
call <font color=red>popTarget()</font>
- <font color=red>targetStack.pop() take computed watcher from targetStack Delete in array </font>
<font color=red> And will Dep.target Specify as the previous... In the array watcher</font>
- <font color=red>Dep.target = render watcher</font>
- In the example above targetStack Array is executing computed Of getter Method has two members
- first :render watcher
- the second :computed watcher
- pop Then there's one left render watcher
- Finally back to computed The calculated result value
watcher.depend()
- When Dep.target In existence , Will execute watcher.depend()
- It's done ,<font color=DarkOrChid>Dep.target = render watcher</font>
watcher.depend()
- And then execute <font color=DarkOrChid>compute watcher Medium watcher.depend() Method </font>
then ,<font color=DarkOrChid> From back to front , Take out one by one computed watcher in deps Array dep, perform dep.depend()</font>
- Be careful : above computed watcher Medium deps Medium dep, Namely this.name Object's dep, Inside subs There is only one in the array computedWatcher
- contrast : stay data When the properties of an object are accessed , Will perform data Of the corresponding property dep.depend()
<font color=DarkOrChid>Dep.target.addDep(this)</font>
- <font color=DarkOrChid> At this time Dep.targt yes render watcher</font>, because popTarget() operation pop Out computed watcher Then there's only one left render watcher 了
addDep front
- render watcher Medium deps It's an empty array
- render watcher Medium newDeps It's an empty array
addDep There are two main things to do
- <font color=DarkOrChid> towards render watcher Of newDeps Add The render watcher Corresponding datad Attribute dep</font>
- <font color=DarkOrChid> towards render watcher Corresponding data Property corresponds to dep Class subs Add render watcher</font>
After adding ,data Of dep.subs = [computed watcher, render watcher]
- <font color=red> So when this.name The corresponding... Is triggered after the property is modified set function , It will trigger dep.notify, Then the cycle sub Medium watcher, perform watcher.update() Method </font>
- <font color=red>[computed watcher, render watcher] This order ensures that in render When ,computed It must be worth it </font>
(3) computed The process of updating
- In the example above ,change The process of , It's a change computed Dependence , Then a series of processes
- Trigger computed Responsive dependent dep.notify() Method , Loop through the dependent dep.subs Every one of the arrays watcher.update() Method , In the example above subs=[computed watcher, render watcher] So first execute computed watcher
compued watcher
- stay update In the method , Will judge lazy=true?, because computed watcher Of lazy=true, So execute dirty=true
render watcher
- stay update In the method , lazy Not for true, Will execute queueWatcher(this), Is to call rendering watcher Re evaluate and render
Information
Actual case - Avoid pit https://juejin.im/post/684490...
detailed https://juejin.im/post/684490...
Source version https://juejin.im/post/684490...
computed watcher https://juejin.im/post/684490...