一,前言
上篇,主要介绍了生成 render 函数 - 代码拼接,主要涉及以下几个点:
- render 函数的分析和实现方案;
- 拼接 render 函数结构:generate(ast);
- 处理属性及属性值中的样式:genProps(ast.attrs);
- 递归处理儿子:genChildren(el);
- 对文本和变量进行包装处理,详细分析文本处理流程;
本篇,生成 render 函数 - 函数生成
二,包装 code 生成 render 函数
1,render 函数对比
对比 vue2 模板生成的render函数:
上篇,完成了return内部的代码拼接工作,即render函数中with内部的代码拼接已经完成;
目前,距离得到真正的render函数,还差以下两步:
- 向外一层,包装
with代码块; - 再向外一层,包装成为
Function;
2,包装 with 并生成 render 函数
// src/complier/index.js
export function compileToFunction(template) {
console.log("***** 进入 compileToFunction:将 template 编译为 render 函数 *****")
// 1,将模板变成 AST 语法树
let ast = parserHTML(template);
// 2,使用 AST 生成 render 函数
let code = generate(ast);
// 包装 Function 和 with 代码块,生成完整的 render 函数
let render = new Function(`with(this){return ${code}}`);
console.log("包装 with 生成 render 函数:"+ render.toString())
return render
}
备注:
new Function和evel的区别:
new Function(str)会将内部的字符串包装成为一个函数;evel(str)则会将内部的字符串作为 js 执行;
将生成的render函数保存到options选项:
// src/init.js
Vue.prototype.$mount = function (el) {
const vm = this;
const opts = vm.$options;
el = document.querySelector(el);
vm.$el = el;
if (!opts.render) {
let template = opts.template;
if (!template) {
template = el.outerHTML;
}
let render = compileToFunction(template);
// 保存生成的 render 函数
opts.render = render;
console.log("打印 compileToFunction 返回的 render = " + JSON.stringify(render))
}
}
测试 render 函数生成:
借助上图中输出的render函数,再说明一下为什么需要使用with代码块进行一次包装:
- 首先,在
with代码块中含是有变量的,比如:name、age等; - 其次,在
html模板中的变量名和数量都是不能确定的,这些变量存在于data中,比如:{name:'brave', age:'34'} - 结论,使用
with进行包装之后,在执行render.call(vm)渲染时,vm中包含全部数据,此时在render函数中的this上下文就是vm,那么就可以通过vm.xxx获取到变量对应的值,从而完成赋值操作;
备注:前端模板引擎的实现原理,大多都是依靠
new Function+with来实现的,比如:ejs、jade、handlerbar;
3,生成 render 函数的流程回顾
从html模板到生成render函数的大致流程:
- 解析
html模板并编译成为ast语法树; - 根据
ast语法树拼接render函数的返回内容code; - 使用
with代码块对生成的code字符串进行一次包装; - 将包装后的完整
code字符串,通过new Function输出为render函数;
至此,整个模板编译的流程就搞定了,后续将进行以下工作:
- 1)根据
render函数,生成虚拟dom... - 2)虚拟
dom + 真实数据, 生成真实 dom...
备注:模板编译中涉及到的
v-for、v-if虽然并没有进行实现,但内部的实现原理也都是一系列的字符串拼接操作;
todo:将整个模板编译部分作为一个主题拿出来;
todo:代码重构:可以将src/complier/index.js中的一系列代码拼接和生成的方法,抽取到src/complier/generate.js中;
三,结尾
本篇,生成 render 函数 - 函数生成,主要做了以下两件事:
- 使用 with 代码块对生成的 code 字符串进行一次包装;
- 将包装后的完整 code 字符串,通过 new Function 输出为 render 函数;
下一篇,根据 render 函数,生成虚拟节点
todo:本篇的内容较少,后续考虑与相关内容进行合并;
维护日志
- 20230126:添加文字内容的代码高亮,优化少量内容描述,更新文章摘要,添加 todo;
- 20230127:调整部分内容和代码,添加 todo;
- 20230128:添加了 2 个备注,说明了 render 函数中使用
with包装的原理;