Writing in the front
Looking back on my three and a half years in the front end, only in the last few months have I actually looked at the source code. For example, when developing components using uni-App, you might want to look at how it is implemented in ElementUI. If EggJS is used for uploading and downloading files, you may find it difficult to write clear files. If EggJS is used for uploading and downloading files, you may want to look at egg-multipart. However, the principle of Vue still remains in the Object. DefineProperty responsive processing and create fragment element processing two-way data binding. Spent a week, forced themselves step by step to debug the source code, to explore the principle, finally finished writing this article on the principle of Vue exploration article. Some mistakes are inevitable, after all, my understanding may be wrong, if you can learn something from this article, I am glad. If you can find out my wrong understanding of Vue principle, please correct me, thank you. Vue source code to start with the first to find the entry file, because it is a Web platform and for the development environment, so the current Vue with compiler version, Entry documents for platforms/web/entry – the runtime – with – compiler. Js, but in the entrance to the file was not found in the Vue constructors, Then follow the import find platforms/web/runtime/index, js, this document defines an important function in Vue. $mount, but the Vue constructor or not. This file mounts the global API for Vue’s constructor and tells me the final directory of Vue’s constructor via import: core/instance/init.js
core/instance/index.js
- Define the constructor Vue and setnew Vue(options)Options is passed in the this._init method
- Call initMixin to add the _init method for Vue
- Call stateMixin to add $data / $props / $set / $delete / $watch method for Vue
- Call eventsMixin to add the $ON / $once / $off / $EMIT method for Vue
- Call lifecycleMixin to add the _update / $forceUpdate / $Destroy method for Vue
- Call renderMixin to add the $nextTick / _render method for Vue
- The Vue constructor is constructed and can be instantiated
- InitMixin (Vue) : core/instance/init.js
function Vue(options) {
if(process.env.NODE_ENV ! = ='production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')}this._init(options)
}
// Register the _init method of the VM to initialize the VM
initMixin(Vue)
$data / $props / $set / $delete / $watch
stateMixin(Vue)
// Initialize the event-related method $on / $once / $off / $emit
eventsMixin(Vue)
// Initialize the lifecycle related interfuse method _update / $forceUpdate / $Destroy
lifecycleMixin(Vue)
Render $nextTick / _render
renderMixin(Vue)
Copy the code
core/instance/init.js
- Record that VM is equal to this, because the caller is this inside the Vue constructor, so VM is equal to an instance of Vue
const vm = this // -> Vue instance
Copy the code
- The assignment _isVue defines the object as a Vue object
vm._isVue = true
Copy the code
- To determine if the VM is a child component, initialize vm.$options in a different way
if (options && options._isComponent) {
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
Copy the code
- Call initLifecycle to add the $children / $parent / $root / $refs attribute to the VM
- Call initEvents to initialize the VM’s event listening mechanism
- Call initRender to add the $Slots / $scopedSlots / _c / $createElement / $attrs / $Listeners attribute to the VM
- Call beforeCreate lifecycle
- Use initInjections to add the Inject attribute to the VM
- Invoke initState to initialize a VM. Add the _props/methods / _data/computed/watch properties
- Call initProvide to add the resolve provide after data/props attribute to the VM
- Call the Created lifecycle
- Call vm.$mount to enter the page rendering process
- Directory: $mount method platforms/web/runtime/index, js
- $mount is redefined in the entry file: platforms/web/entry- Runtime-with-compiler.js
Vue.prototype._init = function (options? :Object) {
const vm = this
// a uid
vm._uid = uid++
// a flag to avoid this being observed
vm._isVue = true
// merge options
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
// expose real self
vm._self = vm
// Vm lifecycle variables are initialized
// $children / $parent / $root / $refs
initLifecycle(vm)
// The parent component is bound to the current component's event
initEvents(vm)
// Compile the VM render initialization
// $slots / $scopedSlots / _c / $createElement / $attrs / $listeners
initRender(vm)
// Call the beforeCreate lifecycle
callHook(vm, 'beforeCreate')
// Inject the inject member into the VM
// resolve injections before data / props
initInjections(vm)
// Initialize _props/methods / _data/computed/watch for a VM
initState(vm)
// Initialize provide
// resolve provide after data/props
initProvide(vm)
// Call the Created Lifecycle
callHook(vm, 'created')
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
Copy the code
initState core/instance/state.js
- Handles data within options
- InitProps, initMethods, and initData mount props/data/methods in options to the VM so that they can be obtained using this
- InitData also responds to options.data after it is mounted to the VM by calling the observe method
- InitComputed initializes the VMCalculate the watcher
- Mount the _computedWatchers attribute on the VM, which is a key-value pair that records watcher
- Walk through options.computed, defining each computed property as oneCalculate the watcherThat is, new Watcher, and will eachCalculate the watcherSave it to _computedWatchers, and then mount each calculated attribute to the VM so that it can be obtained by this, which isCalculate the watcherValue stored in
- InitWatch creates one for each listener in options.watchListen for watcher, which calls the $watch method internally
function initState (vm) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)}if (opts.computed) initComputed(vm, opts.computed)
if(opts.watch && opts.watch ! == nativeWatch) { initWatch(vm, opts.watch) } }Copy the code
observe core/observer/index.js
- Data response _data = options.data
- Checks if the data is of type object, and returns if it is not
- Check whether the data has _ob_ properties
- If yes, return _ directlyob_
- Return new Observer(_data)
- Check whether the data has _ob_ properties
- Objects and arrays are of type Object, so they can enter the reactive processing phase
- Add _ to the current _dataobThe _ attribute, if present, indicates that it has been processed responsively
- Determine the _data type
- If it’s an array, then the _ of _dataproto_ points to arrayMethods, and then iterates over _data, recursively calling Observe again for each element, responding to the child element
- If it is an object, the walk method is called, traversing the key-value pairs of _data, and the defineReactive method is called to reset the getter and setter for each property
- The dependencies are collected in the getter and fired in the setter
- Getter -> If the property is an object and has child properties, then dependency collection is also required for the child properties
- Setter -> performs reactive processing for newValue
// index.js
import { arrayMethods } from './array'
class Observer {
constructor (value) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__'.this)
if (Array.isArray(value)) {
const augment = hasProto
? protoAugment
: copyAugment
augment(value, arrayMethods, arrayKeys)
this.observeArray(value)
} else {
this.walk(value)
}
}
walk (obj) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}
observeArray (items) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
function observe (value, asRootData) {
if(! isObject(value) || valueinstanceof VNode) return
let ob
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if( observerState.shouldConvert && ! isServerRendering() && (Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) && ! value._isVue ) { ob =new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
function defineReactive (
obj,
key,
val,
customSetter,
shallow / / shallow copy
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) return
const getter = property && property.get
const setter = property && property.set
letchildOb = ! shallow && observe(val)Object.defineProperty(obj, key, {
enumerable: true.configurable: true.get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
if(newVal === value || (newVal ! == newVal && value ! == value)) {return
}
if(process.env.NODE_ENV ! = ='production' && customSetter) {
customSetter()
}
if (setter) {
setter.call(obj, newVal)
} else{ val = newVal } childOb = ! shallow && observe(newVal) dep.notify() } }) }Copy the code
core/observer/array.js
- Rewriting will change the methods of the array
- When the original array is changed, the new element is processed responsively -> _ob_.observeArray(inserted)
- Triggers dependencies -> _ when the original array changesob_.dep.depend()
// array.js
const arrayProto = Array.prototype
const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse'
]
methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method]
def(arrayMethods, method, function mutator (. args) {
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
// notify change
ob.dep.notify()
return result
})
})
Copy the code
Dep class core/observer/Dep. Js
- The current watcher is assigned to the target before dependency collection. There is only one target globally, so only one watcher can be triggered at a time
- The subs store isWatcher object that stores an array of DEP objects
class Dep {
static target;
constructor () {
this.id = uid++
this.subs = []
}
addSub (sub) {
this.subs.push(sub)
}
removeSub (sub) {
remove(this.subs, sub)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify () {
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
Copy the code
Watcher
Watcher core/observer/Watcher. Js
let uid = 0
class Watcher {
vm;
expression;
cb;
id;
deep;
user;
lazy;
sync;
dirty;
active;
deps;
newDeps;
depIds;
newDepIds;
getter;
value;
constructor (vm, expOrFn, cb, options) {
this.vm = vm
vm._watchers.push(this)
if (options) {
this.deep = !! options.deepthis.user = !! options.userthis.lazy = !! options.lazythis.sync = !! options.sync }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(a)this.newDepIds = new Set(a)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 = function () {} 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()
}
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 "The ${this.expression}"`)}else {
throw e
}
} finally {
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
addDep (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)
}
}
}
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
}
update () {
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
run () {
if (this.active) {
const value = this.get()
if( value ! = =this.value || isObject(value) || this.deep
) {
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 "The ${this.expression}"`)}}else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
evaluate () {
this.value = this.get()
this.dirty = false
}
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
teardown () {
if (this.active) {
if (!this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this)}let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)}this.active = false}}}const seenObjects = new Set(a)function traverse (val) {
seenObjects.clear()
_traverse(val, seenObjects)
}
function _traverse (val, seen) {
let i, keys
const isA = Array.isArray(val)
if((! isA && ! isObject(val)) || !Object.isExtensible(val)) return
if (val.__ob__) {
const depId = val.__ob__.dep.id
if (seen.has(depId)) return
seen.add(depId)
}
if (isA) {
i = val.length
while (i--) _traverse(val[i], seen)
} else {
keys = Object.keys(val)
i = keys.length
while (i--) _traverse(val[keys[i]], seen)
}
}
Copy the code
There are three types of watcher:Render the watcher Calculate the watcher Listen for watcher
Render the watcher
- Initialize theRender the watcher() => {vm._update(vm._render(), hydrating)}
- The get method is going to put the current firstRender the watcherThe vm._render method returns the virtual DOM and therefore accesses all properties accessed in vNode, so dep. target becomes true when entering their getters. The dependency collection can be started by changing the dep. target back to NULL when the collection is complete so that the next dependency collection will not fail, so use try… catch… In finally, reset dep. target in finally
- The _update method calls the patch method to start rendering the pageCall $mountUnderstanding)
const updateComponent = () = > {
vm._update(vm._render(), hydrating)
}
vm._watcher = new Watcher(vm, updateComponent, noop)
/* new Watcher( vm, () => { vm._update(vm._render(), hydrating) }, () => {} ) this.vm = vm vm._watchers.push(this) this.cb = () => {} this.expression = this.getter = () => { vm._update(vm._render(), hydrating) } this.value = this.get() */
Copy the code
Calculate the watcher
- Generated on initComputedCalculate the watcherThen the defineComputed method is called
- The defineComputed method has two uses
- Find current on VM. _computedWatchersCalculate the watcher, calls the evaluate method on the current instance, and then does a dependency collection
- The evaluate method calls the get method to get the value of the evaluated property, which calls the method that evaluated the property, i.e. () => this.firstName + this.lastname
- The get method is going to put the current firstCalculate the watcherBecause the method that evaluates the property accesses vm.firstName and vm.lastName, dep. target becomes true when entering their getters, and dependency collection can begin. Since you want to change dep. target back to NULL when the collection is finished so that the next dependent collection will not fail, use try… catch… In finally, reset dep. target in finally
- Bind the properties defined in computed to the VM
- Find current on VM. _computedWatchersCalculate the watcher, calls the evaluate method on the current instance, and then does a dependency collection
/* // Computed: {fullName() {return this.firstName + this.lastName}, address: { get() { return this.province + this.city }, set(newVal) { const location = newVal.split('-') this.province = location[0] this.city = location[1] } } } */
for (const key in vm.$options.computed) {
const userDef = computed[key]
const getter = typeof userDef === 'function' ? userDef : userDef.get
vm._computedWatchers[key] = new Watcher(
vm,
getter || noop, // () => this.firstName + this.lastName
noop,
{
lazy: true})}/* new Watcher( vm, () => this.firstName + this.lastName, () => {}, { lazy: true } ) this.vm = vm vm._watchers.push(this) this.cb = () => {} this.lazy = true this.dirty = true this.expression = this.getter = () => this.firstName + this.lastName this.value = undefined */
Copy the code
Listen for watcher
- Initialize theListen for watcherThat’s going to go into the get method, which is going to call this.getter to get the present value of the listening object, so it’s going to go into the getter for the listening object
- The get method first sets the dep. target to currentListen for watcherInstance of, and currentlyListen for watcherTarget in the getter becomes true, and the dependency collection can begin. When the collection is complete, change the dep. target back to null, so that the next dependency collection will not fail. catch… In finally, reset dep. target in finally
Watch: {fullName(newVal, oldVal) {console.log(newVal, oldVal)}, address: {deep: true, immediate: true, handler(newVal, oldVal) { console.log(newVal, oldVal) } } } */
for (const key in vm.$options.watch) {
const handler = watch[key]
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
} else {
createWatcher(vm, key, handler)
}
}
function createWatcher (vm, keyOrFn, handler, options) {
if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
if (typeof handler === 'string') {
handler = vm[handler]
}
return vm.$watch(keyOrFn, handler, options)
}
Vue.prototype.$watch = function (expOrFn, cb, options) {
const vm = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
cb.call(vm, watcher.value)
}
return function unwatchFn () {
watcher.teardown()
}
}
/* new Watcher( vm, 'fullName' (newVal, oldVal) => { console.log(newVal, oldVal) }, { user: true } ) new Watcher( vm, 'address' (newVal, oldVal) => { console.log(newVal, oldVal) }, { user: true, deep: true, immediate: }) this.vm = vm vm._wating. push(this) this.user = true this.deep = false // address watcher is true this.cb = (newVal, oldVal) => { console.log(newVal, oldVal) } this.expression = 'fullName' this.getter = parsePath(expOrFn) // parsePath('fullName') this.value = this.get() -> vm.fullName */
Copy the code
platforms/web/runtime/index.js
Vue.prototype.$mount = function (el, hydrating) {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
Copy the code
platforms/web/entry-runtime-with-compiler.js
- Will platforms/web/runtime/index. Js defined $mount method saved, record formount
- Redefine the $mount method
- Query the template for el, and if EL is body or HTML element, an error is reported
- Check whether the render method is passed in new Vue(options)
- Have render then call before the recordmountmethods
- If not, the template is passed through template, or there is only one template root node (EL).
- If there is only one template root node, el, the outerHTML of the root node is obtained and assigned to the template
- callcompileToFunctionsMethod, passed in to convert the template string to a rendering function
- This file is used to unify options into objects that have the Render method
- Recorded before the callmountmethods
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (el, hydrating) {
el = el && query(el)
/* istanbul ignore if */
if (el === document.body || el === document.documentElement) { process.env.NODE_ENV ! = ='production' && warn(
`Do not mount Vue to <html> or <body> - mount to normal elements instead.`
)
return this
}
const options = this.$options
// resolve template/el and convert to render function
// If template and render are present together, only the contents of render will be compiled
if(! options.render) {let template = options.template
if (template) { // There is a template property
if (typeof template === 'string') { // The template content is a selector or an HTML string
if (template.charAt(0) = = =The '#') { // template: '#app'
template = idToTemplate(template) // Return whether the node exists
/* istanbul ignore if */
if(process.env.NODE_ENV ! = ='production' && !template) {
warn(
`Template element not found or is empty: ${options.template}`.this)}}}else if (template.nodeType) { // getElementById('app')
template = template.innerHTML
} else {
if(process.env.NODE_ENV ! = ='production') {
warn('invalid template option:' + template, this)}return this}}else if (el) { // Get the HTML from EL when there is no template or render
template = getOuterHTML(el)
}
if (template) {
const { render, staticRenderFns } = compileToFunctions(template, {
shouldDecodeNewlines,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
}
}
return mount.call(this, el, hydrating)
}
Copy the code
compileToFunctions
- Higher-order functions, by createCompiler method returns, directory: platforms/web/compiler/index, js
const { compile, compileToFunctions } = createCompiler(baseOptions)
const { render, staticRenderFns } = compileToFunctions(template, {
shouldDecodeNewlines,
delimiters: options.delimiters,
comments: options.comments
}, this)
Copy the code
createCompiler
- A higher-order function returned by the createCompilerCreator method in the directory compiler/index.js
- The createCompilerCreator method is called with a method that converts the template into an AST syntax tree
- Call the parse method to convert the template into an AST syntax tree
- Call the optimize method to optimize the AST syntax tree and mark static nodes and static root nodes
- Call the generate method to concatenate the AST syntax tree into a string of JavaScript code that new Function can execute
- Returns a string of executable JavaScript code from the AST syntax tree/new Function
const createCompiler = createCompilerCreator(function baseCompile (template, options) :CompiledResult {
const ast = parse(template.trim(), options)
optimize(ast, options)
const code = generate(ast, options)
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
})
Copy the code
createCompilerCreator
- Higher-order functions that return the compile and compileToFunctions methods in the directory compiler/create-compiler.js
- compiledIs the object of the AST syntax tree/new Function executable JavaScript code string returned by the createCompiler method above
- Return compile and compileToFunctions
- CompileToFunctions method is createCompileToFunctionFn (compile) method return values, createCompileToFunctionFn method called the compile method returnscompiledAnd thencompiledDeconstruction returns with render and staticRenderFns
- So deconstructing the compileToFunctions called at the entry function yields these two values
function createCompilerCreator (baseCompile) {
return function createCompiler (baseOptions) {
function compile (template, options) {
const finalOptions = Object.create(baseOptions)
if (options) {
// merge custom modules
if (options.modules) {
finalOptions.modules =
(baseOptions.modules || []).concat(options.modules)
}
// merge custom directives
if (options.directives) {
finalOptions.directives = extend(
Object.create(baseOptions.directives),
options.directives
)
}
// copy other options
for (const key in options) {
if(key ! = ='modules'&& key ! = ='directives') {
finalOptions[key] = options[key]
}
}
}
const compiled = baseCompile(template, finalOptions)
return compiled
}
return {
compile,
compileToFunctions: createCompileToFunctionFn(compile)
}
}
}
Copy the code
Call $mount
- Of the abovemountmethods
- Which returns the mountComponent method, this method is defined in the core/instance/lifecycle. Js
- Call beforeMount lifecycle
- Define the updateComponent method to create a concrete implementation of the rendering component
- Call _update method to create each time data update view the concrete implementation of change, which invokes the patch method to contrast the old and new node, directory: core/instance/lifecycle. Js
- The _update method requires a virtual DOM to be passed in, so a call to vm._render is passed in, and the result is a virtual DOM
- The _render method will go to options on the current instance and call the render method. The render method essentially returns the result of the h function, so it will get the virtual DOM of the current template, which is vnode, directory: core/instance/render.js
- The _render method calls the h function internally by calling _c and $createElement, both of which point to the createElement method in the directory: core/vdom/create-element.js
- defineRender the watcher, pass in updateComponent, which is called to update the view whenever the data is updated
- The rendering function will be executed immediately after the definition is successful, and the patch in the _update method will be executed immediately. After the new and old nodes are obtained, they are mounted to the DOM tree through diff algorithm, and finally rendered to the page
- Call the Mounted lifecycle
function mountComponent (vm, el, hydrating) {
vm.$el = el
if(! vm.$options.render) { vm.$options.render = createEmptyVNode// Empty string virtual DOM
}
callHook(vm, 'beforeMount')
let updateComponent
updateComponent = () = > {
vm._update(vm._render(), hydrating)
}
vm._watcher = new Watcher(vm, updateComponent, noop)
hydrating = false
// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')}return vm
}
Copy the code
createElement / _createElement core/vdom/create-element.js
- The createElement method internally calls the _createElement method to create the virtual DOM
- Determines whether the passed data has an IS attribute
- If so, this is a child component tag and converts the tag from ‘component’ to a child component object
- Check whether the TAG does not exist to prevent the IS attribute from pointing to an incorrect route address
- If not, an empty virtual DOM is created
- Determine if the passed data has a children attribute
- < Component :is=’child’ />
- < Component :is=’child’ />
- Determine whether the tag is a string
- If it is a string, it is an AST of element nodes
- If the AST’s tag is an HTML reserved tag, create a virtual DOM for it
- If components exist in the instance object of the Vue of the AST, it indicates that the child component was created in
mode, and then calls createComponent to create a virtual DOM for the child component
- If it is not a string, then it is a child component of the form < Component :is=’child’ /> (the tag was assigned to the child component object above). Call createComponent to create a virtual DOM for this child component
- If it is a string, it is an AST of element nodes
- CreateComponent internal will call resolveConstructorOptions method for component bindings init/insert/prepatch/destroy four hooks, easy to do when patch after the treatment
function _createElement (context, tag, data, children, normalizationType) {
if(isDef(data) && isDef((data).__ob__)) { process.env.NODE_ENV ! = ='production' && warn(
`Avoid using observed data object as vnode data: The ${JSON.stringify(data)}\n` +
'Always create fresh vnode data objects in each render! ',
context
)
return createEmptyVNode()
}
if (isDef(data) && isDef(data.is)) {
tag = data.is
}
if(! tag) {// in case of component :is set to falsy value
return createEmptyVNode()
}
// warn against non-primitive key
if(process.env.NODE_ENV ! = ='production'&& isDef(data) && isDef(data.key) && ! isPrimitive(data.key) ) { warn('Avoid using non-primitive value as key, ' +
'use string/number value instead.',
context
)
}
// support single function children as default scoped slot
if (Array.isArray(children) &&
typeof children[0= = ='function'
) {
data = data || {}
data.scopedSlots = { default: children[0] }
children.length = 0
}
if (normalizationType === ALWAYS_NORMALIZE) {
children = normalizeChildren(children)
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children)
}
let vnode, ns
if (typeof tag === 'string') {
let Ctor
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
if (config.isReservedTag(tag)) {
// platform built-in elements
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined.undefined, context
)
} else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
// component
vnode = createComponent(Ctor, data, context, children, tag)
} else {
// unknown or unlisted namespaced elements
// check at runtime because it may get assigned a namespace when its
// parent normalizes children
vnode = new VNode(
tag, data, children,
undefined.undefined, context
)
}
} else {
// direct component options / constructor
vnode = createComponent(tag, data, context, children)
}
if (isDef(vnode)) {
if (ns) applyNS(vnode, ns)
return vnode
} else {
return createEmptyVNode()
}
}
Copy the code
Render page flow
_patch_
- inRender the watcherIt mentions _patch_ method
- Define directory: platforms/web/runtime/index. Js
- _patch_ is a high-order function that internally returns the real patch method in the directory: core/vdom/patch.js
- _patchThe _ method takes two input arguments, modules and nodeOps
- Modules is an array of hook modules that return the common create/Update/destroy hooks and other hooks based on the Web platform
- Common hook directory: core/vdom/modules/index
- Web platform hooks directory: web/runtime/modules/index
- NodeOps is a return to the object of various DOM manipulation method, directory: platforms/web/runtime/node – ops. Js
import { patch } from './patch'
Vue.prototype.__patch__ = inBrowser ? patch : noop
// platforms/web/runtime/patch.js
const modules = platformModules.concat(baseModules)
const patch = createPatchFunction({ nodeOps, modules })
Copy the code
patch
- Check whether the new node exists
- If the new node does not exist, check whether the old node exists
- If the old node exists, then the component is about to be destroyed, call the invokeDestroyHook method to destroy the old node and its children, and terminate the following program
- If the new node does not exist, check whether the old node exists
- Set isInitialPatch to false, indicating that it is not created for the first time
- Define insertedVnodeQueue as an empty array, waiting for child components to be inserted
- Check whether the old node exists
- If the old node does not exist, set isInitialPatch to true, indicating that it is created for the first time, and callcreateElmMethod to create the real DOM of the new node and mount it to the DOM tree for rendering
- If the old node exists, it is determined whether the old node is a real DOM
- If the old node is not the real (that is, the virtual DOM), then the new node is the same node
- If it is the same node, callpatchVnodeMethod to compare nodes
- If the old node is the real DOM,The old and new nodes cannot be the same node. You can directly remove the old node from the DOM tree
- Create a virtual DOM for emptyNodeAt -> oldVnode = emptyNodeAt(oldVnode)
- callcreateElmMethod creates the real DOM for the new node and inserts it after the old node
- Determines whether the new node has the parent property
- If the new node has the parent property
- Call isPatchable to determine if the new node is a child component (true for elements, false for text nodes)
- If the new node is a child component, assign the virtual DOM of the child component to the new node -> vnode = vnode.ponentInstance._vnode, returning a Boolean value of whether the tag of the child component exists
- If the new node is not a child, return a Boolean value of whether the tag of the new node exists
- Define the patchAble variable to receive the return value from the isPatchable method
- While loop the parent property of the new node, destroying the parent
- Assign the parent property of parent to parent until all the new node’s parents are destroyed
- Call isPatchable to determine if the new node is a child component (true for elements, false for text nodes)
- If the new node has the parent property
- Check whether the old node has a parentNode -> nodeOps. ParentNode (oldElm)
- If the old node has parentNodes, call removeVnodes to remove the old node from the real DOM tree
- If the old node has no parentNode, invokeDestroyHook is called to destroy the old node
- If the old node is the real DOM,The old and new nodes cannot be the same node. You can directly remove the old node from the DOM tree
- If it is the same node, callpatchVnodeMethod to compare nodes
- If the old node is not the real (that is, the virtual DOM), then the new node is the same node
- InvokeInsertHook is called to insert the child component into the DOM tree for rendering
- Return virtual DOM with elm properties mounted -> vnode.elm
function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {
if (isUndef(vnode)) {
if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
return
}
let isInitialPatch = false
const insertedVnodeQueue = []
if (isUndef(oldVnode)) {
isInitialPatch = true
createElm(vnode, insertedVnodeQueue, parentElm, refElm)
} else {
const isRealElement = isDef(oldVnode.nodeType)
if(! isRealElement && sameVnode(oldVnode, vnode)) { patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly) }else {
if (isRealElement) {
oldVnode = emptyNodeAt(oldVnode)
}
const oldElm = oldVnode.elm
const parentElm = nodeOps.parentNode(oldElm)
createElm(
vnode,
insertedVnodeQueue,
oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
)
if (isDef(vnode.parent)) {
let ancestor = vnode.parent
const patchable = isPatchable(vnode)
while (ancestor) {
for (let i = 0; i < cbs.destroy.length; ++i) {
cbs.destroy[i](ancestor)
}
ancestor.elm = vnode.elm
if (patchable) {
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, ancestor)
}
const insert = ancestor.data.hook.insert
if (insert.merged) {
for (let i = 1; i < insert.fns.length; i++) {
insert.fns[i]()
}
}
} else {
registerRef(ancestor)
}
ancestor = ancestor.parent
}
}
if (isDef(parentElm)) {
removeVnodes(parentElm, [oldVnode], 0.0)}else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode)
}
}
}
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
return vnode.elm
}
Copy the code
createElm
- Call createComponent to determine whether the current node is a child component
- If the current node is a child component, the child component is created and the next round of nodes is skipped
- Determine the type of the current node and create the node by calling the different create node methods
- If the tag of the current node exists, then the node is an element node. Call nodeOps. CreateElement to create the real DOM of the current node and mount it to the elm attribute of the current virtual node. Then call the createChildren method to create the children of the node, append the real DOM generated by the children to the DOM tree of the node via the nodeops.appendChild method, and finally call the INSERT method. Append this node to the DOM tree of the parent node
- If the current node is a comment node, call the nodeOps. CreateComment method to create the real DOM of the current node and mount it to the elm property of the current virtual node, then call the INSERT method to append the node to the DOM tree of the parent node
- If the current node is a text node, call the nodeOps. CreateTextNode method to create the real DOM of the current node and mount it to the elm property of the current virtual node. Then call the INSERT method to append the node to the DOM tree of the parent node
function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested) { vnode.isRootInsert = ! nested// for transition enter check
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) return
const data = vnode.data
const children = vnode.children
const tag = vnode.tag
if (isDef(tag)) {
if(process.env.NODE_ENV ! = ='production') {
if (data && data.pre) {
inPre++
}
if(! inPre && ! vnode.ns && ! ( config.ignoredElements.length && config.ignoredElements.some(ignore= > {
return isRegExp(ignore)
? ignore.test(tag)
: ignore === tag
})
) &&
config.isUnknownElement(tag)
) {
warn(
'Unknown custom element: <' + tag + '> - did you ' +
'register the component correctly? For recursive components, ' +
'make sure to provide the "name" option.',
vnode.context
)
}
}
vnode.elm = vnode.ns
? nodeOps.createElementNS(vnode.ns, tag)
: nodeOps.createElement(tag, vnode)
setScope(vnode)
createChildren(vnode, children, insertedVnodeQueue)
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
}
insert(parentElm, vnode.elm, refElm)
if(process.env.NODE_ENV ! = ='production' && data && data.pre) {
inPre--
}
} else if (isTrue(vnode.isComment)) {
vnode.elm = nodeOps.createComment(vnode.text)
insert(parentElm, vnode.elm, refElm)
} else {
vnode.elm = nodeOps.createTextNode(vnode.text)
insert(parentElm, vnode.elm, refElm)
}
}
Copy the code
createComponent
- If it is a child component, there will be element inline attributes and component hook attributes in vNode.data
- The hook attribute contains four hooks: init, INSERT, prepatch, destroy
- The isReactivated variable determines whether the child component is already attached and is a keepAlive component
- If (isDef(I = i.hook) && isDef(I = i.hook))); if (isDef(I = i.hook) && isDef(I = i.hook))); if (isDef(I = i.hook) && isDef(I = i.hook)); core/vdom/create-component.js
- A call to I (that is, hook.init) creates a componentInstance property for the current child component
- Hook.init is called internallycreateComponentInstanceForVnodeMethod, which returns an instance of VueComponent. Because VueComponent inherits from Vue’s constructor, the this._init method is called when return new VueComponent()
- increateComponentInstanceForVnodeThe _isComponent/parent / _parentVnode property is internally mounted to the current instance options, so the _init method of the Vue constructor is called to the initInternalComponent method. Mount the parent / _parentVnode property on the $options of the current instance
- The initLifecycle method is also called within the Vue constructor’s _init method. There is a bit of nesting of parent and parent and parent and children
- Hook.init finally calls the $mount method of the VueComponent instance object to mount the child component
- Hook.init is called internallycreateComponentInstanceForVnodeMethod, which returns an instance of VueComponent. Because VueComponent inherits from Vue’s constructor, the this._init method is called when return new VueComponent()
Therefore, in Vue’s rendering mechanism, if there are child components, the parent component will be created first, and then the child component will be created, and then the child component will be mounted first, and then the parent component will be mounted, because the $mount of the child component exists in the $mount of the parent component
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data
if (isDef(i)) {
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */, parentElm, refElm)
}
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue)
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
}
return true}}}Copy the code
event
- After creating the real DOM process, there are actually event binding and other processing for the real DOM, which is implemented by calling the invokeCreateHooks in the source code
- The createElm method calls the invokeCreateHooks method if the current node’s tag is present, because the current node cannot be bound to events if it is a text or comment node
- The invokeCreateHooks method is also called in the initComponent method called in the createComponent method
- Create array, and then call the hooks in the array. How does CBS come from?
- We’ve already said _patch_ is a higher-order function that internally returns the real patch method, which in turn is a return value from a call to the createPatchFunction method passed two parameters, Modules and nodeOps, the modules is the one above _patchModules return an array of hook modules, including create, update, and destroy hooks
- The call to the createPatchFunction method traverses the modules initialization CBS
- Modules is a two-dimensional array, and there must be two insidecreate / update / destroyHooks (a total of integration), directory: core/vdom/modules/ref. Js and core/vdom/modules/directives. Js
- In addition the platforms/web/runtime/modules directory also integrates other hooks
- Attrs. js is integrated for attrs in the virtual DOMcreate / updateHooks (i.e. : SRC, etc. in tags)
- Class.js is integrated with the tag classcreate / updateHooks (that is, classes and: classes in tags)
- DomProps for the virtual DOM is integrated in domPropscreate / updateHooks (that is, to add properties directly to the child component tag line)
- Events.js is integrated for on in the virtual DOMcreate / updatehook
- Style. Js integrates the staticStyle and style for the virtual DOMcreate / updateHooks (i.e. :style in the tag)
- Transition.js is integrated with the official Vue supplyThe transitionThe component’screate / activate / removehook
- We can see that there are 19 hooks in modules. We can divide the hooks in modules into an array with the same attribute name, and then store them separately in CBS
- create: [updateAttrs, updateClass, updateDOMListeners, updateDOMProps, updateStyle, _enter, create, updateDirectives, ]
- activate: [_enter, ]
- update: [updateAttrs, updateClass, updateDOMListeners, updateDOMProps, updateStyle, update, updateDirectives, ]
- remove: [remove, ]
- destroy: [destroy, unbindDirectives, ]
- Back in the invokeCreateHooks method, it internally iterates through the cbs.create array and then calls each of the eight hooks in create
- When you run the updateDOMListeners hook, determine whether the virtual DOM has the ON attribute (vNode.data.on).
- If there is on attribute on the virtual DOM, then take the real DOM(vnode.elm) of the virtual DOM as the target. And call the updateListeners method to bind an event listener to that target (target.addeventListener).
- UpdateListeners method directory: Core /vdom/helpers/update-listeners
- Target. The addEventListener method from platforms/web/runtime/modules/events. The add method of js, but as a parameter to updateListeners method call
- If there is on attribute on the virtual DOM, then take the real DOM(vnode.elm) of the virtual DOM as the target. And call the updateListeners method to bind an event listener to that target (target.addeventListener).
- The same goes for other hooks
const hooks = ['create'.'activate'.'update'.'remove'.'destroy']
function createPatchFunction(backend) {
let i, j
const cbs = {}
const { modules, nodeOps } = backend
for (i = 0; i < hooks.length; ++i) {
cbs[hooks[i]] = []
for (j = 0; j < modules.length; ++j) {
if (isDef(modules[j][hooks[i]])) {
cbs[hooks[i]].push(modules[j][hooks[i]])
}
}
}
function invokeCreateHooks(vnode, insertedVnodeQueue) {
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, vnode)
}
i = vnode.data.hook // Reuse variable
if (isDef(i)) {
if (isDef(i.create)) i.create(emptyNode, vnode)
if (isDef(i.insert)) insertedVnodeQueue.push(vnode)
}
}
return function patch() {/* invokeCreateHooks() */}}Copy the code
Diff
- The process mentioned above is the first rendering of the page, which is triggered when the data changesRender the watcherTo call the _update method for the contrast rendering process between the old and new nodes
- The core of diff algorithm is how to make a fine comparison and update the DOM tree with the minimum amount when the old and new nodes are the same node and both have child nodes
patchVnode
As described in the patch method, if the new and old nodes are the same, the patchVnode method is called to compare the nodes
- Determine whether the new and old nodes are congruent. If they are congruent, the new and old nodes do not need to be rendered, and the epicycle comparison is directly withdrawn
- Const elm = const elm = vnode.elm = oldvnode.elm
- Determine if the old and new nodes are static nodes (call the optimize method in the createCompiler section above to mark the static node and the static root)
- If both old and new nodes are static nodes, exit the epicycle comparison
- Determine whether the data of the new node exists, and determine whether the data.hook. Prepatch hook exists
- If so, the new node is a child component and immediately calls the prepatch hook that the child was mounted when it was created
- Determines whether the text attribute exists in the new node
- If the text property does not exist in the new node
- If both the old node and the new node have the children attribute, callupdateChildrenPerform fine comparison (i.e. Diff algorithm)
- If only the children attribute exists on the new node, then the old node is a text node or an empty node. Call nodeops.settextContent to empty the text in the old node, then call addVnodes to insert the new node into the old node, and finally delete the old node
- If only the children attribute exists on the old node, then the new node is an empty node and the removeVnodes method is called to remove the old node
- If the old node has a text property, then the new node is an empty node and the nodeops.settextContent method is called to empty the old node
- If there is a text property in the new node, but the value of the new node’s text property is different, the nodeops.settextContent method is called to replace the old node’s content with the value of the new node’s text property
- If the text property does not exist in the new node
function patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly) {
if (oldVnode === vnode) return
const elm = vnode.elm = oldVnode.elm
if (isTrue(vnode.isStatic) &&
isTrue(oldVnode.isStatic) &&
vnode.key === oldVnode.key &&
(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
) {
vnode.componentInstance = oldVnode.componentInstance
return
}
let i
const data = vnode.data
if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
i(oldVnode, vnode)
}
const oldCh = oldVnode.children
const ch = vnode.children
if (isDef(data) && isPatchable(vnode)) {
for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
}
if (isUndef(vnode.text)) {
if (isDef(oldCh) && isDef(ch)) {
if(oldCh ! == ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly) }else if (isDef(ch)) {
if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, ' ')
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
} else if (isDef(oldCh)) {
removeVnodes(elm, oldCh, 0, oldCh.length - 1)}else if (isDef(oldVnode.text)) {
nodeOps.setTextContent(elm, ' ')}}else if(oldVnode.text ! == vnode.text) { nodeOps.setTextContent(elm, vnode.text) }if (isDef(data)) {
if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
}
}
Copy the code
updateChildren
- Four matching policies:New before old before new after old after new after old before new before old
- New before: node newStartVnode whose children property sequence of the new node is (newStartIdx = 0)
- New: the children attribute sequence of the new node is newEndVnode (newEndIdx = newchildren.length-1)
- OldStartVnode: oldStartVnode whose children attribute sequence is (oldStartIdx = 0)
- Old after: old node oldEndVnode whose children attribute sequence is (oldEndIdx = oldchildren.length-1)
- Set the loop condition to a judgment of (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx)
- Breaking out of the loop means that the children of one of the new and old nodes must have gone through
- If the old one doesn’t exist
- Indicates that the old front is marked undefined
- Move oldStartIdx back one place
- Update oldStartVnode
- If the old does not exist
- Indicates that old is marked undefined
- Move oldEndIdx forward one bit
- Update oldEndVnode
- Hit the new before the old
- The patchVnode method is invoked for comparisonNew old before
- Will newStartIdx/oldStartIdxafterTo move a
- updateNew old beforeNewStartVnode and oldStartVnode
- Hit the old after the new
- The patchVnode method is invoked for comparisonNew old after
- Will newEndIdx/oldEndIdxbeforeTo move a
- updateNew old afterNewEndVnode and oldEndVnode
- Hit the new before the old
- The patchVnode method is invoked for comparisonAfter the new old before
- Inserts the hit node (old before) after old
- Will newEndIdxbeforeMove one place, oldStartIdxafterTo move a
- updateAfter the new old beforeNewEndVnode and oldStartVnode
- Hit the old before the new
- The patchVnode method is invoked for comparisonBefore the new old
- Inserts the hit node (old after) before the old before
- Will newStartIdxafterI’m going to move one bit, oldEndIdxbeforeTo move a
- updateBefore the new oldNewStartVnode and oldEndVnode
- Define a map record that does not match the old and new nodes of four matching policies
- If oldKeyToIdx does not exist, the map is an empty table and the createKeyToOldIdx method is called to create a new table
- Record all children in the map table before and after the old
- Each key-value pair in the map table takes the current child’s key as its key and the sequence in which it is in oldChildren as its value
- Return map table assignment to oldKeyToIdx
- Determines whether the new prefix has a key attribute
- If there is a key attribute before the new, then go to oldKeyToIdx and find the sequence in children of the old node corresponding to the key, and then assign the value to idxInOld
- If there is no key attribute before the new, the findIdxInOld method is called to find the sequence of the corresponding child before the new in the children of the old node, and then the value is assigned to idxInOld
- Check whether idxInOld exists
- If idxInOld does not exist, the new node is a new node
- Call the createElm method to create the node and mount the real DOM of the new node to the new elm property before it
- If idxInOld exists, assign the corresponding child in the old node to vnodeToMove
- Check whether vnodeToMove and the new node are the same
- When there is a new key attribute, it finds the element with the same key in the map and assigns the value to idxInOld. It is possible that there are nodes with the same key but different other attributes in the map table
- If vnodeToMove and the new node are the same
- Call the patchVnode method to compare vnodeToMove with the new node, and set the child of the old node corresponding to vnodeToMove to undefined
- Because the child is an object, it appears in the body of the loopIf the old one doesn’t exist 和 If the old does not existIn the case
- If vnodeToMove and the new node are different, the new node is a new node
- Call the createElm method to create the node and mount the real DOM of the new node to the new elm property before it
- Check whether vnodeToMove and the new node are the same
- If idxInOld does not exist, the new node is a new node
- Will newStartIdxafterTo move a
- updateBefore the newThe corresponding newStartVnode
- If oldKeyToIdx does not exist, the map is an empty table and the createKeyToOldIdx method is called to create a new table
- Jump out of the loop
- Determine whether oldStartIdx > oldEndIdx is true
- If this is true, all the old nodes are traversed first
- IsUndef (newCh[newEndIdx + 1])
- If no, all new nodes are traversed
- If yes, the new node is not fully traversed
- Define refElm as child-elm after new
- If ELM exists, the child is also present in the old node, before the unprocessed items in the new node are inserted into the child by calling the addVnodes method
- If ELM does not exist, which means the child is not in the old node, call the addVnodes method to append the unprocessed items in the new node to the end of the old node
- IsUndef (newCh[newEndIdx + 1])
- If not, all new nodes are traversed first
- The remaining child in the old node does not exist in the new node
- Call removeVnodes to remove all remaining children from the old node
- If this is true, all the old nodes are traversed first
- Determine whether oldStartIdx > oldEndIdx is true
function createKeyToOldIdx(children, beginIdx, endIdx) {
let i, key
const map = {}
for (i = beginIdx; i <= endIdx; ++i) {
key = children[i].key
if (isDef(key)) map[key] = i
}
return map
}
function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
let oldStartIdx = 0
let newStartIdx = 0
let oldEndIdx = oldCh.length - 1
let oldStartVnode = oldCh[0]
let oldEndVnode = oldCh[oldEndIdx]
let newEndIdx = newCh.length - 1
let newStartVnode = newCh[0]
let newEndVnode = newCh[newEndIdx]
let oldKeyToIdx, idxInOld, vnodeToMove, refElm
constcanMove = ! removeOnlywhile (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (isUndef(oldStartVnode)) {
oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx]
} else if (sameVnode(oldStartVnode, newStartVnode)) {
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
} else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
} else {
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
if (isUndef(idxInOld)) { // New element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
} else {
vnodeToMove = oldCh[idxInOld]
/* istanbul ignore if */
if(process.env.NODE_ENV ! = ='production' && !vnodeToMove) {
warn(
'It seems there are duplicate keys that is causing an update error. ' +
'Make sure each v-for item has a unique key.')}if (sameVnode(vnodeToMove, newStartVnode)) {
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue)
oldCh[idxInOld] = undefined
canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
} else {
// same key but different element. treat as new element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
}
}
newStartVnode = newCh[++newStartIdx]
}
}
if (oldStartIdx > oldEndIdx) {
refElm = isUndef(newCh[newEndIdx + 1])?null : newCh[newEndIdx + 1].elm
addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
} else if (newStartIdx > newEndIdx) {
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
}
}
Copy the code
Global method of Vue
core/global-api/index.js
- Platforms /web/entry- Runtime -with-compiler.js the Vue constructor is from this file
- This file defines the global API for Vue
- Calling initUse gives Vue the ability to register plug-ins through vue.use
- Calling initMixin gives Vue the ability to mix through vue.mixin
- The call to initExtend gives Vue the ability to generate a subclass of the Vue constructor via vue.extend (typically used to create components).
- Calling initAssetRegisters gives Vue the ability to implement directives with vue. directive, generate components with Vue.component, and define filters with vue.filter
Vue.use core/global-api/use.js
- In short, the install method is called, so custom plug-ins must have the install method (objects need to have the install property, classes need to have the install static method).
- If the custom plug-in is itself a method, it is called
function initUse(Vue) {
Vue.use = function (plugin) {
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
const args = toArray(arguments.1)
args.unshift(this) // put Vue in the first argument to call again for -> install(Vue)
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') {
plugin.apply(null, args)
}
installedPlugins.push(plugin)
return this}}Copy the code
Vue.mixin core/global-api/mixin.js
- The internal implementation is more complicated, but simply merge two objects (object.assign (VM, mixin)).
Vue.extend core/global-api/extend.js
- Extends Vue {} class Vue. Extends Vue {}
- The caller is Vue’s constructor, so this refers to Vue’s constructor
- Define Super = this, which refers to the constructor of Vue
- Define Sub, point its prototype to Super, and its constructor to itself, which completes inheritance
- Assign Sub to the VueComponent method, whose constructor points to the VueComponent method
- An internal call to this._init, because Sub’s prototype points to Vue’s constructor, calls the _init method of the superclass Vue
- Options = mergeOptions(super.options, extendOptions)
- Calling initProps and initComputed initializes the properties defined in its props and computed
- Extend/mixin/use, a global method that inherits from the parent class
- Inherits the parent class attribute Component/Directive/filter, defined in initAssetRegisters
- Return the Sub constructor
Vue.component / Vue.directive / Vue.filter
- Because the input parameters are the same, they are defined together
- The goal is simply to bind the Components/Directives/filters property to the VM and then store the defined methods as key-value pairs
/* Vue.component('child', { props: ['msg'], template: 'father said: {{msg}}
', mounted() { console.log('child mounted') } }) Vue.directive('focus', { inserted(el) { el.focus() } }) Vue.filter('child', options) */
const ASSET_TYPES = [
'component'.'directive'.'filter'
]
function initAssetRegisters (Vue) {
ASSET_TYPES.forEach(type= > {
Vue[type] = function (id, definition) {
if(! definition) {return this.options[type + 's'][id]
} else {
if(process.env.NODE_ENV ! = ='production') {
if (type === 'component' && config.isReservedTag(id)) {
warn(
'Do not use built-in or reserved HTML elements as component ' +
'id: ' + id
)
}
}
if (type === 'component' && isPlainObject(definition)) {
definition.name = definition.name || id
definition = this.options._base.extend(definition)
}
if (type === 'directive' && typeof definition === 'function') {
definition = { bind: definition, update: definition }
}
this.options[type + 's'][id] = definition
return definition
}
}
})
}
Copy the code