6. 5. Smart Mattress
From (a) small dishes of chicken Vue source reading – new Vue () to do something, we say the entrance file is platforms/web/entry – runtime. Js, but this is actually the runtime Vue, actually Vue whole also contains a compiler (compile), Since Vue allows us to declare components as template strings, rendering still requires a render function, and the vue compiler mainly does a function that converts template strings into render functions.
A call to $mount during initialization mounts the component instance
Vue.prototype._init = function (options) {...// Initialize a bunch of things
// Mount the component instance
if(vm.$options.el) { vm.$mount(vm.$options.el); }}Copy the code
The flow chart
1. Vue compiler entry file
platforms/web/entry-runtime-with-compiler.js
Prototype.$mount,template there are three ways to write template, for these three ways need to do a uniform conversion to the template string
- String template
var vm = new Vue({
el: '#app'.template:
Template string
})
Copy the code
- The selector matches the element
innerHTML
The template
<div id="app">
<div>test1</div>
<script type="x-template" id="test"></script>
</div>
var vm = new Vue({
el: '#app'.template: '#test'
})
Copy
Copy the code
dom
Element matches the elementinnerHTML
The template
<div id="app">
<div>test1</div>
<span id="test"><div class="test2">test2</div></span>
</div>
var vm = new Vue({
el: '#app'.template: document.querySelector('#test')})Copy the code
To summarize
- Uniformly converted to template strings
- generate
render
andstaticRenderFns
function - As with runtime logic, the original is called
$mount
Function of the mount
// platforms/web/runtime/index.js
Vue.prototype.$mount = function (el? : string | Element,hydrating? : boolean) :Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
// platforms/web/entry-runtime-with-compiler.js
// $mount is already declared at runtime/index. It is a function that does not need to be compiled
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (el? : string | Element,hydrating? : boolean) :Component {
el = el && query(el)
// ...
const options = this.$options
if(! options.render) {let template = options.template
if (template) {
if (typeof template === 'string') {
// Selector matches
if (template.charAt(0) = = =The '#') {
template = idToTemplate(template)
// ...
}
// DOM elements match
} else if (template.nodeType) {
template = template.innerHTML
} else {
// ...
return this
}
// If template is not passed, use el as the root node to create the template
} else if (el) {
template = getOuterHTML(el)
}
if (template) {
// ...
// Generate the render function
const { render, staticRenderFns } = compileToFunctions(template, {
outputSourceRange: process.env.NODE_ENV ! = ='production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
// ...}}// Call the original logic
return mount.call(this, el, hydrating)
}
Vue.compile = compileToFunctions
Copy the code
2. Generation of render function
The flow chart
compileToFunctions
Convert the template string to the Render function
This function takes a template string, a compile configuration, and a VM instance
Vue.prototype.$mount = function () {...if(! options.render) {var template = options.template;
if (template) {
var ref = compileToFunctions(template, {
outputSourceRange: "development"! = ='production'.shouldDecodeNewlines: shouldDecodeNewlines,
shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
// Plain text inserts delimiters
delimiters: options.delimiters,
// Whether to keep comments in the template
comments: options.comments
}, this);
varrender = ref.render; }... }}Copy the code
Now we’re going to go to compileToFunctions where does this function come from
createCompiler
Build compiler
// platforms/web/compiler/index.js
const { compile, compileToFunctions } = createCompiler(baseOptions)
// compiler/index.js
// The argument passed is the core code for the entire compilation
export const createCompiler = createCompilerCreator(function baseCompile (template: string,options: CompilerOptions) :CompiledResult {
// Parse templates into abstract syntax trees
const ast = parse(template.trim(), options)
// The syntax tree will be optimized if the parameter configuration is optimized
if(options.optimize ! = =false) {
optimize(ast, options)
}
// The modified AST is then regenerated back to the code
const code = generate(ast, options)
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
})
Copy the code
createCompilerCreator
The builder that generates the compiler
The createCompilerCreator function has only one purpose. It uses the idea of partial functions to cache the base compiler method baseCompile and return a programmer generator.
// compiler/create-compiler.js
export function createCompilerCreator (baseCompile: Function) :Function {
return function createCompiler (baseOptions: CompilerOptions) {
function compile (template: string,options? : CompilerOptions) :CompiledResult {
const finalOptions = Object.create(baseOptions)
const errors = []
const tips = []
let warn = (msg, range, tip) = > {
(tip ? tips : errors).push(msg)
}
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 || null),
options.directives
)
}
// Copy the other attributes
for (const key in options) {
if(key ! = ='modules'&& key ! = ='directives') {
finalOptions[key] = options[key]
}
}
}
finalOptions.warn = warn
// The actual compiler function is executed
const compiled = baseCompile(template.trim(), finalOptions)
// ...
compiled.errors = errors
compiled.tips = tips
return compiled
}
return {
compile,
compileToFunctions: createCompileToFunctionFn(compile)
}
}
}
Copy the code
createCompileToFunctionFn
CreateCompileToFunctionFn using the concept of closure, the compiled template cache, the cache before compiled the results will be preserved, the cache can be used to avoid repeated compile waste caused by the performance. CreateCompileToFunctionFn will eventually compileToFunctions method returns.
// compiler/to-function.js
function createCompileToFunctionFn (compile) {
var cache = Object.create(null);
return function compileToFunctions (template,options,vm) { options = extend({}, options); ...// Caching is useful to avoid wasting performance by compiling the same template repeatedly
const key = options.delimiters
? String(options.delimiters) + template
: template
if (cache[key]) {
return cache[key]
}
// Execute the compile method
varcompiled = compile(template, options); ...// turn code into functions
var res = {};
var fnGenErrors = [];
// The compiled function body string is passed as an argument to createFunction, which returns the final render function
res.render = createFunction(compiled.render, fnGenErrors);
res.staticRenderFns = compiled.staticRenderFns.map(function (code) {
returncreateFunction(code, fnGenErrors) }); ...return (cache[key] = res)
}
}
function createFunction (code, errors) {
try {
return new Function(code)
} catch (err) {
errors.push({ err, code })
return noop
}
}
Copy the code
conclusion
createCompilerCreator(baseCompiler)(baseOptions).compileToFunctions(template,options,vm)
Copy the code
Here the compiler logic understanding is very uncomfortable, the author mainly refers to the instance mount process and template compilation · in-depth analysis of Vue source code (penblog.cn), here the idea of partial function refers to learning, The parse and generate functions in createCompilerCreator are the most important part of compiling, but they are so complicated that I won’t go into them here.
3. From render function to VDOM
Now we need to convert the render function to VDOM, after converting the VDOM to the real DOM and mounting it to the page, back to $mount where we started
Vue.prototype.$mount = function (el? : string | Element,hydrating? : boolean) :Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (el? : string | Element,hydrating? : boolean) :Component {
// ...
return mount.call(this, el, hydrating)
}
Copy the code
mountComponent
beforeMount
andmounted
Call to the hook function- Create an instance of Watcher where the updated callback function is
updateComponent
Continue to see laterupdateComponent
The declaration and implementation of this function
// core/instance/lifecycle.js
export function mountComponent (vm: Component,el: ? Element,hydrating? : boolean) :Component {
vm.$el = el
// ...
// Call beforeMount hook
callHook(vm, 'beforeMount')
updateComponent = () = > {
vm._update(vm._render(), hydrating)
}
new Watcher(vm, updateComponent, noop, {
before () {
if(vm._isMounted && ! vm._isDestroyed) { callHook(vm,'beforeUpdate')}}},true /* isRenderWatcher */)
hydrating = false
if (vm.$vnode == null) {
vm._isMounted = true
// Call mounted hook
callHook(vm, 'mounted')}return vm
}
Copy the code
vm._render
RenderMixin () declares the prototype method _render, which converts the render function into a Virtual DOM.
export function renderMixin (Vue: Class<Component>) {
// ...
Vue.prototype._render = function () :VNode {
// ...
try {
currentRenderingInstance = vm
// Generate the virtual DOM
vnode = render.call(vm._renderProxy, vm.$createElement)
} catch (e) {
// ...
vnode = vm._vnode
} finally {
currentRenderingInstance = null
}
// ...
return vnode
}
}
Copy the code
Vnode = render. Call (vm._renderProxy, vm.$createElement)
vm.renderProxy
This variable is actually added to _init in the initialization instance
Vue.prototype._init = function (options? :Object) {
// ...
if(process.env.NODE_ENV ! = ='production') {
// Data filtering detection in the development environment
initProxy(vm)
} else {
// Production is the vue instance itself
vm._renderProxy = vm
}
// ...
}
initProxy = function initProxy (vm) {
if (hasProxy) {
// determine which proxy handler to use
const options = vm.$options
const handlers = options.render && options.render._withStripped
? getHandler
: hasHandler
vm._renderProxy = new Proxy(vm, handlers)
} else {
vm._renderProxy = vm
}
}
Copy the code
vm.$createElement
$c and $createElement differ only in the last parameter of initRender (_init)
$c
: internal call in the render function converted via template string$createElement
Pass in the render function as an argument when you write it by hand
function initRender(vm) {
// ...
vm._c = function(a, b, c, d) { return createElement(vm, a, b, c, d, false); }
vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); };
// ...
}
// core/vdom/create-element.js
function createElement (
context, / / vm instances
tag, / / label
data, // Node-related data, attributes
children, / / child nodes
normalizationType,
alwaysNormalize // Distinguish between internal compiled render and handwritten render
) {
If there is no data, the third parameter is used as the fourth parameter, and so on.
if (Array.isArray(data) || isPrimitive(data)) {
normalizationType = children;
children = data;
data = undefined;
}
// alwaysNormalize distinguishes between internal compilation and user handwritten render
if (isTrue(alwaysNormalize)) {
normalizationType = ALWAYS_NORMALIZE;
}
// How to actually generate a Vnode
return _createElement(context, tag, data, children, normalizationType)
}
Copy the code
_createELement
Validation is performed on the incoming data, which guarantees subsequent VDOM generation
- The data object
data
It cannot be reactive data tag
withis
You have to do something special when you do dynamicskey
The value must be the original data type- native
DOM
Should not be usednative
The modifier - . Others don’t read the whole story and don’t seem to make much sense
Render is divided into two functions to convert children depending on whether the render is input by the user or generated by the system
normalizeChildren
: user input, merge of text nodes, recursive callsimpleNormalizeChildren
: generated by the system, mainly to do an array level flattening
export function _createElement (context: Component, tag? : string | Class<Component> |Function | Object, data? : VNodeData, children? : any, normalizationType? : number) :VNode | Array<VNode> {
// 1. Data objects cannot be reactive data defined in the Vue data attribute.
if(isDef(data) && isDef((data: any).__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()
}
// 2. Special processing is required when using is as a dynamic tag
if (isDef(data) && isDef(data.is)) {
tag = data.is
}
if(! tag) {return createEmptyVNode()
}
// 3. The key value must be the original data type
if(process.env.NODE_ENV ! = ='production'&& isDef(data) && isDef(data.key) && ! isPrimitive(data.key) ) {if(! __WEEX__ || ! ('@binding' in data.key)) {
warn(
'Avoid using non-primitive value as key, ' +
'use string/number value instead.',
context
)
}
}
// The first element of the children function type is used as the default slot
if (Array.isArray(children) &&
typeof children[0= = ='function'
) {
data = data || {}
data.scopedSlots = { default: children[0] }
children.length = 0
}
// This is what I said before, the difference between self-written render and framework-generated render
// If children is a simple data type, the text virtual DOM needs to be generated
if (normalizationType === ALWAYS_NORMALIZE) {
children = normalizeChildren(children)
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children)
}
// ...
}
// core/vdom/helpers/normalize-children.js
// Handle the compile-generated render function
export function simpleNormalizeChildren (children: any) {
for (let i = 0; i < children.length; i++) {
// If the child node is an array, perform the flattening operation to create a one-dimensional array.
if (Array.isArray(children[i])) {
return Array.prototype.concat.apply([], children)
}
}
return children
}
// Handle the user-defined render function
export function normalizeChildren (children: any): ?Array<VNode> {
return isPrimitive(children)
// Generate a literal node
? [createTextVNode(children)]
: Array.isArray(children)
? normalizeArrayChildren(children)
: undefined
}
function normalizeArrayChildren (children: any, nestedIndex? : string) :Array<VNode> {
const res = []
let i, c, lastIndex, last
// Iterate over the child nodes
for (i = 0; i < children.length; i++) {
c = children[i]
if (isUndef(c) || typeof c === 'boolean') continue
lastIndex = res.length - 1
last = res[lastIndex]
// The current child is an array
if (Array.isArray(c)) {
if (c.length > 0) {
// Recurse for children
c = normalizeArrayChildren(c, `${nestedIndex || ' '}_${i}`)
// If the first child of the currently traversed child node is a literal node, and the last byte point of the current literal node is also, the two are merged and the first byte point of the child node is removed
if (isTextNode(c[0]) && isTextNode(last)) {
res[lastIndex] = createTextVNode(last.text + (c[0]: any).text)
c.shift()
}
res.push.apply(res, c)
}
// The current child node is a simple data type
} else if (isPrimitive(c)) {
// Continue merging
if (isTextNode(last)) {
// Merge the last text node with the last one
res[lastIndex] = createTextVNode(last.text + c)
} else if(c ! = =' ') {
// If it is not a text node, create a text node for the current text and insert it at the end
res.push(createTextVNode(c))
}
} else {
// The current traversal is a text node, and the last is also a text node, then merge
if (isTextNode(c) && isTextNode(last)) {
res[lastIndex] = createTextVNode(last.text + c.text)
} else {
// Add a key value if the key value does not exist and is a list
if (isTrue(children._isVList) &&
isDef(c.tag) &&
isUndef(c.key) &&
isDef(nestedIndex)) {
c.key = `__vlist${nestedIndex}_${i}__ `
}
res.push(c)
}
}
}
return res
}
Copy the code
4. Map from VDOM to real DOM
updateComponent = function () {
// render generates the virtual DOM, update renders the real DOM
vm._update(vm._render(), hydrating);
};
Copy the code
vm._update
The change method is added at lifecycleMixin()
function lifecycleMixin() {
Vue.prototype._update = function (vnode, hydrating) {
var vm = this;
var prevEl = vm.$el;
// prevVnode is the old vNode node
var prevVnode = vm._vnode;
// Check whether there are old nodes to determine whether it is the first rendering or data update
// First render
if(! prevVnode) { vm.$el = vm.__patch__(vm.$el, vnode, hydrating,false)}else {
// Data updatevm.$el = vm.__patch__(prevVnode, vnode); }}Copy the code
__patch__
reatePatchFunction
Method passes an object as a parameter that has two properties,nodeOps
andmodules
.nodeOps
Encapsulates a series of operational primitivesDOM
Object method. whilemodules
Defines a hook function for a modulecreatePatchFunction
The function has over a thousand lines and I’m not going to list them here, but basically what’s going on inside it first defines a bunch of helper methods, and the core is through calls, okaycreateElm
methodsdom
Operation, create node, insert child node, recursively create a completeDOM
Tree and insert intoBody
In the. And in the phase of producing reality, there will bediff
Algorithm to determine before and afterVnode
In order to minimize the real phase of change. There will be a chapter to explain it laterdiff
Algorithm.createPatchFunction
You just need to remember a few conclusions, and the function will be called internally wrappedDOM api
, according to theVirtual DOM
To generate real nodes. If a component is encounteredVnode
, the mount procedure of the child component is recursively invoked
The number.
// platforms/web/runtime/index.js
Vue.prototype.__patch__ = inBrowser ? patch : noop
// platforms/web/runtime/patch.js
export const patch: Function = createPatchFunction({ nodeOps, modules })
// Freeze a collection of methods that manipulate dom objects
var nodeOps = /*#__PURE__*/Object.freeze({
createElement: createElement$1.createElementNS: createElementNS,
createTextNode: createTextNode,
createComment: createComment,
insertBefore: insertBefore,
removeChild: removeChild,
appendChild: appendChild,
parentNode: parentNode,
nextSibling: nextSibling,
tagName: tagName,
setTextContent: setTextContent,
setStyleScope: setStyleScope
});
// Defines the module's hook function
var platformModules = [
attrs,
klass,
events,
domProps,
style,
transition
];
var modules = platformModules.concat(baseModules);
Copy the code
Copy the code