When learning becomes a habit, knowledge becomes common sense. Thank you for your likes, collections and comments.
The new video and articles will be sent to WeChat official account for the first time. Li Yongning
The article has been included in github warehouse liyongning/blog , welcome to Watch and Star.
preface
Last article Interpretation of Vue source code (2) -- Vue initialization process Explain the initialization process of Vue in detail and understand what new Vue(options) does. The implementation of data response is briefly introduced in one sentence, and this article will explain the implementation principle of Vue data response in detail.
target
- Deeply understand the principle of Vue data response.
- What is the difference between methods, computed and watch?
Source code interpretation
After learning from the previous article, I believe we all know the entry position of the source code reading of the principle of responsiveness, which is the step of processing data responsiveness in the initialization process, that is, calling the initState method in / SRC / core / instance / init JS file.
initState
/src/core/instance/state.js
/** * Two things: * Data responsive entry: process props, methods, data, computed, and watch respectively * Priority: the attributes in props, methods, data and computed objects cannot be duplicated, and the priority is consistent with the listing order * The key in computed cannot be the same as the key in props and data, and the methods do not affect it */ export function initState (vm: Component) { vm._watchers = [] const opts = vm.$options // Process props object, set response formula for each property of props object, and proxy it to vm instance if (opts.props) initProps(vm, opts.props) // Process the methods object, check whether the value of each attribute is a function, compare it with the props attribute, and judge it again. Finally, vm[key] = methods[key] if (opts.methods) initMethods(vm, opts.methods) /** * Did three things * 1,The attributes on the data object cannot be the same as those on the props and methods objects * 2,Proxy attributes on data objects to vm instances * 3,Set the response expression for the data on the data object */ if (opts.data) { initData(vm) } else { observe(vm._data = {}, true /* asRootData */) } /** * Three things: * 1,Create a watcher instance for computed[key]. The default is lazy execution * 2,Proxy computed[key] to vm instance * 3,The key in calculated cannot duplicate the attribute in data and props */ if (opts.computed) initComputed(vm, opts.computed) /** * Three things: * 1,Processing the watch object * 2,For each watch Key creates a watcher instance. The relationship between key and watcher instance may be one to many * 3,If immediate is set, the callback function is executed immediately */ if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) } /** * In fact, it can also be seen here that there is no difference between calculated and watch in essence. They are both responsive through the watcher * If there is any difference, it is only the difference in the way of use. To put it simply: * 1,watch: It is applicable to asynchronous or expensive operations when data changes, that is, operations that need to wait for a long time can be put in the watch * 2,computed: Asynchronous methods can be used, but they don't make any sense. Therefore, computed is more suitable for some synchronous calculations */ }
initProps
src/core/instance/state.js
// Process props object, set response formula for each property of props object, and proxy it to vm instance function initProps (vm: Component, propsOptions: Object) { const propsData = vm.$options.propsData || {} const props = vm._props = {} // Cache each key of props and optimize performance // cache prop keys so that future props updates can iterate using Array // instead of dynamic object key enumeration. const keys = vm.$options._propKeys = [] const isRoot = !vm.$parent // root instance props should be converted if (!isRoot) { toggleObserving(false) } // Traversal props object for (const key in propsOptions) { // Cache key keys.push(key) // Get the default value of props[key] const value = validateProp(key, propsOptions, propsData, vm) // Set the data response for each key of props defineReactive(props, key, value) // static props are already proxied on the component's prototype // during Vue.extend(). We only need to proxy props defined at // instantiation here. if (!(key in vm)) { // Proxy key to vm object proxy(vm, `_props`, key) } } toggleObserving(true) }
proxy
/src/core/instance/state.js
// Set the proxy and proxy the key to the target export function proxy (target: Object, sourceKey: string, key: string) { sharedPropertyDefinition.get = function proxyGetter () { return this[sourceKey][key] } sharedPropertyDefinition.set = function proxySetter (val) { this[sourceKey][key] = val } Object.defineProperty(target, key, sharedPropertyDefinition) }
initMethods
/src/core/instance/state.js
/** * Did the following three things, in fact, the most critical thing is the third thing * 1,Verify that the metadata [key] must be a function * 2,Weight judgment * methods The key in cannot be the same as the key in props * methos The key in overlaps with the existing methods on the Vue instance. It is generally some built-in methods, such as $and_ Starting method * 3,Put methods[key] on the vm instance and get vm[key] = methods[key] */ function initMethods (vm: Component, methods: Object) { // Get props configuration item const props = vm.$options.props // Traversal methods object for (const key in methods) { if (process.env.NODE_ENV !== 'production') { if (typeof methods[key] !== 'function') { warn( `Method "${key}" has type "${typeof methods[key]}" in the component definition. ` + `Did you reference the function correctly?`, vm ) } if (props && hasOwn(props, key)) { warn( `Method "${key}" has already been defined as a prop.`, vm ) } if ((key in vm) && isReserved(key)) { warn( `Method "${key}" conflicts with an existing Vue instance method. ` + `Avoid defining component methods that start with _ or $.` ) } } vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm) } }
initData
src/core/instance/state.js
/** * Did three things * 1,The attributes on the data object cannot be the same as those on the props and methods objects * 2,Proxy attributes on data objects to vm instances * 3,Set the response expression for the data on the data object */ function initData (vm: Component) { // Get data object let data = vm.$options.data data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {} if (!isPlainObject(data)) { data = {} process.env.NODE_ENV !== 'production' && warn( 'data functions should return an object:\n' + 'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function', vm ) } /** * Two things * 1,The attributes on the data object cannot be the same as those on the props and methods objects * 2,Proxy attributes on data objects to vm instances */ const keys = Object.keys(data) const props = vm.$options.props const methods = vm.$options.methods let i = keys.length while (i--) { const key = keys[i] if (process.env.NODE_ENV !== 'production') { if (methods && hasOwn(methods, key)) { warn( `Method "${key}" has already been defined as a data property.`, vm ) } } if (props && hasOwn(props, key)) { process.env.NODE_ENV !== 'production' && warn( `The data property "${key}" is already declared as a prop. ` + `Use prop default value instead.`, vm ) } else if (!isReserved(key)) { proxy(vm, `_data`, key) } } // Set the response expression for the data on the data object observe(data, true /* asRootData */) } export function getData (data: Function, vm: Component): any { // #7573 disable dep collection when invoking data getters pushTarget() try { return data.call(vm, vm) } catch (e) { handleError(e, vm, `data()`) return {} } finally { popTarget() } }
initComputed
/src/core/instance/state.js
const computedWatcherOptions = { lazy: true } /** * Three things: * 1,Create a watcher instance for computed[key]. The default is lazy execution * 2,Proxy computed[key] to vm instance * 3,The key in calculated cannot duplicate the attribute in data and props * @param {*} computed = { * key1: function() { return xx }, * key2: { * get: function() { return xx }, * set: function(val) {} * } * } */ function initComputed (vm: Component, computed: Object) { // $flow-disable-line const watchers = vm._computedWatchers = Object.create(null) // computed properties are just getters during SSR const isSSR = isServerRendering() // Traversing the computed object for (const key in computed) { // Get the value corresponding to key, that is, getter function const userDef = computed[key] 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) { // Create a watcher instance for the computed property watchers[key] = new Watcher( vm, getter || noop, noop, // Configuration item. computed is lazy by default computedWatcherOptions ) } if (!(key in vm)) { // Proxy the properties in the computed object to the vm instance // This allows you to use VM Computedkey accesses the calculation property defineComputed(vm, key, userDef) } else if (process.env.NODE_ENV !== 'production') { // The non production environment has a re judgment process. The attributes in the calculated object cannot be the same as those in data and props 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) } } } } /** * Proxy the key in the computed object to the target (vm) */ export function defineComputed ( target: any, key: string, userDef: Object | Function ) { const shouldCache = !isServerRendering() // Construct attribute descriptors (get, set) if (typeof userDef === 'function') { sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) : createGetterInvoker(userDef) sharedPropertyDefinition.set = noop } else { 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 ) } } // Intercept target Key access and settings Object.defineProperty(target, key, sharedPropertyDefinition) } /** * @returns Returns a function that accesses VM Computedproperty is executed and the execution result is returned */ function createComputedGetter (key) { // The principle that the calculated attribute value will be cached is also combined with the watcher dirty,watcher.evalaute,watcher.update implementation return function computedGetter () { // Get the watcher corresponding to the current key const watcher = this._computedWatchers && this._computedWatchers[key] if (watcher) { // Calculate the value corresponding to the key by executing calculated Key callback function // watcher. The dirty attribute is the principle that the computed calculation result will be cached // <template> // <div>{{ computedProperty }}</div> // <div>{{ computedProperty }}</div> // </template> // In this case, in one rendering of the page, there is only the first computedProperty in the two DOMS // Will execute computed The callback function of computedproperty calculates the actual value, // Execute the watcher Evalaute, and the second one doesn't go through the calculation process, // Because the last time I executed watcher When evaluating, put the watcher Set dirty to false, // After the page is updated, wathcer The update method will put the watcher Dirty reset to true, // For recalculating the calculated. When the next page is updated Key results if (watcher.dirty) { watcher.evaluate() } if (Dep.target) { watcher.depend() } return watcher.value } } } /** * Same function as getecomputer */ function createGetterInvoker(fn) { return function computedGetter () { return fn.call(this, this) } }
initWatch
/src/core/instance/state.js
/** * Two things are done to handle the entry of the watch object: * 1,Traversing the watch object * 2,Call the createWatcher function * @param {*} watch = { * 'key1': function(val, oldVal) {}, * 'key2': 'this.methodName', * 'key3': { * handler: function(val, oldVal) {}, * deep: true * }, * 'key4': [ * 'this.methodNanme', * function handler1() {}, * { * handler: function() {}, * immediate: true * } * ], * 'key.key5' { ... } * } */ function initWatch (vm: Component, watch: Object) { // Traversing the watch object for (const key in watch) { const handler = watch[key] if (Array.isArray(handler)) { // handler is an array, traversing the array, getting every item in it, and then calling createWatcher. for (let i = 0; i < handler.length; i++) { createWatcher(vm, key, handler[i]) } } else { createWatcher(vm, key, handler) } } } /** * Two things: * 1,Compatibility processing to ensure that the handler must be a function * 2,Call $watch * @returns */ function createWatcher ( vm: Component, expOrFn: string | Function, handler: any, options?: Object ) { // If the handler is an object, get the value of the handler option if (isPlainObject(handler)) { options = handler handler = handler.handler } // If the handler is a string, it means a methods method to get vm[handler] if (typeof handler === 'string') { handler = vm[handler] } return vm.$watch(expOrFn, handler, options) } /** * Create a watcher and return to unwatch. Complete the following five things: * 1,Compatibility processing to ensure that cb in the last new Watcher is a function * 2,Mark user watcher * 3,Create a watcher instance * 4,If immediate is set, cb is executed immediately * 5,Return to unwatch * @param {*} expOrFn key * @param {*} cb Callback function * @param {*} options Configuration item, the user directly calls this$ A configuration item may be passed when watching * @returns Returns the unwatch function, which is used to cancel watch listening */ Vue.prototype.$watch = function ( expOrFn: string | Function, cb: any, options?: Object ): Function { const vm: Component = this // Compatibility processing because the user calls VM$ The cb set during watch may be an object if (isPlainObject(cb)) { return createWatcher(vm, expOrFn, cb, options) } // options.user refers to the user watcher, as well as the render watcher, that is, the watcher instantiated in the updateComponent method options = options || {} options.user = true // Create a watcher const watcher = new Watcher(vm, expOrFn, cb, options) // If the user sets immediate to true, the callback function is executed immediately if (options.immediate) { try { cb.call(vm, watcher.value) } catch (error) { handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`) } } // Returns an unwatch function, which is used to cancel listening return function unwatchFn () { watcher.teardown() } }
observe
/src/core/observer/index.js
/** * The real entrance to responsive processing * Create an observer instance for the object. If the object has been observed, return the existing observer instance. Otherwise, create a new observer instance * @param {*} value Object = > {} */ export function observe (value: any, asRootData: ?boolean): Observer | void { // Non object and VNode instances are not processed in response if (!isObject(value) || value instanceof VNode) { return } let ob: Observer | void if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { // If the value object exists__ ob__ Property, it indicates that observation has been made, and it is returned directly__ ob__ attribute ob = value.__ob__ } else if ( shouldObserve && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue ) { // Create observer instance ob = new Observer(value) } if (asRootData && ob) { ob.vmCount++ } return ob }
Observer
/src/core/observer/index.js
/** * The Observer class will be attached to each observed object, value__ ob__ = this * The properties of the object are converted into getters / setters, and dependency and notification updates are collected */ export class Observer { value: any; dep: Dep; vmCount: number; // number of vms that have this object as root $data constructor (value: any) { this.value = value // A dep instance this.dep = new Dep() this.vmCount = 0 // Set on value object__ ob__ attribute def(value, '__ob__', this) if (Array.isArray(value)) { /** * value As array * hasProto = '__proto__' in {} * Used to determine whether the object exists__ proto__ Property through obj__ proto__ Prototype chain of objects that can be accessed * But because__ proto__ It is not a standard attribute, so some browsers do not support it, such as IE6-10 and Opera 10 one * Why should we judge? It's because we have to pass it later__ proto__ Prototype chain of operational data * Override the default seven prototype methods of the array to realize the array response */ if (hasProto) { // Yes__ proto__ protoAugment(value, arrayMethods) } else { copyAugment(value, arrayMethods, arrayKeys) } this.observeArray(value) } else { // value is the object and sets the response expression for each attribute of the object (including nested objects) this.walk(value) } } /** * Traverse each key on the object and set the response formula for each key * This is only possible if the value is an object */ walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]) } } /** * Traverse the array, set observation for each item of the array, and handle the case that the array element is an object */ observeArray (items: Array<any>) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } } }
defineReactive
/src/core/observer/index.js
/** * Intercept reading and setting operations of obj[key]: * 1,Collect dependencies during the first reading. For example, there will be a read operation when the render function is executed to generate a virtual DOM * 2,Set new values when updating and notify dependent updates */ export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) { // Instantiate DEP, one key and one dep const dep = new Dep() // Get the attribute descriptor of obj[key] and return it directly if it is found to be a non configurable object const property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { return } // Record getter and setter and get val value const getter = property && property.get const setter = property && property.set if ((!getter || setter) && arguments.length === 2) { val = obj[key] } // Recursive call to handle the case where the value of val, i.e. obj[key], is an object, so as to ensure that all keys in the object are observed let childOb = !shallow && observe(val) // Responsive core Object.defineProperty(obj, key, { enumerable: true, configurable: true, // get intercepts the reading operation of obj[key] get: function reactiveGetter () { const value = getter ? getter.call(obj) : val /** * Dep.target It is a static attribute of Dep class with the value of Watcher, which will be set when instantiating Watcher * When instantiating watcher, the callback function passed by new Watcher will be executed (except computed, because it is lazy to execute) * If there is VM in the callback function The read behavior of key will trigger the read interception here for dependency collection * After the callback function is executed, it will set Dep.target to null to avoid repeated collection of dependencies here */ if (Dep.target) { // Depending on the collection, add a watcher in dep and DEP in the watcher dep.depend() // childOb refers to the observer object of the nested object in the object. If it exists, it is also subject to dependency collection if (childOb) { // This is this key. The reason why the responsive update can be triggered when the chidlkey is updated childOb.dep.depend() // If obj[key] is an array, the array response is triggered if (Array.isArray(value)) { // Add dependencies to the items of an array object dependArray(value) } } } return value }, // set intercepts the setting of obj[key] set: function reactiveSetter (newVal) { // Old obj[key] const value = getter ? getter.call(obj) : val // If the new and old values are the same, return directly. If they are not the same as the new values, the reactive update process will not be triggered /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } // The absence of setter indicates that this property is read-only. return directly // #7981: for accessor properties without setter if (getter && !setter) return // Set new value if (setter) { setter.call(obj, newVal) } else { val = newVal } // Observe the new value so that the new value is also responsive childOb = !shallow && observe(newVal) // Dependency notification update dep.notify() } }) }
dependArray
/src/core/observer/index.js
/** * Traverse each array element, recursively handle the case where the array item is an object, and add dependencies for it * Because the previous recursion phase cannot add dependencies to the object elements in the array */ function dependArray (value: Array<any>) { for (let e, i = 0, l = value.length; i < l; i++) { e = value[i] e && e.__ob__ && e.__ob__.dep.depend() if (Array.isArray(e)) { dependArray(e) } } }
Array response
src/core/observer/array.js
/** * Defines the arrayMethods object, which is used to enhance array prototype * When accessing the seven methods on the arrayMethods object, it will be intercepted to implement the array response */ import { def } from '../util/index' // Backup array prototype object const arrayProto = Array.prototype // Create new arrayMethods by inheritance export const arrayMethods = Object.create(arrayProto) // There are seven methods to manipulate the array, which can change the array itself const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] /** * Intercept mutation methods and trigger events */ methodsToPatch.forEach(function (method) { // cache original method // Cache native methods, such as push const original = arrayProto[method] // def is object Defineproperty, intercepting arraymethods Method access def(arrayMethods, method, function mutator (...args) { // Execute native methods first, such as push apply(this, args) const result = original.apply(this, args) const ob = this.__ob__ // If the method is one of the following three, the element is newly inserted let inserted switch (method) { case 'push': case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } // Respond to newly inserted elements if (inserted) ob.observeArray(inserted) // Notification update ob.dep.notify() return result }) })
def
/src/core/util/lang.js
/** * Define a property. */ export function def (obj: Object, key: string, val: any, enumerable?: boolean) { Object.defineProperty(obj, key, { value: val, enumerable: !!enumerable, writable: true, configurable: true }) }
protoAugment
/src/core/observer/index.js
/** * Set target__ proto__ The prototype object of is src * For example, array objects, arr__ proto__ = arrayMethods */ function protoAugment (target, src: Object) { /* eslint-disable no-proto */ target.__proto__ = src /* eslint-enable no-proto */ }
copyAugment
/src/core/observer/index.js
/** * Defines the specified attribute on the target object * For example, array: define the seven methods for the array object */ function copyAugment (target: Object, src: Object, keys: Array<string>) { for (let i = 0, l = keys.length; i < l; i++) { const key = keys[i] def(target, key, src[key]) } }
Dep
/src/core/observer/dep.js
import type Watcher from './watcher' import { remove } from '../util/index' import config from '../config' let uid = 0 /** * A dep corresponds to an obj key * When reading responsive data, it is responsible for collecting dependencies. What are the watcher s that each dep (or obj.key) depends on * When the responsive data is updated, it is responsible for notifying those watcher s in the dep to execute the update method */ export default class Dep { static target: ?Watcher; id: number; subs: Array<Watcher>; constructor () { this.id = uid++ this.subs = [] } // Add a watcher in dep addSub (sub: Watcher) { this.subs.push(sub) } removeSub (sub: Watcher) { remove(this.subs, sub) } // Like adding dep to watcher depend () { if (Dep.target) { Dep.target.addDep(this) } } /** * Notify all watchers in dep and execute watcher Update() method */ notify () { // stabilize the subscriber list first const subs = this.subs.slice() if (process.env.NODE_ENV !== 'production' && !config.async) { // subs aren't sorted in scheduler if not running async // we need to sort them now to make sure they fire in correct // order subs.sort((a, b) => a.id - b.id) } // Traverse the watcher stored in dep and execute the watcher update() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } } /** * For the currently executing watcher, only one Watcher will be executing at the same time * Dep.target = Currently executing watcher * Complete the assignment by calling pushTarget method and reset by calling popTarget method (null) */ Dep.target = null const targetStack = [] // Called when dependency collection is needed, set Dep.target = watcher export function pushTarget (target: ?Watcher) { targetStack.push(target) Dep.target = target } // Dependency collection end call, set Dep.target = null export function popTarget () { targetStack.pop() Dep.target = targetStack[targetStack.length - 1] }
Watcher
/src/core/observer/watcher.js
/** * A component a watcher (render watcher) or an expression a watcher (user watcher) * When the data is updated, the watcher will be triggered to access this The watcher will also be triggered when computedproperty */ 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 = function() { return this.xx } // In this Execute this in get Dependency collection will be triggered when getter // To be followed up When XX is updated, a response will be triggered 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() } /** * Execute this Getter and collect dependencies again * this.getter It is the second parameter passed when instantiating the watcher, a function or string, such as the read this returned by updateComponent or parsePath Function of XX attribute value * Why collect dependencies again? * Because triggering the update indicates that the responsive data has been updated, but the updated data has been observe d, but there is no dependency collection, * Therefore, when updating the page, the render function will be executed again, and the read operation will be triggered during execution. At this time, dependency collection will be carried out */ get () { // Open Dep.target, Dep.target = this pushTarget(this) // value is the result of the callback function execution let value const vm = this.vm try { // Execute the callback function, such as updateComponent, and enter the patch phase 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) } // Close Dep.target, Dep.target = null popTarget() this.cleanupDeps() } return value } /** * Add a dependency to this directive. * Two things: * 1,Add dep to yourself (watcher) * 2,Add a watcher to dep */ addDep (dep: Dep) { // If dep already exists, it will not be added repeatedly const id = dep.id if (!this.newDepIds.has(id)) { // Cache dep.id for duplicate judgment this.newDepIds.add(id) // Add dep this.newDeps.push(dep) // Avoid adding watcher, this. Repeatedly in dep Depids is set in the cleanupDeps method if (!this.depIds.has(id)) { // Add watcher to dep 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 } /** * According to the watcher configuration item, decide how to go next, usually queueWatcher */ update () { /* istanbul ignore else */ if (this.lazy) { // Go here when lazy execution, such as computed // Setting dirty to true allows the computedGetter to recalculate the execution result of the computed callback function when executing this.dirty = true } else if (this.sync) { // Synchronous execution, using VM$ You can pass a sync option when using the watch or watch option, // When true, the watcher will not go to the asynchronous update queue and directly execute this run // Method // This attribute does not appear in the official document this.run() } else { // When updating, it is usually here to put the watcher into the watcher queue queueWatcher(this) } } /** * The refresh queue function flushSchedulerQueue is called to complete the following: * 1,Execute the second parameter passed by instantiating the watcher, updateComponent, or get this A function of XX (the function returned by parsePath) * 2,Update old value to new value * 3,Execute the third parameter passed when instantiating the watcher, such as the callback function of the user watcher */ run () { if (this.active) { // Call this Get method 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 ) { // Update old value to new value const oldValue = this.value this.value = value if (this.user) { // If it is the user watcher, the callback function, the third parameter passed by the user, will be executed. The parameters are val and oldVal try { this.cb.call(this.vm, value, oldValue) } catch (e) { handleError(e, this.vm, `callback for watcher "${this.expression}"`) } } else { // Render watcher, this CB = NOOP, an empty function this.cb.call(this.vm, value, oldValue) } } } } /** * The lazy watcher will call this method * For example: computed, getting VM This method is called when the value of computedproperty is * Then execute this Get, the callback function of the watcher, gets the return value * this.dirty It is set to false, so that the page will only be computed once in this rendering Callback function of key, * This is also one of the differences between computed and methods. It is the principle that computed has a cache * The page will update this Dirty will be reset to true. This step is in this Completed in the update method */ 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 } } }
summary
The interviewer asked: how is the Vue responsive principle implemented?
Answer:
- The core of the response is through object Defineproperty intercepts access to and setting of data
There are two types of responsive data:
Object, loop through all the attributes of the object, and set getters and setters for each attribute to intercept access and set. If the attribute value is still the object, recursively set getters and setters for each key on the attribute value
- When accessing data (obj.key), collect dependencies and store relevant watcher s in dep
- When setting data, dep notifies the relevant watcher to update
Array, the seven enhanced arrays can change their own prototype methods, and then intercept the operations on these methods
- When new data is added, it is processed in a responsive manner, and then dep notifies the watcher to update it
- When deleting data, dep should also notify the watcher to update
The interviewer asked: what is the difference between methods, calculated and watch?
Answer:
<!DOCTYPE html> <html lang="en"> <head> <title>methods,computed,watch What's the difference?</title> </head> <body> <div id="app"> <!-- methods --> <div>{{ returnMsg() }}</div> <div>{{ returnMsg() }}</div> <!-- computed --> <div>{{ getMsg }}</div> <div>{{ getMsg }}</div> </div> <script src="../../dist/vue.js"></script> <script> new Vue({ el: '#app', data: { msg: 'test' }, mounted() { setTimeout(() => { this.msg = 'msg is changed' }, 1000) }, methods: { returnMsg() { console.log('methods: returnMsg') return this.msg } }, computed: { getMsg() { console.log('computed: getMsg') return this.msg + ' hello computed' } }, watch: { msg: function(val, oldVal) { console.log('watch: msg') new Promise(resolve => { setTimeout(() => { this.msg = 'msg is changed by watch' }, 1000) }) } } }) </script> </body> </html>
Click to view the dynamic diagram demonstration , dynamic diagram address: https://p6-juejin.byteimg.com...
The example is actually the answer
Usage scenario
- methods are generally used to encapsulate some complex processing logic (synchronous and asynchronous)
- computed is generally used to encapsulate some simple synchronization logic, return the processed data, and then display it in the template to reduce the weight of the template
- watch is generally used to perform asynchronous or expensive operations when data changes
difference
methods VS computed
Through the example, we can find that if the same methods or computed attribute is used in multiple places in a rendering, the methods will be executed multiple times, while the computed callback function will only be executed once.
By reading the source code, we know that in a rendering, if you access the computed property multiple times, you will only execute the callback function of the computed property for the first time. For other subsequent accesses, you will directly use the first execution result (watcher.value), and the implementation principle of all this is through The control of dirty attribute is implemented. methods, each access is a simple method call (this.xxMethods).
computed VS watch
Through reading the source code, we know that the essence of computed and watch is the same. They are implemented internally through Watcher. In fact, there is no difference. There are only two points to make the difference: 1. The difference in use scenarios; 2. Computed is lazy by default and cannot be changed.
methods VS watch
In fact, there is nothing comparable between methods and watch. They are completely two things. However, in use, some logic in watch can be drawn into methods to improve the readability of the code.
link
- Supporting video, focusing on WeChat official account reply Get "proficient in Vue technology stack source code principle video version":
- Proficient in Vue technology stack source code principle column
- github warehouse liyongning/Vue Welcome, Star
Thank you for your likes, collections and comments. I'll see you next time.
When learning becomes a habit, knowledge becomes common sense. Thank you for your likes, collections and comments.
The new video and articles will be sent to WeChat official account for the first time. Li Yongning
The article has been included in github warehouse liyongning/blog , welcome to Watch and Star.