模板编译的作用
用户只需要编写类似html的代码-vuejs模板,通过编译器将模板转换为返回VNode的render函数,用户就不用写复杂的render函数了
.vue文件会被webpack在构建的过程中转换成render函数,需要vueloader
tips
- 在使用vue2的时候,标签中的文本内容尽量不要添加多余的空白和换行的内容,vue2的render函数会全部保持原样输出,这样会额外的增加内存的消耗,而vue3优化了这个问题
模板编译的入口
关于compileToFunctions函数
在我们的入口文件entry-runtime-with-compiler.js
中我们可以找到一个compileToFunctions
函数
compileToFunctions
函数的作用就是把template编译成render
,staticRenderFns
,并返回compileToFunctions
核心就是先去找缓存中编译的结果,如果有的话直接返回,没有的话开始编译,并且把编译的字符串形式的代码转换成函数的形式,最后缓存并且返回-
读取缓存中的 CompiledFunctionResult 对象,如果有直接返回
-
把模板编译为编译对象(render, staticRenderFns),字符串形式的js代码
const compiled = compile(template, options) ...
-
调用
createFunction
把字符串形式的代码转换成js方法 -
缓存并返回res对象(render, staticRenderFns方法)
-
关于createCompiler(baseOptions)函数
createCompiler(baseOptions)
函数返回了compileToFunctions
函数
baseOptions
是平台相关的options,src\platforms\web\compiler\options.js 中定义- 这个函数中定义了compile函数,而compile函数的核心作用就是合并选项,调用 baseCompile 进行编译、记录错误,最后返回编译好的对象
- 返回 {
compile,
compileToFunctions
}
return { compile, compileToFunctions: createCompileToFunctionFn(compile) }
- compileToFunctions函数是模板编译的入口
关于createCompilerCreator(function baseCompile ())函数
createCompilerCreator(function baseCompile ())
函数返回了createCompiler
函数
- createCompilerCreator 以
baseCompile
函数为参数 baseCompile
函数内部做了一些关于抽象语法树ast相关的事情- 把模板转换成ast 抽象语法树---解析parse
- 优化抽象语法树 --- 优化optimize
- 把抽象语法树转换成字符串形式的js代码 --- 生成generate
- 返回 ast/render(渲染函数)/staticRenderFns(静态渲染函数, 生成静态 VNode 树)
- 返回
createCompiler
函数
模板转换成ast抽象语法树之parse
parse函数在处理的过程中会调用parseHTML
方法,依次去遍历HTML模板字符串,把HTML模板字符串转换成AST对象,类似于一个普通的对象,html中的属性和指令都会记录在ast对象的相应属性上
优化抽象语法树之optimize
优化的目的是用来标记抽象语法树中的静态节点,标记之后将来就不需要重新渲染,在patch的时候可以直接跳过这些静态子树
静态节点:parts of DOM that never needs to change
静态根节点:标签中包含子标签,并且没有动态内容,也就是里面都是纯文本内容,如果标签中只包含纯文本内容,没有子标签,vue中是不会去优化的
把抽象语法树转换成字符串形式的js代码之generate
。。。
模板编译的过程
模板编译就是最终把模板字符串转换成渲染函数
是把模板字符串首先转换成AST对象,然后优化AST对象,优化的过程其实就是在标记静态节点和静态根节点,然后把优化好的ast对象转换成字符串形式的代码,最终把字符串形式的代码通过new Function()
转换成匿名函数,这个匿名函数就是最后生成的render函数
总结
- 首先来看模板编译的入口函数
compileToFunctions
- 首先从缓存中加载编译好的render函数
- 如果缓存中没有的话,调用
compile(template, options)
开始编译
- 在
compile
函数中(核心是合并options)- 首先需要合并baseOptions和传进来的options
- 然后调用
baseCompile(template.trim(), finalOptions)
去编译模板
- 真正的处理是在
baseCompile
中完成的,完成了模板编译最核心的三件事情- 模板转换成ast抽象语法树之parse
- parse() --- 把template转换成AST语法树
- 优化抽象语法树之optimize
- 标记AST语法树中的静态节点和静态根节点
- patch()的过程中会跳过这些静态节点,不需要每次重新渲染的时候重新生成节点
- 优化过的ast对象转换成字符串形式的代码之generate
- 模板转换成ast抽象语法树之parse
baseCompile
执行完毕之后,会回到compileToFunctions
- 继续把生成的字符串形式的js代码转变成函数的形式,通过调用
createFunction
createFunction
的底层调用的是new Function()
- 最后缓存并返回
// 4. 缓存并返回res对象(render, staticRenderFns方法) return (cache[key] = res)
- 调用完
compileToFunctions
,render和staticRenderFns就初始化完毕了,最终会被挂载到Vue实例的options对应的属性中
- 继续把生成的字符串形式的js代码转变成函数的形式,通过调用
模板编译的过程中会标记静态根节点,对静态根节点进行优化处理,重新渲染的时候不需要再处理静态根节点,因为内容不会发生改变 模板中不要写过多的无意义的空白和换行,生成ast对象的时候会保留这些空白和换行,都会被存到内存中,对浏览器渲染没有任何意义
组件化回顾
Vue的核心组成就是数据绑定和组件化
- 一个Vue组件就是一个拥有预定义选项的一个vue实例
- 一个组件可以组成页面上的一个功能完备的区域,组件可以包含脚本、样式、模板
组件注册
组件注册方式
-
全局组件
Vue.components
内部的实现是通过Vue.extend来实现的 -
局部组件
Vue.extend(extendOptions)实现
基于传入的选项对象创建了组件的构造函数,组件的构造函数通过原型继承继承自Vue的构造函数,所以组件对象拥有和Vue实例一样的成员,核心代码如下
const Sub = function VueComponent (options) {
// 调用 _init() 初始化
this._init(options)
}
// 原型继承自Vue vue的原型上之前注入了_init(),所以sub中可以访问_init(),所以可以在VueComponent中访问_init()方法
Sub.prototype = Object.create(Super.prototype) // 所有的组件都是继承自 vue super就是vue
Sub.prototype.constructor = Sub
Sub.cid = cid++ // 缓存要用
Sub.options = mergeOptions(
Super.options,
extendOptions
)
Sub['super'] = Super
组件的创建过程
回顾首次渲染过程
- 首先调用Vue构造函数,在构造函数中调用了
this._init()
this._init()
中最终调用了this.$mount()
去挂载this.$mount()
中调用了mountComponent()
mountComponent
中又去创建了渲染Watchernew Watcher()
- 创建了渲染Watcher
new Watcher()
的时候,传递了一个参数updateComponent
- 而
updateComponent
中最终调用了vm._update
和vm._render()
updateComponent = () => {
vm._update(vm._render(), hydrating) // _render生成虚拟dom _update调用patch 对比两个虚拟dom的差异并更新
}
vm._render()
中调用了渲染函数,用户传入的或者编译生成的,在渲染函数中通过createElement()
创建了Vnode对象
createElement()
最终调用了createComponent()
- createComponent()把组件转换成了Vnode对象
- 这个函数内部通过
installComponentHooks()->componentVNodeHooks
初始化了四个钩子函数,并在init钩子函数中创建了组件对象(组件实例) - 而init()钩子函数是在patch()的过程中调用的
组件创建和挂载
创建由父到子,挂载由子到父