写文章不容易,点个赞呗兄弟
专一 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工做原理,源码版助于了解内部详情,让咱们一块儿学习吧 研究基于 Vue版本 【2.5.17】数组
若是你以为排版难看,请点击 下面连接 或者 拉到 下面关注公众号也能够吧缓存
【Vue原理】Compile - 源码版 之 重新建实例到 compile结束的主要流程 bash
Compile 的内容十分之多,今天先来个热身,先不研究 compile 内部编译细节,而是记录一下闭包
重新建实例开始,到结束 compile ,其中的大体外部流程,不涉及 compile 的内部流程函数
或者说,咱们要研究 compile 这个函数是怎么生成的学习
注意,若是你没有准备好,请不要阅读这篇文章ui
注意哦,会很绕,别晕了this
好的,正文开始spa
首先,当咱们经过 Vue 新建一个实例的时候会调用Vueprototype
因此从 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 已经在这里生成了
咱们观察到
在上面这个函数中,主要就作了三件事
根据你传入的参数,来各类获取 template 模板
这里应该都看得懂了,根据DOM,或者根据选择器
经过 compileToFunctions ,传入 template
就能够生成 render 和 staticRenderFns
看着是挺简单哦,就一个 compileToFunctions,可是我告诉你,这个函数的诞生可不是这么容易的,兜兜转转,十分曲折,至关得曲折复杂,没错,这就是咱们下面研究的重点
可是这流程其实好像也没有什么帮助?可是若是你阅读源码的话,或许能够对你理清源码有些许帮助吧
再一次佩服 尤大的脑回路
保存在 vm.$options 上,用于在后面调用
下面就来讲 compileToFunctions 的诞生史
注意,很绕很绕,作好心理准备
首先我定位到 compileToFunctions,看到下面这段代码
var ref$1 = createCompiler();
// compileToFunctions 会返回 render 函数 以及 staticRenderFns
var compileToFunctions = ref$1.compileToFunctions;
复制代码
因而我知道
compileToFunctions 是 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
}
}
);
复制代码
卧槽,又来一个函数,别晕啊兄弟
首先明确两点
一、createCompiler 是 createCompilerCreator 生成的
二、给 createCompilerCreator 传了一个函数 baseCompile
这个 baseCompile 就是 生成 render 的大佬
看到里面包含了 渲染三巨头,【parse,optimize,generate】
可是今天不是讲这个的,这三个东西,每一个内容都十分巨大
这里先跳过,反正 baseCompile 很重要,会在后面被调用到,得先记着
而后,没错,咱们又遇到了一个 函数 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,这个函数主要做用是
调用 baseCompile,把 baseCompile 执行结果 return 出去
baseCompile 以前咱们强调过的,就是那个生成 render 的大佬
忘记的,能够回头看看,执行完毕会返回
{ render,staticRenderFns }
其实 返回的这两个函数的做用大体都是同样的
都是为了执行上面那个 内部 compile
还记得开篇咱们的 compileToFunctions 吗
就是那个在 vm.$mount 里咱们要探索的东西啊
就是他这个吊毛,生成的 render 和 staticRenderFns
再看看那个 内部 compile,能够看到他执行完就是返回
{ render, staticRenderFns }
你看,内部 compile 就是 【vm.$mount 执行的 compileToFunctions】 啊
为何 compileToFunctions 没有直接赋值为 compile 呢!!
能够看到,没有直接让 compileToFunctions = 内部compile
而是把 内部 compile 传给了 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 中,目的是进行 缓存