What does new Vue() do when you quickly understand it? (below)
After the initialization phase, it is time to mount the components, but it is important to mention the concept of the virtual Dom before mounting. We know that [email protected] introduced the virtual Dom. The main problem is that in most cases, it can reduce the expensive performance required by using JavaScript to manipulate the large Dom across threads, and make Dom operation performance higher. And the virtual Dom can be used for SSR and cross-end use. The virtual Dom, as its name implies, is not a real Dom, but rather a representation of the real Dom using JavaScript objects. A real Dom is nothing more than a tag name, attribute, child node, etc. to describe it. For example, a real Dom in a page looks like this:
<div id='app' class='wrap'>
<h2>
hello
</h2>
</div>
Copy the code
We can describe it in the render function like this:
new Vue({
render(h) {
return h('div', {
attrs: {
id: 'app',
class: 'wrap'
}
}, [
h('h2'.'hello')]}})Copy the code
This time it is not described with objects, using the data structure in the render function to describe the real Dom, and now we need to convert this description into the form of objects, the render function is using the parameter H method and the VNode class to instantiate them, so before we understand the implementation principle of H, Let’s take a look at what the VNode class is and find out where it is defined:
exportdefault class VNode { constructor ( tag data children text elm context componentOptions asyncFactory ) { this.tag = tag This. data = data // attributes such as id/class this.children = children // child this.text = text // text content this.elm = elm // This. Context = context // This. FnContext = undefined // This Function component context this.fnOptions = undefined // Function component configuration this.fnScopeId = undefined // Function component ScopeId this.key = data && data.key // Node binding keys such as V -for This.componentOptions = componentOptions // VNode options this.componentInstance = undefined // Componentinstance This.parent = undefined // placeholder of the vNode component this.raw =false// Whether it is a platform tag or text this.isStatic =false// Static node this.isrootinsert =true// Whether to insert this.iscomment = as the rootfalse// Whether the annotation node this.isverification =false// Whether to clone the node this.isonce =falseAsyncMeta = undefined // asyncMeta = this.isAsyncPlaceholder = asyncplaceholderfalse// Whether it is an asynchronous placeholder} getchild() {// Aliasreturn this.componentInstance
}
}
Copy the code
This is where the VNode class is defined, and it’s scary. It supports up to eight arguments in total, but it’s not used very often. For example, tag is the name of the element node, children is its child node, and text is the text within the text node. The instantiated object has twenty-three attributes that describe a node inside the VUE, which describes how it will be created as a real Dom. Most attributes default to false or undefined, and valid values for these attributes can be used to assemble different descriptions, such as element nodes, text nodes, comment nodes, etc., in the real Dom. Through such a VNode class, corresponding nodes can also be described. Some nodes are also encapsulated in VUE:
Comment Node ↓
export const createEmptyVNode = (text = ' ') => {
const node = new VNode()
node.text = text
node.isComment = true
return node
}
Copy the code
- Create an empty
VNode
, the valid attribute is onlytext
andisComment
To represent a comment node.
Real comment nodes: <! VNode createEmptyVNode ('Comment node')
{
text: 'Comment node',
isComment: true
}
Copy the code
Text node ↓
export function createTextVNode (val) {
return new VNode(undefined, undefined, undefined, String(val))
}
Copy the code
- It’s just set up
text
Property, which describes the text inside the tag
VNode createTextVNode('Text node')
{
text: 'Text node'
}
Copy the code
Clone node ↓
export function cloneVNode (vnode) { const cloned = new VNode( vnode.tag, vnode.data, vnode.children, vnode.text, vnode.elm, vnode.context, vnode.componentOptions, vnode.asyncFactory ) cloned.ns = vnode.ns cloned.isStatic = vnode.isStatic cloned.key = vnode.key cloned.isComment = vnode.isComment cloned.fnContext = vnode.fnContext cloned.fnOptions = vnode.fnOptions cloned.fnScopeId = vnode.fnScopeId cloned.asyncMeta = vnode.asyncMeta cloned.isCloned =true
return cloned
}
Copy the code
- Put an existing one
VNode
A copy of the node, only of the node being copiedisCloned
Properties forfalse
, while copying the obtained nodeisCloned
Properties fortrue
Except that they are identical.
Element node ↓
Real element node: <div> hello <span>Vue! </span> </div>'div',
children: [
{
text: 'hello'
},
{
tag: 'span',
children: [
{
text: Vue!
}
]
}
],
}
Copy the code
Component node ↓
New Vue({render(h) {render(h) {returnH (App)}}) VNode'vue-component-2', componentInstance: {... }, componentOptions: {... }, context: {... }, data: {... }}Copy the code
- The component’s
VNode
There are two unique attributes compared to element nodescomponentInstance
andcomponentOptions
.VNode
There are many types of, and they all come from thisVNode
Class instantiated, just different properties.
Start the mount phase
The end of the this._init() method:... Initialize theif (vm.$options.el) {
vm.$mount(vm.$options.el)
}
Copy the code
If the user has an incoming EL attribute, the vm.$mount method is executed and passed el to begin the mount. The $mount method here is slightly different in the full and runtime versions as follows:
Runtime version: vue.prototype.$mount = function(el) {// Initial definitionreturnmountComponent(this, query(el)); } const mount = vue.prototype$mount
Vue.prototype.$mount = function(el) {// extend the compiledif(! this.$options.render) { ---|
if(this.$options.template) { ---| ... After the compiler conversion get render function - | compilation phase} -- -- -- -- - | |}return mount.call(this, query(el))
}
-----------------------------------------------
export functionQuery (el) {// Get the mounted nodeif(typeof el === 'string') {// For example#app
const selected = document.querySelector(el)
if(! selected) {return document.createElement('div')}return selected
} else {
return el
}
}
Copy the code
The full version of the operation, first to cache the $mount method on the mount variable, then use function hijacking to redefine the $mount function, add compile-related code inside it, and finally use the original definition of the $mount method to mount. So the key is to understand the mountComponent method in the original $mount method definition:
export function mountComponent(vm, el) {
vm.$el = el
...
callHook(vm, 'beforeMount')... const updateComponent =function () {
vm._update(vm._render())
}
...
}
Copy the code
We first assign the passed EL to vm.$el, which is a real DOM, and then execute the user-defined beforeMount hook. Next we define an important function variable, updateComponent, which internally executes the vm._render() method, passing the result back to vm._update() for execution. In this chapter we’ll focus on what the vm._render() method does and take a look at its definition:
Vue.prototype._render = function() {
const vm = this
const { render } = vm.$options
const vnode = render.call(vm, vm.$createElement)
return vnode
}
Copy the code
We first get our custom render function, pass in the vm.$createElement method (h, above), and assign the result to vNode. This completes the rendering of the data structure inside the render function to vNode. The vm.$createElement is mounted to the VM instance within the initRender initialization method:
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) // Compile the VM.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true) / / handwritingCopy the code
The render functions, whether compiled or written, return createElement, and continue to look for its definition:
const SIMPLE_NORMALIZE = 1
const ALWAYS_NORMALIZE = 2
export default createElement(
context,
tag,
data,
children,
normalizationType,
alwaysNormalize) {
if(Array. IsArray (data) | | isPrimitive (data)) {/ / data is an Array or base type normalizationType = children - | children = data - | displacement parameters data = undefined --| }if(isTrue(alwaysNormalize)) {// Render normalizationType = ALWAYS_NORMALIZE}return _createElement(contenxt, tag, data, children, normalizationType)
}
Copy the code
If the third argument is passed in an array (child element) or a value of the underlying type, the position of the argument is changed. It then does something about whether the last argument passed in is true or false, which determines what happens to the children property afterwards. Here’s another encapsulation of _createElement, so we’ll continue to look at its definition:
export function _createElement(
context, tag, data, children, normalizationType
) {
if(normalizationType === ALWAYS_NORMALIZE) {// Render function children = normalizeChildren(children)}else if(normalizationType === SIMPLE_NORMALIZE) {// compile render function children = simpleNormalizeChildren(children)}if(typeof tag === 'string') {// taglet vnode, Ctor
if(config.isReservedTag(tag)) {// Vnode = new vnode (tag, data, children, undefined, undefined, context)}... }elseVnode = createComponent(tag, data, context, children)}...return vnode
}
Copy the code
First we’ll see that we do different things to children for the Boolean value of the last argument, formatting children as a one-dimensional array if the render function is compiled:
functionSimpleNormalizeChildren (children) {// Compile the render handlerfor (let i = 0; i < children.length; i++) {
if (Array.isArray(children[i])) {
return Array.prototype.concat.apply([], children)
}
}
return children
}
Copy the code
We’ll now focus on the hand-written render function. From the _createElement method below, we know that converting a VNode is divided into two cases:
1. The normal element node is converted to
VNode
Using the children array as an example, let’s show how a normal element can be converted to a VNode:
render(h) {
return h(
"div",
[
[
[h("h1"."title h1")],
[h('h2'."title h2")]
],
[
h('h3'.'title h3')]]); }Copy the code
Because the _createElement method encapsulates the h method, the first argument to the H method corresponds to the tag in the _createElement method, and the second argument corresponds to the data. H (‘h1’, ‘title h1’); children (‘ title h1’, ‘title h1’); So the normalizeChildren method converts it to [createTextVNode(children)] a text VNode:
functionNormalizeChildren (children) {// Hand render's handler functionreturnIsPrimitive typeof (children) / / original type string/number/symbol/one of Boolean? [createTextVNode(children)] // Convert an Array to a text node: array.isarray (children) // If an Array? normalizeArrayChildren(children) : undefined }Copy the code
This condition in the _createElement method is then satisfied:
if(typeof tag === 'string'){tag indicates the H1 tagif(config.isReservedTag(tag)) {vnode = new vnode (tag, // h1 data, // undefined children, convert to [{text:'title h1'}]
undefined,
undefined,
context
)
}
}
...
returnVnode returns the vnode structure: {tag: h1, children: [{text: title h1}]}Copy the code
H (‘h2’, “title h2”), and h(‘h3’, ‘title h3’) will yield three VNode instances. The outermost h(div, [[VNode,VNode],[VNode]]) method is then executed. Notice that it is a two-dimensional Array, and it satisfies the array. isArray(children) condition in normalizeChildren. The normalizeArrayChildren method is executed:
functionNormalizeArrayChildren (children) {const res = [for(leti = 0; i < children.length; I++) {// iterate over each termlet c = children[i]
if(isUndef(c) || typeof c === 'boolean') {// If undefined or Booleancontinue/ / skip}if(array.isarray (c)) {// If an item is an Arrayif(c.length > 0) {c = normalizeArrayChildren(c) // The recursive result assigned to c is [VNode]... Merge the adjacent text node res.push.apply(res, c) // small operation}}else{... res.push(c) } }return res
}
Copy the code
If an item in children is an array, it calls itself recursively, passing itself in and overwriting itself with the result returned by res.push(c), where c is the array structure of [VNode]. Apply (res, c) to add to res. Here vue shows a small operation, pushing an array inside an array, which is supposed to be a two-dimensional array, res.push. Apply (res, c), and the result is a one-dimensional array. Res returns the final result [VNode, VNode, VNode], which is what children will look like. H (‘div’, [VNode, VNode, VNode]) satisfies the same condition as before:
if(config.isReservedTag(tag)) {// tag is div vnode = new vnode (tag, data, children, undefined, undefined, context)}return vnode
Copy the code
So the resulting VNode structure looks like this:
{
tag: 'div',
children: [VNode, VNode, VNode]
}
Copy the code
This is how a common element node is converted to a VNode.
2. Components are converted to
VNode
Let’s take a look at the creation process of the component VNode. The following is a common example:
main.js
new Vue({
render(h) {
return h(App)
}
})
app.vue
import Child from '@/pages/child'
export default {
name: 'app',
components: {
Child
}
}
Copy the code
I don’t know if you have printed the imported component directly, but let’s print the App component in main.js:
{beforeCreate: [ƒ] beforeDestroy: [ƒ] Components: {Child: {... }} name:"app"ƒ () : [] __file:"src/App.vue"
_compiled: true
}
Copy the code
We just defined the name and components properties, why are there so many properties printed out? This is added after vue-loader parsing. For example, render: ƒ () is converted from the template template of App component, and we can remember this as a component object.
Let’s take a quick look at the previous _createElement function:
export function _createElement(
context, tag, data, children, normalizationType
) {
...
if(typeof tag === 'string') {// tag... }elseVnode = createComponent(tag, // component object data, // undefined context, // current VM instance children // undefined)}...return vnode
}
Copy the code
It is clear that the tag is not a string, and the createComponent() method is called instead:
export functionCreateComponent (// on Ctor, data = {}, context, children, tag) {const baseCtor = context.$options._base
if(isObject(Ctor)) {// Component object Ctor = baseCtor. Extend (Ctor) // Subclass Vue}... }Copy the code
One thing to add here is that when defining the global API before new Vue() :
export function initGlobalAPI(Vue) {
...
Vue.options._base = Vue
Vue.extend = function(extendOptions){...}
}
Copy the code
$options._base = context.$options._base = context.$options._base = context.$options._base = context.$options._base = context.$options._base = context.$options._base = context.$options._base = context.$options._base
Vue.cid = 0
let cid = 1
Vue.extend = function(extendOptions = {}) {const Super = this / / Vue base class constructor const name = extendOptions. Name | | Super. Options. The name const Sub =function} sub.prototype = object.create (super.prototype) // Vue inherited base class initializes the definition method of prototype Sub. Prototype. The constructor = Sub / / Sub constructor to subclass cid = cid++ Sub. The options = mergeOptions (/ / subclass merge options Super.options, // Components, directives, filters, _base extendOptions // incoming component object) Sub['super'] = Super // Vue base class // Assign static methods of base class to subclasses sub.extend = super.extend sub.mixin = super.mixin sub.use = super.use ASSET_TYPES.forEach(function (type{/ / /'component'.'directive'.'filter']
Sub[type] = Super[type]})if(name) {let the component recursively call itself, So be sure to define the name attribute Sub.options.components[name] = Sub // mount the subclass to its components attribute} sub.superoptions = super.options Sub.extendOptions = extendOptionsreturn Sub
}
Copy the code
If we look at the extend method carefully, we can see that the component object we pass is the same as the options in the new Vue(Options), and then we merge it with the prototype method defined before the Vue and the global API, and return a new constructor. It has the full functionality of Vue. Let’s move on to the other logic of createComponent:
export functionCreateComponent (// middle Ctor, data = {}, context, children, tag) {... On = data.nativeon // Native event of the component installComponentHooks(data) // Add hook methods to components... }Copy the code
On is the event object passed by the parent component to the child component and assigned to the variable listeners. Data.nativeon is an event bound to a component with a native modifier. InstallComponentHooks is used to attach hooks to the component’s data property, including init, prepatch, insert, destroy, and so on. These four methods will be used later in the patch phase to convert vNodes into real Dom, and we will see what their definitions are when we use them. We continue with the other logic of createComponent:
export functionCreateComponent (// 下 Ctor, data = {}, context, children, tag) {... Const name = Ctor. Options. The name | | tag / / stitching component tag with const vnode = new vnode (/ / create components vnode ` vue - component -${Ctor.cid}${name ? `-${name}` : ' '} ', // corresponding to the tag attribute data, // the parent component passes custom events and the mounted hook object undefined, // corresponding to the children attribute undefined, // corresponding to the text attribute undefined, ComponentOptions {// componentOptions Ctor, // subclass constructors propsData, // props object sets of specific values, // The parent component passes the custom event object collection tag, // the name of the component is children // the contents of the slot, also in VNode format}, asyncFactory)return vnode
}
Copy the code
Vnodes generated by the component are as follows:
{
tag: 'vue-component-1-app', context: {... }, componentOptions: { Ctor:function() {... }, propsData: undefined, children: undefined, tag: undefined, children: undefined }, data: { on: Undefined, // data: {init:function() {... }, insert:function() {... }, prepatch:function() {... }, destroy:function() {... }}}}Copy the code
If you see a tag attribute that starts with vue-Component, it is a component. This is the initialization of a component VNode. If h is a component object, it is converted to a subclass of Vue. The children, text, ele of VNode are undefined, but its unique attribute componentOptions holds the information required by the component. Their VNodes are generated, and we’ll use them in the next section to make them real Dom~.
We’ll end this chapter with an interview question that Vue is likely to be asked
The interviewer smiled politely and asked,
- Excuse me,
vue@2
Why virtualDom
Talk about virtualDom
Understand?
Dui back:
- As modern applications demand more and more complex functions from pages, more and more states can be managed, if not used before
JavaScript
Threads to operate frequentlyGUI
The enormity of threadsDom
, will have a lot of performance loss, and will also cause difficult state management, logic confusion, etc. The introduction of the virtualDom
After that, the inside of the frame will be virtualDom
Tree structure and realityDom
It’s mapped so we don’t have to do it in imperativeDom
, you can shift the center of gravity to maintain the state within the tree structure, and the state changes will driveDom
Change, specificDom
operationvue
Help us do it, and most of this can be done hereJavaScript
Thread completion, higher performance. - virtual
Dom
It’s just a data structure that allows it to be used not only in the browser environment, but also withSSR
As well asWeex
Such scenarios.
Next: Vue principle analysis (five) : thoroughly understand the virtual Dom to the real Dom generation process
Easy to click a like or follow bai, also easy to find ~
Reference:
Vue. Js source code comprehensive in-depth analysis
Vue.js is easy to understand
Share a component library for everyone, may use up ~ ↓
A library of vUE functional components that you might want to use.