Vue2初渲染流程

Vue2初渲染流程

  • template模板 => ast 语法树(描述语法的)=> render函数 => 虚拟dom
  • new Vue时会产生一个watcher(渲染watcher) vm._update(vm._render()) 创建真实节点

获取template模版

  • render 有render直接使用
  • template 没有render看template
  • 最后是el找html找外部模板
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// init.js
export function initMixin(Vue){
Vue.prototype._init = function(options){
...
if(vm.$options.el){
vm.$mount(vm.$options.el)
}
}
Vue.prototype.$mount = function(el){
el = el && document.querySelector(el);
const vm = this;
const options = vm.$options;

vm.$el = el;
if(!options.render){
let template = options.template;
if(!template && el){
template = el.outerHTML;
}
...
}
...
}
}

模版编译成render函数

生成compileToFunctions函数主要流程

  • 模板编译成render函数
    • parseHtml 生成ast语法树
    • generate 变为一个_c(“div”,{a:1},_c())
    • 使用with包裹生成的字符串 let str = with(this){return ${code}} (渲染的时候去实例取值)
    • return new Function(str)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// compiler/index.js
import { parseHtml } from './parse';
import { generate } from './generate';

export function compileToFunctions(template){
// 生成 ast 语法树
let ast = parseHtml(template);

// ast 生成 虚拟dom
let code = generate(ast);

// 生成render函数
let render = `with(this){ return ${code} }`

// 把字符串变成一个函数
let fn = new Function(render);

// 返回render函数
return fn;
}

render函数挂在到 vm.options上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// init.js
import { mountComponent } from './lifecycle';

export function initMixin(Vue){
...

Vue.prototype.$mount = function (el){
el = el && document.querySelector(el);
const vm = this;
const options = vm.$options;

vm.$el = el;

if(!options.render){
let template = options.template;
if(!template && el){
template = el.outerHTML;
}
// template => render方法
// 1.处理模板变为ast树 2.标记静态节点 3.codegen=>return 字符串 4.new Function + with (render函数)
const render = compileToFunctions(template);
options.render = render; // 保证render一定有
}

mountComponent(vm);// 组件挂载
}
}

render函数生成虚拟dom

  • Vue 是通过Watcher渲染页面
  • Watcher 第二个参数是 updateComponent => vm._update(vm_render());
  • vm._render 产生虚拟节点
  • vm._update 把虚拟节点渲染为真实节点
1
2
3
4
5
6
7
8
9
10
11
// lifecycle.js
import Watcher from './observer/watcher';

export function mountComponent(vm){
// Watcher中会执行此方法
let updateComponent = () => {
vm._update(vm._render());
}
new Watcher(vm,updateComponent,()=>{},true);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// render.js

import { createElement, createTextVnode } from "./vdom/index.js";

export function renderMixin(Vue){
Vue.prototype._c = function(...args){ // 创建元素的虚拟节点
return createElement(this,...args);
}
Vue.prototype._v = function(text){ // 创建文本虚拟节点
return createTextVnode(this,text);
}
Vue.prototype._s = function(val){ // 转化成字符串
return val == null ?'':(typeof val == 'object')? JSON.stringify(val): val;
}
Vue.prototype._render = function(){
const vm = this;
let render = vm.$options.render; // 获取编译后的render方法

let vnode = render.call(vm); // 调用render方法产生虚拟节点 (会自动将值进行渲染)

render vnode; // 返回虚拟节点
}
}

// vdom/index.js
export function createElement(vm,tag,data= {},...children){
return vnode(vm,tag,data,data.key,children,undefined)
}

export function createTextVnode(vm,text){
return vnode(vm,undefined,undefined,undefined,undefined,text)
}

function vnode(vm,tag,data,key,children,text,componentOptions){
return { // 可以根据需求任意添加
vm,
tag,
data,
key,
children,
text,
componentOptions
}
}

虚拟dompatch渲染到页面

patch 分为初渲染和后续的dom-diff;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// lifecycle.js
import { patch } from './vdom/patch';
import Watcher from './observer/watcher';

export function lifecycle(Vue){
Vue.prototype._update = function(vnode){
const vm = this;

// 通过patch吧虚拟节点转为真实节点
vm.$el = patch(vm.$el,vnode); //组件渲染用patch方法后会产生$el属性
}
}

export function mountComponent(vm){
// Watcher中会执行此方法
let updateComponent = () => {
vm._update(vm._render());
}
new Watcher(vm,updateComponent,()=>{},true);
}

// vdom/patch.js
export function patch(oldVnode,vnode){ // oldVnode 是一个真实元素
const isRealElement = oldVnode.nodeType;
if(isRealElement){
const oldElm = oldVnode; // id="app"
const parentElm = oldElm.parentNode;// body
let el = createElm(vnode); // 根据虚拟节点创建真实的节点
parentElm.insertBefore(el,oldElm.nextSibling); // 将创建的节点插到原有的节点的下一个
parentElm.removeChild(oldElm);// 删除原有的节点

return el // vm.$el
}else{
// dom-diff
}
}

function updateProperties(vnode,oldProps = {}){
let newProps = vnode.data || {}; // 属性
let el = vnode.el; // 当前的真实元素

// 1.老的属性 新的没有 删除属性
for(let key in oldProps){
if(!newProps[key]){
el.removeAttribute(key);
}
}

let newStyle = newProps.style || {};
let oldStyle = oldProps.style || {};
for(let key in oldStyle){ // 判断样式新老先比对
if(!newStyle[key]){
el.style[key] = '';
}
}

// 2. 新的属性老的没有,直接用新的覆盖,不考虑有没有
for(let key in newProps){
if(key == 'style'){
for(let styleName in newProps.style){
el.style[styleName] = newProps.style[styleName]
}
}else if(key === 'class'){
el.className = newProps.class;
}else{
el.setAttribute(key,newProps[key])
}
}
}

export function createElm(vnode){
let { tag,children, key, data, text, vm } = vnode;

if(typeof tag === 'string'){
vnode.el = document.createElement(tag);
updateProperties(vnode); // 更新属性
children.forEach(child=>{
vnode.el.appendChild(createElm(child)); // 递归创建子元素
})
}else{
vnode.el = document.createTextNode(text)
}
return vnode.el;
}

初渲染完成,撒花 QAQ