写文章不容易,点个赞呗兄弟
专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧
研究基于 Vue 版本 【2.5.17】
如果你觉得排版难看,请点击 下面链接 或者 拉到 下面 关注公众号 也可以吧
【Vue 原理】从模板到 DOM 的简要流程
今天的计划是,探索 Vue 模板挂载到页面是怎么样的一个流程,内容是指 正常 HTML 标签的模板挂载,这部分内容很重要。
而这部分内容也是为了 讲解 Component 作为铺垫,因为到最后 Component 必然也是作为一个正常标签去挂载,所以先把这部分抽出来讲
首先,这个流程,个人认为可以分为两大部分,分别是 init 和 mount
顾名思义,init 必定是和初始化有关,mount 和 挂载 DOM 有关
Init
首先,当你开始调用 Vue 的时候,比如这样
// js
new Vue({el: document.getElementsByTagName("div")[0],
})
// html,够简洁了吧
<div></div>
那么,先进入的肯定是 Vue 这个构造函数,呈上来!
function Vue(options) {this._init(options);
}
Vue.prototype._init = function(options) {
// 初始化 选项,computed,data 之类的
// 初始化实例,给实例绑定些方法
// 触发 beforeCreated,created 钩子
}
这个 _init 方法,是构建 Vue 实例的时候调用的,而创建 Vue 实例,并非只有通过 new Vue 创建,有可能是 Vue 内部创建的,比如 component
所以,才需要提取出一个 init 方法
然后,init 到这里就结束了,下面就到了另一个流程 mount
Mount
init 结束,就开始解析模板啦,生成 DOM 啦,挂载 DOM 啦 之类的
开始正文,首先,从什么时候开始?此时需要亮出 _init 方法,没错,就是上面出现的方法
其实在这个方法的最后,有一个调用执行挂载 DOM 的方法,如下
Vue.prototype._init = function(options) {
.....
if (vm.$options.el) {vm.$mount(vm.$options.el);
}
}
可以看到一句代码,vm.$mount,没错,就在这里开启了 DOM 挂载的 里程碑
但是,等等,有限制条件 vm.$options.el,也就是,必须有传入 el 才会在 最后调用 挂载 DOM
所以,并不是所有的 Vue 实例新建都会在 init 结尾调用 vm.$mount 去挂载 DOM,比如 component 两个过程就是分开的
我们还是先来看看 vm.$mount 吧
Vue.prototype.$mount = function(el) {return mountComponent(this, query(el))
};
var mount = Vue.prototype.$mount;
Vue.prototype.$mount = function(el) {
... 解析模板,生成模板渲染函数,保存渲染函数到 options
return mount.call(this, el)
}
原样呈现了,Vue 中有两个 $mount 函数,第一个的作用是给第二个 调用...... 如果大家看源码,不要搞混了喂
其中涉及到一个函数,mountComponent,速看
function mountComponent(vm, el) {new Watcher(vm, function() {vm._update(vm._render()
})
return v
}
function Watcher(vm, expOrFn) {
this.getter = expOrFn;
this.get();}
Watcher.prototype.get = function() {value = this.getter(vm);
}
上面代码的作用可以说是,为 Vue 实例新建监听者 watcher,并设置一个更新函数
而这个更新函数,会在新建 watcher 后 马上执行,就是马上执行了一遍这行代码
vm._update(vm._render())
1、vm._render
这个函数的作用是,执行之前解析得到的【渲染函数】,渲染函数执行完会返回一个 模板对应的【VNode】
vm._render 再把这个 vnode 返回
于是就把这个 vnode,传给了 vm._update 中当做了第一个参数
render 函数的内容其实非常的多,但是这里一笔带过,只用知道是用来生成 Vnode 就好了,具体的内容会有具体的文章讲解
Vue.prototype._render = function() {vnode = render();
return vnode
}
2、vm._update
这个函数的作用是,对比 vnode,挂载更新 DOM
1、如果存在旧 vnode,那么会对比旧 vnode 和 刚传入的新 vnode,不断地 patch 得到最小变化单位,从而只更新这部分 DOM
2、如果不存在旧 vnode,那么就直接把 vnode 转换为 dom 挂载到页面
其中,生成 DOM 和 挂载 DOM 用到的方法是 createElm
方法很简单,无非就是通过 标签名创建 DOM,然后插入到页面中
function createElm(vnode, parentElm, refElm) {
var children = vnode.children;
var tag = vnode.tag;
vnode.elm = document.createElement(tag);
// 不断递归遍历子节点
createChildren(vnode, children);
// 插入 DOM 节点
insert(parentElm, vnode.elm, refElm);
}
function createChildren(vnode, children) {if (Array.isArray(children)) {for (var i = 0; i < children.length; ++i) {createElm(children[i], vnode.elm, null);
}
}
}
function insert(parent, elm, ref) {if (parent) {
// 如果存在兄弟节点,就查到兄弟前面
if (ref) {
// 兄弟节点的父节点和 本节点父节点相同
if (ref.parentNode === parent) {parent.insertBefore(elm, ref);
}
}
// 如果没有兄弟节点,就直接查到父节点最后
else {parent.appendChild(elm);
}
}
}
总结
两个过程如下
init
1、初始化选项
2、初始化实例
mount
1、解析模板,生成并保存渲染函数
2、新建 watcher 并立即执行更新函数 vm._update(vm._render)
3、vm._render 调用渲染函数生成 VNode,传给 vm._update
4、调用 vm._update,根据 VNode 生成 DOM 并挂载