vue源码解析之编译过程-含2种模式(及vue-loader作用)
自从上一次内部分享会分享了 vue的diff算法 后,小伙伴们一致对vue的源码感兴趣,那就整吧
注:以下内容只讲了几个关键步骤,个人觉得,先把关键步骤了解明白,知道关键步骤的 输入和输出 后,自己也可以尝试手写实现
2种模式指的是
- .html文件模式。.html文件内使用vue,没有vue-loader
- 执行.html文件 是vue的最基本的执行,不用加入vue-loader。先了解这个过程,后续更好理解vue-loader做了什么
- .vue文件模式。使用webpack工程,用vue-loader解析.vue 文件
编译过程
(2种模式,大部分过程是相同的,就获取 匿名渲染函数 上有所不同)
- 先初始化各种属性和方法
- .html文件模式:
- 拿到#app对应的dom代码字符串template
- 编译template生成ast树 和 匿名渲染函数
- .vue文件模式:
- vue-loader编译.vue文件,得到 匿名渲染函数
- 执行 vm._update(vm._render(), hydrating); // (关键函数) 渲染/更新 函数
- 先执行vm._render(),通过 执行 匿名渲染函数,得到 虚拟dom树vnode
- 在执行vm._update() ,层层递归 虚拟dom树vnode,得到真正的dom节点,然后update到真正的dom树上去,然后浏览器渲染出最新的dom树
.html文件模式的编译过程
执行.html文件 是vue的最基本的执行,不用加入vue-loader。先了解这个过程,后续更好理解vue-loader做了什么
测试文件:.html文件
- CDN引入vue的未压缩版,在script标签内,直接使用vue
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> </head> <body> <div id="app"> {{aa}} --- 1 <div @click="qqq">click me</div> {{C_aa}} </div> <script type="module"> debugger new Vue({ el: '#app', data: { aa: 123 }, watch: { aa (nval, oval) { console.log(nval, oval) } }, computed: { C_aa () { return this.aa + 100 } }, methods: { async qqq () { this.aa = this.aa + 1 } } }) </script> </body> </html>
以下按源代码执行顺序,从上到下
-
先初始化各种属性和方法
initLifecycle(vm); // 初始化生命周期 initEvents(vm); // 初始化事件 initRender(vm); // 初始化,处理渲染模板的函数 callHook(vm, 'beforeCreate'); // 执行 beforeCreate initInjections(vm); // 初始化inject before data/props initState(vm); // 初始化 props,methods,data,computed,watch initProvide(vm); // 初始化provide after data/props callHook(vm, 'created'); // 执行created -
拿到#app对应的dom代码字符串
template = getOuterHTML(el);
打印template就是:
"<div id="app"> {{aa}} --- 1 <div @click="qqq">click me</div> {{C_aa}} </div>" -
编译template生成ast树 和 匿名渲染函数
var compiled = compile(template, options);
-
得到ast树(好处是 方便解析一些vue在标签内的一些特定语法,最终有助于生成匿名渲染函数)
compiled: { ast: 太大了 如图, }树结构,子元素都在children内
-
得到 匿名渲染函数
compiled: { render: with(this){return _c('div',{attrs:{"id":"app"}},[_v("\n "+_s(aa)+" --- 1\n "),_c('div',{on:{"click":qqq}},[_v("click me")]),_v("\n "+_s(C_aa)+"\n")])} }通过 createFunction(compiled.render, fnGenErrors); 得到 匿名渲染函数
(function anonymous( ) { with(this){return _c('div',{attrs:{"id":"app"}},[_v("\n "+_s(aa)+" --- 1\n "),_c('div',{on:{"click":qqq}},[_v("click me")]),_v("\n "+_s(C_aa)+"\n")])} })还会把这个 匿名渲染函数 的结果缓存起来,key是上面的template,value是匿名渲染函数
-
-
callHook(vm, 'beforeMount'); // 执行beforeMount
-
执行 vm._update(vm._render(), hydrating); // (关键函数) 渲染/更新 函数
- 先执行vm._render(),通过 执行 匿名渲染函数,得到 虚拟dom树vnode
- 虚拟dom树vnode是用js对象去表示dom树
- 操作js要比操作真实的dom性能高很多,特别是做 新旧vnode之间的 diff算法 的时候
// 执行 匿名渲染函数,得到vnode 虚拟dom树 vnode = render.call(vm._renderProxy, vm.$createElement); vnode结构如下图 - 在执行vm._update() ,层层递归 虚拟dom树vnode,得到真正的dom节点,然后update到真正的dom树上去,然后浏览器渲染出最新的dom树
diff算法的细节,可以看我另一篇:juejin.cn/post/685003…if (!prevVnode) { // 第一次渲染 /* 第一次渲染,里面通过递归,一层一层的 createElement 生成真正的dom, 在appendChild到页面上,在removeChild #app的dom */ vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */); } else { /* updates 通过diff算法,一层一层的对比新旧vonde(时间复杂是n), 高效的得到新旧vnode的差异,然后根据vnode生成真实的dom,并update到真实的dom树中 */ vm.$el = vm.__patch__(prevVnode, vnode); // 旧vnode 和 新vnode }
- 先执行vm._render(),通过 执行 匿名渲染函数,得到 虚拟dom树vnode
-
callHook(vm, 'mounted'); // 执行mounted,此时,真实的dom树已经好了,可以获取到dom元素了
-
调用结束
.vue文件模式的编译过程
测试文件:.vue文件(内容同上)
-
使用webpack工程,用vue-loader解析.vue 文件
app.vue
<template> <div id="app"> {{aa}} --- 1 <div @click="qqq">click me</div> {{C_aa}} </div> </template> <script> export default { name: 'App', data () { return { aa: 123 } }, watch: { aa (nval, oval) { console.log(nval, oval) } }, computed: { C_aa () { return this.aa + 100 } }, methods: { async qqq () { this.aa = this.aa + 1 } } } </script>main.js
import Vue from 'vue' import App from './App.vue' console.log(App) debugger new Vue({ render: h => { debugger console.log(h(App)) return h(App) } }).$mount('#app')
以下按源代码执行顺序,从上到下
-
先初始化各种属性和方法(同.html文件模式)
-
通过vue-loader编译.vue文件,得到 匿名渲染函数
- 先看看vue-loader编译后的.vue文件 长什么样子:(上面main.js 第3行的打印)
- 点击 App.vue?6fd5:1 后,可以得到 如下 匿名渲染函数
var render = function() { var _vm = this var _h = _vm.$createElement var _c = _vm._self._c || _h return _c("div", { attrs: { id: "app" } }, [ _vm._v(" " + _vm._s(_vm.aa) + " --- 1 "), _c("div", { on: { click: _vm.qqq } }, [_vm._v("click me")]), _vm._v(" " + _vm._s(_vm.C_aa) + " ") ]) } var staticRenderFns = [] render._withStripped = true export { render, staticRenderFns }- 总结:vue-loader的作用:编译.vue文件,得到 匿名渲染函数
- 先看看vue-loader编译后的.vue文件 长什么样子:(上面main.js 第3行的打印)
-
往下的其他步骤 同.html文件模式一样
下一篇
码字不易,点赞鼓励!