vue模板编译流程

原本是想理一理虚拟dom,结果根本不知道虚拟dom是怎么来的,于是先理清楚模板编译的流程。
因为自身能力问题,没法手写实现,只是单纯的理清除模板编译的流程,然后贴一些关键代码,
可以自己去源码找到关键的地方。我是直接在vue.js里面直接看,并不是下载vue的npm去找各个模块,
所以有什么理解错误的欢迎指教。

一、获取HTML(template)

先了解一下,vue有两个运行环境的编译,一个是npm运行时的runtime版本,一个是浏览器引入vuejs的runtime-compile版本。

runtime-only:

用vue-loader将.vue文件编译成js,然后使用render函数渲染,
打包的时候就编译成了render函数需要的格式,不需要在客户端编译:
在这里插入图片描述
所以我们用webpack开发要使用render函数,如果没有render函数会报错:

new Vue({
  render: h => h(App),
}).$mount('#app')

//error
You are using the runtime-only build of Vue where the template compiler is not available.
 Either pre-compile the templates into render functions, or use the compiler-included build.
runtime-compile

将模板字符串编译成js进行渲染,运行时直接在客户端编译,所以初始化vue的时候一般传入el,也可以使用template或者$mount。
三个的优先级:render > template > el > $mount。

vue.js引入使用render:

  new Vue({
    el: '#app',
    data(){
      return {
        msg: 'msg'
      }
    },
    render(h){
      return h('div', {
        attrs:{"id":"app"}, 
        staticClass:"test", 
        staticStyle:{"background":"red"}
        }, 'is render')
    }
  })

  //生成
  <div id="app" class="test" style="background: red;">is render</div>

vue.js里面可以找到getOuterHTML函数:

template = getOuterHTML(el);

function getOuterHTML (el) {
    if (el.outerHTML) {
      return el.outerHTML
    } else {
      var container = document.createElement('div');
      container.appendChild(el.cloneNode(true));
      return container.innerHTML
    }
}

这就是获取传入的el元素的HTML,可以自己使用这个函数看看结果,其实就是整个标签的字符串。

二、转化成ast

ast叫抽象语法树,所有语言都可以转化成ast。当获取了HTML的内容,第一步要先把HTML转成ast,
用ast可以进行各种编译扩展,vue只是拿来生成render函数。

vue.js里面搜索:
var ast = parse(template.trim(), options);
然后打印结果:
在这里插入图片描述
可以都展开看看内容,vue2主要是用正则一个一个匹配出来,可以搜索startTagOpen看看那些主要正则。

vue3好像换了,这个目录下:packages > compiler-core > src > parse.ts
可以看见里面好像是一个一个字符去解析,生成的结果字段也改了一些,但是最终效果都是差不多的。

tips

{{}}是大胡子语法mustache,挺多库使用的,可以自行了解一下。

parseHTML函数

解析的主要函数,通过正侧和栈数据结构把开始标签、结束标签、文本、注释等等分别进行不同的处理,
给不同元素类型加上不同的type,用来标记不同的节点类型。

optimize函数

生成之后会调用这个函数:

if (options.optimize !== false) {
  optimize(ast, options);
}

添加标记是否是静态节点(static)和静态根节点(staticRoot),试了一下,只要不是纯静态的,staticRoot都是false。
主要是优化patch性能,静态的可以跳过比对直接复制使用。

三、生成render函数

有了ast之后,通过generate函数生成render函数:

  var createCompiler = createCompilerCreator(function baseCompile (
    template,
    options
  ) {
    var ast = parse(template.trim(), options);
    if (options.optimize !== false) {
      optimize(ast, options);
    }
    var code = generate(ast, options);
    return {
      ast: ast,
      render: code.render,
      staticRenderFns: code.staticRenderFns
    }
  });

render函数其实就是一个带有with语法的字符串:

with(this){
    return _c('div',{attrs:{"id":"app"}},
    [_c('p',{staticClass:"test",staticStyle:{"background":"red"}},
    [_v("是"+_s(msg)+_s(msg))]),_v(" "),_c('div',[_v("ss")])])
}
tips:with语法用来处理_s(msg)等数据变量
let obj = {name: 'wade'}
with(obj){
    console.log(name) //wade
}

vue只要传入vue实例this,就可以在生成虚拟dom的时候把数据直接赋值进去。

createFunction

有了字符串,就可以通过new Function执行,vue里面:

  function createFunction (code, errors) {
    try {
      return new Function(code)
    } catch (err) {
      errors.push({ err: err, code: code });
      return noop
    }
  }
_c _s _v

render函数不止使用了这三个,简单介绍这三个:

  • _c createElement 创建元素
  • _v createTextVNode 创建文本
  • _s toString 把数据JSON.stringify或者String

另外的函数可以搜索installRenderHelpers,里面有各种处理函数。

genElement

这个里面进行了一系列判断,然后生成render函数,里面也能看见v-for优先级大于v-if。
里面对template、slot、component、element都进行了判断然后进行不同的处理。

四、生成虚拟dom

有了render函数,调用就可以生成虚拟dom(vnode),生成的时间就是在mount。Vue.prototype.mount。 Vue.prototype.mountVue.prototype.mount里面调用了mountComponent。

mountComponent函数

里面能看见runtime-only的报错,也可以看见beforeMount、mounted挂载,还有Watcher等,
但是核心是vm._update(vm._render(), hydrating);

Vue.prototype._render

_render其实就是调用render,然后生成虚拟dom:

vnode = render.call(vm._renderProxy, vm.$createElement);

可以直接打印这个vnode看看虚拟dom的结构:
在这里插入图片描述
会发现跟那些分析虚拟dom diff算法的有一些不同,比如props,vue其实是data,
不过这只是字段使用的不同,原理是都一样。

仔细看也会发现虚拟dom跟ast非常像,不过会多出一些属性。要明确ast是语法层面的属性,描述的是语言本身,
可以描述dom、js、css、Java等语言,不能随意添加不存在的属性。
虚拟dom是自己定义用来描述的dom的数据结构,可以自己随意添加需要的属性。

五、生成真实dom

生成真实dom是使用Vue.prototype._update,里面有几行代码:

  var prevVnode = vm._vnode;
  var restoreActiveInstance = setActiveInstance(vm);
  vm._vnode = vnode;
  // Vue.prototype.__patch__ is injected in entry points
  // based on the rendering backend used.
  if (!prevVnode) {
    // initial render
    vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
  } else {
    // updates
    vm.$el = vm.__patch__(prevVnode, vnode);
  }
  • prevVnode其实就是oldVnode
  • vnode是最新的虚拟dom
  • vm._vnode = vnode;把新的缓存,下次更新就是oldVnode
  • !prevVnode如果没有旧的,相当于是第一次渲染,直接更新,不用比对(initial render)
  • __patch__其实就是patch函数Vue.prototype.patch = inBrowser ? patch : noop;

调用_update会生成真实的dom,至于怎么生成的,这次不去了解,其实核心就是patch函数,也是dom diff的核心。

上面就是vue模板编译的大概流程,总结一下:

  • 获取HTML(template)
  • 转化成ast
  • 生成render函数
  • 生成虚拟dom
  • 生成真实dom

模板编译大致的步骤就这样,最好是可以对照着几个核心的函数,然后自己到vue.js源码里面看看。
最好当然是把vue npm包拉下来看,划分的会更细。

理清除了vue模板编译的流程,再去看依赖收集,看什么时机触发跟新,然后再去学dom diff,会比较容易一点。

最后,过程是清楚了,但最核心的实现步骤自己并没有完全懂,也就是没办法手写,毕竟看代码比敲代码容易多了。

欢迎关注 coding个人笔记 订阅号

### Vue 模板编译原理 Vue.js 的模板编译过程是其核心功能之一,它能够将声明式的模板语法转化为高效的虚拟 DOM 渲染函数。以下是关于 Vue 模板编译的具体实现细节: #### 编译流程概述 Vue 模板编译主要分为三个阶段:解析(Parse)、优化(Optimize)和代码生成(Codegen)。这三个阶段共同构成了从模板到渲染函数的核心逻辑。 --- #### 解析(Parse) 在这一阶段,Vue模板字符串解析为抽象语法树(Abstract Syntax Tree, AST)。AST 是一种数据结构,用于表示源代码的语法规则[^3]。 例如,给定如下模板: ```html <template> <div> <h1 :id="someId">Hello</h1> </div> </template> ``` 经过 Parse 阶段后会得到类似于以下的 AST 结构: ```javascript { type: 1, tag: 'div', attrsList: [], children: [ { type: 1, tag: 'h1', attrsList: [{ name: ':id', value: 'someId' }], children: [{ type: 2, text: 'Hello' }] } ] } ``` 此阶段的主要目标是从模板中提取出所有的节点及其属性,并将其转换为易于操作的数据结构[^1]。 --- #### 优化(Optimize) 优化阶段的目标是对 AST 进行静态分析,标记其中的静态节点。所谓静态节点是指那些不会随响应式状态变化而改变的内容。通过提前识别这些节点,Vue 可以跳过不必要的比较操作,从而提升性能[^5]。 对于上述例子中的 `<h1>` 节点,“Hello” 文本部分会被标记为静态内容,因为它的值不依赖于任何动态绑定。 --- #### 代码生成(Codegen) 在 Codegen 阶段,Vue 将优化后的 AST 转化为渲染函数的代码字符串。这是一段可以直接被 JavaScript 执行的代码,负责创建虚拟 DOM 并更新实际 DOM[^4]。 继续以上述模板为例,最终生成的 render 函数可能形似如下: ```javascript function render() { return _c('div', {}, [ _c('h1', { attrs: { id: this.someId } }, ['Hello']) ]) } ``` 这里 `_c` 是 `createElement` 方法的一个缩写形式,用于简化虚拟 DOM 创建的过程。 --- #### 工具支持 开发者可以通过官方提供的工具 https://template-explorer.vuejs.org 来直观地观察任意模板对应的 AST 和 Render Function 输出。这对于学习和调试都非常有帮助。 --- ### 总结 Vue 模板编译的本质在于将 HTML 样式的模板语言高效地转义为可运行的 JavaScript 渲染函数。整个过程经历了 **解析 -> 优化 -> 代码生成** 三大步骤,每一步都紧密配合,确保了框架既灵活又高性能的表现。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值