共计 4473 个字符,预计需要花费 12 分钟才能阅读完成。
写文章不容易,点个赞呗兄弟
专注 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 中,目的是进行 缓存