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 (5) -- global API The implementation principle of each global API of Vue is introduced in detail. This article will introduce the implementation principle of each example method in detail.
target
Deeply understand the implementation principle of the following example method.
- vm.$set
- vm.$delete
- vm.$watch
- vm.$on
- vm.$emit
- vm.$off
- vm.$once
- vm._update
- vm.$forceUpdate
- vm.$destroy
- vm.$nextTick
- vm._render
Source code interpretation
entrance
/src/core/instance/index.js
This file is the entry file of Vue instance, including the definition of Vue constructor and the initialization of each instance method.
// Constructor for Vue function Vue (options) { // Call Vue prototype._ Init method, which is defined in initMixin this._init(options) } // Define Vue prototype._ Init method initMixin(Vue) /** * definition: * Vue.prototype.$data * Vue.prototype.$props * Vue.prototype.$set * Vue.prototype.$delete * Vue.prototype.$watch */ stateMixin(Vue) /** * Define event related methods: * Vue.prototype.$on * Vue.prototype.$once * Vue.prototype.$off * Vue.prototype.$emit */ eventsMixin(Vue) /** * definition: * Vue.prototype._update * Vue.prototype.$forceUpdate * Vue.prototype.$destroy */ lifecycleMixin(Vue) /** * Execute installRenderHelpers in Vue Install the runtime convenience program on the prototype object * * definition: * Vue.prototype.$nextTick * Vue.prototype._render */ renderMixin(Vue)
vm.$data,vm.$props
src/core/instance/state.js
These are two instance properties, not instance methods. Here is a brief introduction. Of course, its implementation is also very simple
// data const dataDef = {} dataDef.get = function () { return this._data } // props const propsDef = {} propsDef.get = function () { return this._props } // Mount the data attribute and props attribute to Vue On the prototype object // In this way, you can use this$ Data and this$ Props to access data and props objects Object.defineProperty(Vue.prototype, '$data', dataDef) Object.defineProperty(Vue.prototype, '$props', propsDef)
vm.$set
/src/core/instance/state.js
Vue.prototype.$set = set
set
/src/core/observer/index.js
/** * Via Vue Or set this$ The set method sets the value val for the specified key of the target * If the target is an object and the key does not exist, set the response for the new key, and then execute the dependency notification */ export function set (target: Array<any> | Object, key: any, val: any): any { if (process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target)) ) { warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`) } // Update the elements of the array with the specified subscript, Vue Set (array, IDX, Val), and realize the responsive update through the splice method if (Array.isArray(target) && isValidArrayIndex(key)) { target.length = Math.max(target.length, key) target.splice(key, 1, val) return val } // Update the existing properties of the object, Vue Set (obj, key, Val) and execute the update if (key in target && !(key in Object.prototype)) { target[key] = val return val } const ob = (target: any).__ob__ // You cannot add dynamic responsive attributes to Vue instances or $data, which is one of the uses of vmCount, // this.$data ob Vmcount = 1, indicating the VM of the root component and other sub components Vmcount is 0 if (target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV !== 'production' && warn( 'Avoid adding reactive properties to a Vue instance or its root $data ' + 'at runtime - declare it upfront in the data option.' ) return val } // target is not a responsive object. The new attribute will be set, but it will not be processed in a responsive manner if (!ob) { target[key] = val return val } // Define a new attribute for the object, set the response through the defineReactive method, and trigger the dependency update defineReactive(ob.value, key, val) ob.dep.notify() return val }
vm.$delete
/src/core/instance/state.js
Vue.prototype.$delete = del
del
/src/core/observer/index.js
/** * Via Vue Delete or VM$ Delete deletes the specified key of the target object * The array is implemented through the splice method, and the object deletes the specified key through the delete operator and executes the dependency notification */ export function del (target: Array<any> | Object, key: any) { if (process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target)) ) { warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`) } // If target is an array, delete the element of the specified subscript through the splice method if (Array.isArray(target) && isValidArrayIndex(key)) { target.splice(key, 1) return } const ob = (target: any).__ob__ // Avoid deleting the properties of Vue instances or the data of $data if (target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV !== 'production' && warn( 'Avoid deleting properties on a Vue instance or its root $data ' + '- just set it to null.' ) return } // If the attribute does not exist, end directly if (!hasOwn(target, key)) { return } // Delete the properties of an object through the delete operator delete target[key] if (!ob) { return } // Execute dependency notification ob.dep.notify() }
vm.$watch
/src/core/instance/state.js
/** * 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() } }
vm.$on
/src/core/instance/events.js
const hookRE = /^hook:/ /** * Listen for custom events on the instance, VM_ event = { eventName: [fn1, ...], ... } * @param {*} event A single event name or an array of multiple event names * @param {*} fn Callback function executed when event is triggered * @returns */ Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component { const vm: Component = this if (Array.isArray(event)) { // Event is an array composed of multiple event names, then traverse these events and call $on recursively in turn for (let i = 0, l = event.length; i < l; i++) { vm.$on(event[i], fn) } } else { // Store the registered events and callbacks in the form of key value pairs in VM_ VM. In event object_ event = { eventName: [fn1, ...] } (vm._events[event] || (vm._events[event] = [])).push(fn) // hookEvent, which provides an opportunity to inject the declaration cycle method into the component instance from the outside // For example, inject additional logic into the mounted method of the component from outside the component // This capability is implemented in combination with the callhook method if (hookRE.test(event)) { vm._hasHookEvent = true } } return vm }
About hookEvent, the next article will introduce it in detail.
vm.$emit
/src/core/instance/events.js
/** * Trigger the specified event on the instance, VM_ event[event] => cbs => loop cbs => cb(args) * @param {*} event Event name * @returns */ Vue.prototype.$emit = function (event: string): Component { const vm: Component = this if (process.env.NODE_ENV !== 'production') { // Convert the event name to smaller const lowerCaseEvent = event.toLowerCase() // This means that HTML attributes are not case sensitive, so you can't use v-on to listen for event names in the form of humps. Instead, you should use event names in the form of hyphens if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) { tip( `Event "${lowerCaseEvent}" is emitted in component ` + `${formatComponentName(vm)} but the handler is registered for "${event}". ` + `Note that HTML attributes are case-insensitive and you cannot use ` + `v-on to listen to camelCase events when using in-DOM templates. ` + `You should probably use "${hyphenate(event)}" instead of "${event}".` ) } } // From VM_ Get the callback function array of the current event from the event object, call the callback function in the array at one time, and pass the provided parameters let cbs = vm._events[event] if (cbs) { cbs = cbs.length > 1 ? toArray(cbs) : cbs const args = toArray(arguments, 1) const info = `event handler for "${event}"` for (let i = 0, l = cbs.length; i < l; i++) { invokeWithErrorHandling(cbs[i], vm, args, vm, info) } } return vm }
vm.$off
/src/core/instance/events.js
/** * Remove the custom event listener, that is, from VM_ Find the corresponding event in the event object, remove all events or remove the callback function of the specified event * @param {*} event * @param {*} fn * @returns */ Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component { const vm: Component = this // vm.$off() removes all listeners on the instance = > VM_ events = {} if (!arguments.length) { vm._events = Object.create(null) return vm } // Remove some events event = [event1,...], Traverse the event array and recursively call VM$ off if (Array.isArray(event)) { for (let i = 0, l = event.length; i < l; i++) { vm.$off(event[i], fn) } return vm } // Except VM$ All except off () will eventually come here and remove the specified event const cbs = vm._events[event] if (!cbs) { // Indicates that the event has not been registered return vm } if (!fn) { // If fn callback function is not provided, remove all callback functions of this event, VM_ event[event] = null vm._events[event] = null return vm } // To remove the specified callback function of a specified event is to find the callback function from the callback array of the event and then delete it let cb let i = cbs.length while (i--) { cb = cbs[i] if (cb === fn || cb.fn === fn) { cbs.splice(i, 1) break } } return vm }
vm.$once
/src/core/instance/events.js
/** * Listen to a custom event, but only trigger it once. Once triggered, the listener is removed * vm.$on + vm.$off * @param {*} event * @param {*} fn * @returns */ Vue.prototype.$once = function (event: string, fn: Function): Component { const vm: Component = this // Call $on, but the callback function of $on is specially handled. When triggered, execute the callback function, remove the event listener, and then execute the callback function you set function on() { vm.$off(event, on) fn.apply(vm, arguments) } on.fn = fn vm.$on(event, on) return vm }
vm._update
/src/core/instance/lifecycle.js
/** * It is responsible for updating the page. The entry location of the first rendering and subsequent updates of the page is also the entry location of the patch */ Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) { const vm: Component = this const prevEl = vm.$el const prevVnode = vm._vnode const restoreActiveInstance = setActiveInstance(vm) vm._vnode = vnode // Vue.prototype.__patch__ is injected in entry points // based on the rendering backend used. if (!prevVnode) { // This is the first rendering, that is, when initializing the page vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */) } else { // When the responsive data is updated, that is, when the page is updated, go here vm.$el = vm.__patch__(prevVnode, vnode) } restoreActiveInstance() // update __vue__ reference if (prevEl) { prevEl.__vue__ = null } if (vm.$el) { vm.$el.__vue__ = vm } // if parent is an HOC, update its $el as well if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) { vm.$parent.$el = vm.$el } // updated hook is called by the scheduler to ensure that children are // updated in a parent's updated hook. }
vm.$forceUpdate
/src/core/instance/lifecycle.js
/** * Call watcher directly Update method to force the component to re render. * It only affects the instance itself and the subcomponents inserted into the slot content, not all subcomponents */ Vue.prototype.$forceUpdate = function () { const vm: Component = this if (vm._watcher) { vm._watcher.update() } }
vm.$destroy
/src/core/instance/lifecycle.js
/** * Completely destroy an instance. Clean up its connection with other instances and unbind all its instructions and event listeners. */ Vue.prototype.$destroy = function () { const vm: Component = this if (vm._isBeingDestroyed) { // Indicates that the instance has been destroyed return } // Call beforeDestroy hook callHook(vm, 'beforeDestroy') // The identity instance has been destroyed vm._isBeingDestroyed = true // Remove yourself from your father ($parent) ($children) const parent = vm.$parent if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) { remove(parent.$children, vm) } // Remove dependent listening if (vm._watcher) { vm._watcher.teardown() } let i = vm._watchers.length while (i--) { vm._watchers[i].teardown() } // remove reference from data ob // frozen object may not have observer. if (vm._data.__ob__) { vm._data.__ob__.vmCount-- } // call the last hook... vm._isDestroyed = true // Call__ patch__, Destroy node vm.__patch__(vm._vnode, null) // Call destroyed hook callHook(vm, 'destroyed') // Turn off all event listening of the instance vm.$off() // remove __vue__ reference if (vm.$el) { vm.$el.__vue__ = null } // release circular reference (#6759) if (vm.$vnode) { vm.$vnode.parent = null } }
vm.$nextTick
/src/core/instance/render.js
Vue.prototype.$nextTick = function (fn: Function) { return nextTick(fn, this) }
nextTick
/src/core/util/next-tick.js
const callbacks = [] /** * Accomplish two things: * 1,Wrap the flushSchedulerQueue function with try catch and put it into the callbacks array * 2,If pending is false, it means that there is no flushCallbacks function in the browser's task queue * If pending is true, it indicates that the flushCallbacks function has been put into the browser's task queue, * When the flushCallbacks function is to be executed, pending will be set to false again, indicating that the next flushCallbacks function can enter * The browser's task queue * pending Function: ensure that there is only one flushCallbacks function in the browser's task queue at the same time * @param {*} cb Receive a callback function = > flushschedulerqueue * @param {*} ctx context * @returns */ export function nextTick (cb?: Function, ctx?: Object) { let _resolve // Use the callbacks array to store the wrapped cb function callbacks.push(() => { if (cb) { // Wrap the callback function with try catch to facilitate error capture try { cb.call(ctx) } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } }) if (!pending) { pending = true // Execute timerFunc and put the flushCallbacks function in the browser's task queue (micro task queue is preferred) timerFunc() } // $flow-disable-line if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } }
vm._render
/src/core/instance/render.js
/** * Generate VNode by executing render function * However, a lot of exception handling code is added */ Vue.prototype._render = function (): VNode { const vm: Component = this const { render, _parentVnode } = vm.$options if (_parentVnode) { vm.$scopedSlots = normalizeScopedSlots( _parentVnode.data.scopedSlots, vm.$slots, vm.$scopedSlots ) } // Set the parent vnode. This allows the rendering function to access the data on the placeholder node. vm.$vnode = _parentVnode // render self let vnode try { currentRenderingInstance = vm // Execute the render function to generate vnode vnode = render.call(vm._renderProxy, vm.$createElement) } catch (e) { handleError(e, vm, `render`) // Here, it means that an error occurred while executing the render function // The development environment renders the error message, and the production environment returns the previous vnode to prevent the rendering error from leading to blank components /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) { try { vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e) } catch (e) { handleError(e, vm, `renderError`) vnode = vm._vnode } } else { vnode = vm._vnode } } finally { currentRenderingInstance = null } // If the returned vnode is an array and contains only one element, it will be flattened directly if (Array.isArray(vnode) && vnode.length === 1) { vnode = vnode[0] } // When the render function makes an error, it returns an empty vnode if (!(vnode instanceof VNode)) { if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) { warn( 'Multiple root nodes returned from render function. Render function ' + 'should return a single root node.', vm ) } vnode = createEmptyVNode() } // set parent vnode.parent = _parentVnode return vnode }
installRenderHelpers
src/core/instance/render-helpers/index.js
This method is responsible for installing a large number of short tool functions related to rendering on the instance. These tool functions are used in the rendering functions generated by the compiler, such as VM after v-for compilation_ l. There is also the most familiar h function (vm._c), but it is not declared here. It is declared in the initRender function.
The installRenderHelpers method is called in renderMixin.
/** * Mount the shorthand rendering tool function on the instance * @param {*} target Vue example */ export function installRenderHelpers (target: any) { target._o = markOnce target._n = toNumber target._s = toString target._l = renderList target._t = renderSlot target._q = looseEqual target._i = looseIndexOf target._m = renderStatic target._f = resolveFilter target._k = checkKeyCodes target._b = bindObjectProps target._v = createTextVNode target._e = createEmptyVNode target._u = resolveScopedSlots target._g = bindObjectListeners target._d = bindDynamicKeys target._p = prependModifier }
If you are interested in a certain method, you can delve into it yourself.
summary
The interviewer asked: VM$ What did set (obj, key, Val) do?
Answer:
vm.& dollar; Set is used to add a new property to the responsive object, ensure that the new property is also responsive, and trigger the view update. Because Vue cannot detect the new attributes of the object or add an element to the array through the index, such as this obj. newProperty = 'val',this.arr[3] = 'val'. That's why VM$ Set, which is Vue Alias of set.
- Add a new responsive data for the object: call the defineReactive method to add responsive data for the object, and then execute dep.notify to notify the dependency and update the view
- Add a new responsive data to the array: through the splice method
The interviewer asked: VM$ What does delete (obj, key) do?
Answer:
vm.$delete is used to delete attributes on an object. If the object is responsive and can ensure that the view update can be triggered. This method is mainly used to avoid the situation that Vue cannot detect the deletion of attributes. It's Vue Alias of delete.
- Delete the elements of the specified subscript of the array, which is completed internally through the splice method
- To delete a specified attribute on an object, first delete the attribute through the delete operator, and then execute dep.notify to notify the dependency and update the view
The interviewer asked: VM$ What does watch (exporfn, callback, [options]) do?
Answer:
vm.$watch is responsible for observing the change of the calculation result of an expression or a function on the Vue instance. When it changes, the callback function will be executed and two parameters will be passed to the callback function. The first is the updated new value and the second is the old value.
One thing to note here is: if you are observing an object, such as an array, when you add an element to the array with array methods, such as push, the new value passed by the callback function is the same as the old value when triggered, because they point to the same reference, Therefore, when observing an object and judging whether the new and old values are equal in the callback function, we need to pay attention to it.
vm.$ The first parameter of watch only receives the key path of simple responsive data. For more complex expressions, it is recommended to use the function as the first parameter.
As for VM$ The internal principle of watch is:
- Set options User = true, the flag is a user watcher
- Instantiate a watcher instance. When a data update is detected, trigger the execution of the callback function through the watcher, and pass the new and old values as the parameters of the callback function
- Returns an unwatch function to cancel observation
The interviewer asked: VM$ What did on (event, callback) do?
Answer:
Listens to user-defined events on the current instance. Events can be generated by VM& dollar; When triggered by emit, the callback function will receive all the additional parameters of the incoming event trigger function (vm.$emit).
vm.$ The principle of on is very simple, which is to handle the two parameters of event and callback passed, and store the registered event and callback function in VM in the form of key value pairs_ In the event object, VM_ events = { eventName: [cb1, cb2, ...], ... }.
The interviewer asked: VM$ What did emit (eventName, [... Args]) do?
Answer:
Trigger the specified event on the current instance, and the additional parameters will be passed to the callback function of the event.
Its internal principle is to execute VM_ All callback functions in events [eventName].
It can be seen from the implementation principle of $Emon at this time, so it depends on who can trigger the event Interpretation of Vue source code (2) -- Vue initialization process The explanation of initEvent in will understand what is being said, because VM is used internally for the processing of component custom events$ on,vm.$emit.
The interviewer asked: VM$ What does off ([event, callback]) do?
Answer:
Remove the custom event listener, that is, remove VM_ Relevant data on the events object.
- If no parameters are provided, all event listeners of the instance will be removed
- If only the event parameter is provided, all listeners for the event on the instance are removed
- If both parameters are provided, remove the listener corresponding to the event on the instance
The interviewer asked: VM$ What does once (event, callback) do?
Answer:
Listen for a custom event, but the event will only be triggered once. Once triggered, the listener is removed.
Its internal implementation principle is:
- Wrap the callback function passed by the user. When the wrapper function is executed, in addition to the user callback function, it will also execute VM$ Off (event, wrapper function) removes the event
- Use VM$ On (event, wrapper function) registers events
The interviewer asked: VM_ What does update (vnode, hydrating) do?
Answer:
The official document does not specify the API. This is an instance method used in the internal source code. It is responsible for updating the page. It is the entry of page rendering. It determines whether to render for the first time or update the page according to the existence of prevVnode\_\_ patch\_\_ Function passes different parameters. This method will not be used in business development.
The interviewer asked: VM$ What does forceupdate () do?
Answer:
Force the Vue instance to re render, which only affects the component instance itself and the sub components inserted into the slot content, not all sub components. Its internal principle is simple, that is, directly call VM_ watcher.update(), which is the watcher Update() method, execute this method to trigger component update.
The interviewer asked: VM$ What did destroy () do?
Answer:
Responsible for completely destroying an instance. Clean up its connection with other instances and unbind all its instructions and event listeners. During execution, two hook functions beforeDestroy and destroy will be called. This method is not used in most business development scenarios, and is generally operated through v-if instructions. Its internal principle is:
- Call beforeDestroy hook function
- Remove yourself from dad ($parent) and destroy your relationship with dad
- Through the watcher Teardown() to remove dependency listening
- Via VM\_\_ patch\_\_ (vnode, null) method to destroy the node
- Call the destroyed hook function
- Via VM$ The off method removes all event listeners
The interviewer asked: VM$ What did nexttick (CB) do?
Answer:
vm.$nextTick is Vue The alias of nexttick, whose function is to delay the execution of callback function cb, is generally used for this Key = newval after changing the data, you want to get the changed DOM data immediately:
this.key = 'new val' Vue.nextTick(function() { // DOM updated })
Its internal execution process is:
- this.key = 'new val', trigger the update of dependency notification and put the watcher responsible for the update into the watcher queue
- Put the function of refreshing the watcher queue into the callbacks array
- Put a function to refresh the callbacks array in the asynchronous task queue of the browser
- vm.$nextTick(cb) to jump the queue and put the cb function directly into the callbacks array
- The function of refreshing the callbacks array will be executed at a certain time in the future
- Then execute many functions in the callbacks array to trigger the watcher Execute run and update DOM
- Since the cb function is put into the callbacks array later, this ensures that the DOM update is completed first, and then the cb function is executed
The interviewer asked: VM_ What did render do?
Answer:
The official document does not provide this method. It is an instance method used in the source code, which is responsible for generating vnode. The key code is just one line, and the render function is executed to generate vnode. However, a lot of exception handling code is added.
link
- Supporting video, 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.