写文章不容易,点个赞呗兄弟
专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧
研究基于 Vue版本 【2.5.17】
如果你觉得排版难看,请点击 下面链接 或者 拉到 下面关注公众号也可以吧
【Vue原理】Compile - 源码版 之 从新建实例到 compile结束的主要流程
Compile 的内容十分之多,今天先来个热身,先不研究 compile 内部编译细节,而是记录一下
从新建实例开始,到结束 compile ,其中的大致外部流程,不涉及 compile 的内部流程
或者说,我们要研究 compile 这个函数是怎么生成的
注意,如果你没有准备好,请不要阅读这篇文章
注意哦,会很绕,别晕了
好的,正文开始
首先,当我们通过 Vue 新建一个实例的时候会调用Vue
所以从 Vue 函数入手
function Vue(){
// .....
vm.$mount(vm.$options.el);
}
然后内部的其他处理都可以忽视,直接定位到 vm.$mount,就是从这里开始去编译的
继续去查找这个函数
Vue.prototype.$mount = function(el) {
var options = this.$options;
if (!options.render) {
var tpl= options.template;
// 获取模板字符串
if (tpl) {
// 根据传入的选择器找到元素,然后拿到该元素内的模板
// 本来有很多种获取方式,但是为了简单,我们简化为一种,知道意思就可以了
tpl = document.querySelector(tpl).innerHTML;
}
if (tpl) {
// 生成 render 函数保存
var ref = compileToFunctions(tpl, {},this);
// 每一个组件,都有自己的 render
options.render = ref.render
options.staticRenderFns =ref.staticRenderFns;
}
}
// 执行上面生成的 render,生成DOM,挂载DOM,这里忽略不讨论
return mount.call(this, el)
};
compile 的主要作用就是,根据 template 模板,生成 render 函数
那么到这里,整个流程就走完了,因为 render 已经在这里生成了
我们观察到
在上面这个函数中,主要就做了三件事
1 获取 template 模板
根据你传入的参数,来各种获取 template 模板
这里应该都看得懂了,根据DOM,或者根据选择器
2 生成 render
通过 compileToFunctions ,传入 template
就可以生成 render 和 staticRenderFns
看着是挺简单哦,就一个 compileToFunctions,但是我告诉你,这个函数的诞生可不是这么容易的,兜兜转转,十分曲折,相当得曲折复杂,没错,这就是我们下面研究的重点
但是这流程其实好像也没有什么帮助?但是如果你阅读源码的话,或许可以对你理清源码有些许帮助吧
再一次佩服 尤大的脑回路
3 保存 render
保存在 vm.$options 上,用于在后面调用
下面就来说 compileToFunctions 的诞生史
注意,很绕很绕,做好心理准备
首先我定位到 compileToFunctions,看到下面这段代码
var ref$1 = createCompiler();
// compileToFunctions 会返回 render 函数 以及 staticRenderFns
var compileToFunctions = ref$1.compileToFunctions;
于是我知道
compileToFunctions 是 createCompiler 执行返回的!!
那么继续定位 createCompiler
createCompiler
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
}
}
);
卧槽,又来一个函数,别晕啊兄弟
不过,注意注意,这里是重点,非常重
首先明确两点
1、createCompiler 是 createCompilerCreator 生成的
2、给 createCompilerCreator 传了一个函数 baseCompile
baseCompile
这个 baseCompile 就是 生成 render 的大佬
看到里面包含了 渲染三巨头,【parse,optimize,generate】
但是今天不是讲这个的,这三个东西,每个内容都十分巨大
这里先跳过,反正 baseCompile 很重要,会在后面被调用到,得先记着
然后,没错,我们又遇到了一个 函数 createCompilerCreator ,定位它!
createCompilerCreator
function createCompilerCreator(baseCompile) {
return function () {
// 作用是合并选项,并且调用 baseCompile
function compile(template) {
// baseCompile 就是 上一步传入的,这里执行得到 {ast,render,statickRenderFn}
var compiled = baseCompile(template);
return compiled
}
return {
// compile 执行会返回 baseCompile 返回的 字符串 render
compile: compile,
// 为了创建一层 缓存闭包,并且闭包保存 compile
// 得到一个函数,这个函数是 把 render 字符串包在 函数 中
compileToFunctions: createCompileToFunctionFn(compile)
}
}
}
这个函数执行过后,会返回一个函数
很明显,返回的函数就 直接赋值 给了上面讲的的 createCompiler
我们看下这个返回给 createCompiler 的函数里面都干了什么?
生成一个函数 compile
内部存在一个函数 compile,这个函数主要作用是
调用 baseCompile,把 baseCompile 执行结果 return 出去
baseCompile 之前我们强调过的,就是那个生成 render 的大佬
忘记的,可以回头看看,执行完毕会返回
{ render,staticRenderFns }
返回 compileToFunctions 和 compile
其实 返回的这两个函数的作用大致都是一样的
都是为了执行上面那个 内部 compile
但是为什么分出一个 compileToFunctions 呢?
还记得开篇我们的 compileToFunctions 吗
就是那个在 vm.$mount 里我们要探索的东西啊
就是他这个吊毛,生成的 render 和 staticRenderFns
再看看那个 内部 compile,可以看到他执行完就是返回
{ render, staticRenderFns }
你看,内部 compile 就是 【vm.$mount 执行的 compileToFunctions】 啊
为什么 compileToFunctions 没有直接赋值为 compile 呢!!
因为要做模板缓存!!
可以看到,没有直接让 compileToFunctions = 内部compile
而是把 内部 compile 传给了 createCompileToFunctionFn
没错 createCompileToFunctionFn 就是做缓存的
为了避免每个实例都被编译很多次,所以做缓存,编译一次之后就直接取缓存
createCompileToFunctionFn
来看看内部的源码,缓存的代码已经标红
function createCompileToFunctionFn(compile) {
// 作为缓存,防止每次都重新编译
// template 字符串 为 key , 值是 render 和 staticRenderFns
var cache = Object.create(null);
return function compileToFunctions(template, options, vm) {
var key = template;
// 有缓存的时候直接取出缓存中的结果即可
if (cache[key]) return cache[key]
// compile 是 createCompileCreator 传入的compile
var compiled = compile(template, options);
var res = {
// compiled.render 是字符串,需要转成函数
render : new Function(compiled.render)
staticRenderFns : compiled.staticRenderFns.map(function(code) {
return new Function(code, fnGenErrors)
});
};
return (cache[key] = res)
}
}
额外:render 字符串变成可执行函数
var res = {
render: new Function(compiled.render) ,
staticRenderFns: compiled.staticRenderFns.map(function(code) {
return new Function(code, fnGenErrors)
});
};
这段代码把 render 字符串可执行函数,因为render生成的形态是一个字符串,如果后期要调用运行,比如转成函数
所以这里使用了 new Function() 转化成函数
同理,staticRenderFns 也一样,只不过他是数组,需要遍历,逐个转化成函数
他的缓存是怎么做的
使用一个 cache 闭包变量
template 为 key
生成的 render 作为 value
当实例第一次渲染解析,就会被存到 cache 中
当实例第二次渲染解析,那么就会从 cache 中直接获取
什么时候实例会解析第二次?
比如 页面A到页面B,页面B又转到页面A。
页面A 这个实例,按理就需要解析两次,但是有缓存之后就不会
理清思路
也就是说,compileToFunctions 其实内核就是 baseCompile!
不过 compileToFunctions 是经过了 两波包装的 baseCompile
第一波包装在 createCompilerCreator 中的 内部 compile 函数中
内部函数的作用是
合并公共options和 自定义options ,但是相关代码已经省略,
另一个就是执行 baseCompile
第二波包装在 createCompileToFunctions 中,目的是进行 缓存