[前端] Vue编译器源码分析compileToFunctions作用详解

2159 0
黑夜隐士 2022-10-21 15:59:38 | 显示全部楼层 |阅读模式
目录

    引言
      Vue.prototype.$mount函数体shouldDecodeNewlinesoptions.delimiters & options.commentscompileToFunctions函数逐行分析createFunction 函数源码



引言

接上篇Vue编译器源码分析文章我们来分析:compileToFunctions的作用。
经过前面的讲解,我们已经知道了 compileToFunctions 的真正来源你可能会问为什么要弄的这么复杂?为了搞清楚这个问题,我们还需要继续接触完整的代码。
下面我们继续探索compileToFunctions是如何把模板字符串template编译成渲染函数render的。

Vue.prototype.$mount函数体

回归到Vue.prototype.$mount函数体内。
  1. var ref = compileToFunctions(template, {
  2.         shouldDecodeNewlines: shouldDecodeNewlines,
  3.         shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
  4.         delimiters: options.delimiters,
  5.         comments: options.comments
  6. }, this);
复制代码
在此传递给compileToFunctions的第一个参数就是模板字符串template,而第二个参数则是一个配置选项options。
先说说这些配置选项中的属性!

shouldDecodeNewlines

源码出处
  1. // check whether current browser encodes a char inside attribute values
  2. var div;
  3. function getShouldDecode(href) {
  4.         div = div || document.createElement('div');
  5.         div.innerHTML = href ? "<a href="\n"/>" : "<div a="\n"/>";
  6.         return div.innerHTML.indexOf('
  7. ') > 0
  8. }
  9. // #3663: IE encodes newlines inside attribute values while other browsers don't
  10. var shouldDecodeNewlines = inBrowser ? getShouldDecode(false) : false;
  11. // #6828: chrome encodes content in a[href]
  12. var shouldDecodeNewlinesForHref = inBrowser ? getShouldDecode(true) : false;
复制代码
这个是什么意思呢?
大致的翻译下,在我们innerHTML获取内容时,换行符和制表符分别被转换成了&#10和&#9。在IE中,不仅仅是 a 标签的 href 属性值,任何属性值都存在这个问题。
这就会影响Vue的编译器在对模板进行编译后的结果,为了避免这些问题Vue需要知道什么时候要做兼容工作,如果 shouldDecodeNewlines 为 true,意味着 Vue 在编译模板的时候,要对属性值中的换行符或制表符做兼容处理。而shouldDecodeNewlinesForHref为true 意味着Vue在编译模板的时候,要对a标签的 href 属性值中的换行符或制表符做兼容处理。

options.delimiters & options.comments

两者都是当前Vue实例的$options属性,并且delimiters和comments都是 Vue 提供的选项。




现在我们已经搞清楚了这些配置选项是什么意思,那接下来我们把目光放在《Vue编译器源码分析(二)》针对compileToFunctions函数逐行分析。

compileToFunctions函数逐行分析
  1. function createCompileToFunctionFn(compile) {
  2.         var cache = Object.create(null);
  3.         return function compileToFunctions(
  4.                 template,
  5.                 options,
  6.                 vm
  7.         ) {
  8.                 options = extend({}, options);
  9.                 var warn$$1 = options.warn || warn;
  10.                 delete options.warn;
  11.                 /* istanbul ignore if */
  12.                 {
  13.                         // detect possible CSP restriction
  14.                         try {
  15.                                 new Function('return 1');
  16.                         } catch (e) {
  17.                                 if (e.toString().match(/unsafe-eval|CSP/)) {
  18.                                         warn$$1(
  19.                                                 'It seems you are using the standalone build of Vue.js in an ' +
  20.                                                 'environment with Content Security Policy that prohibits unsafe-eval. ' +
  21.                                                 'The template compiler cannot work in this environment. Consider ' +
  22.                                                 'relaxing the policy to allow unsafe-eval or pre-compiling your ' +
  23.                                                 'templates into render functions.'
  24.                                         );
  25.                                 }
  26.                         }
  27.                 }
  28.                 // check cache
  29.                 var key = options.delimiters ?
  30.                         String(options.delimiters) + template :
  31.                         template;
  32.                 if (cache[key]) {
  33.                         return cache[key]
  34.                 }
  35.                 // compile
  36.                 var compiled = compile(template, options);
  37.                 // check compilation errors/tips
  38.                 {
  39.                         if (compiled.errors && compiled.errors.length) {
  40.                                 warn$$1(
  41.                                         "Error compiling template:\n\n" + template + "\n\n" +
  42.                                         compiled.errors.map(function(e) {
  43.                                                 return ("- " + e);
  44.                                         }).join('\n') + '\n',
  45.                                         vm
  46.                                 );
  47.                         }
  48.                         if (compiled.tips && compiled.tips.length) {
  49.                                 compiled.tips.forEach(function(msg) {
  50.                                         return tip(msg, vm);
  51.                                 });
  52.                         }
  53.                 }
  54.                 // turn code into functions
  55.                 var res = {};
  56.                 var fnGenErrors = [];
  57.                 res.render = createFunction(compiled.render, fnGenErrors);
  58.                 res.staticRenderFns = compiled.staticRenderFns.map(function(code) {
  59.                         return createFunction(code, fnGenErrors)
  60.                 });
  61.                 // check function generation errors.
  62.                 // this should only happen if there is a bug in the compiler itself.
  63.                 // mostly for codegen development use
  64.                 /* istanbul ignore if */
  65.                 {
  66.                         if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) {
  67.                                 warn$$1(
  68.                                         "Failed to generate render function:\n\n" +
  69.                                         fnGenErrors.map(function(ref) {
  70.                                                 var err = ref.err;
  71.                                                 var code = ref.code;
  72.                                                 return ((err.toString()) + " in\n\n" + code + "\n");
  73.                                         }).join('\n'),
  74.                                         vm
  75.                                 );
  76.                         }
  77.                 }
  78.                 return (cache[key] = res)
  79.         }
  80. }
复制代码
注意compileToFunctions函数是接收三个参数的,第三个参数是当前Vue实例。
首先:
  1. options = extend({}, options);
  2. var warn$$1 = options.warn || warn;
  3. delete options.warn;
复制代码
通过extend 把 options 配置对象上的属性扩展一份到新对象上,定义warn$$1变量。warn是一个错误信息提示的函数。
接下来:
  1. // detect possible CSP restriction
  2. try {
  3.         new Function('return 1');
  4. } catch (e) {
  5.         if (e.toString().match(/unsafe-eval|CSP/)) {
  6.                 warn$$1(
  7.                         'It seems you are using the standalone build of Vue.js in an ' +
  8.                         'environment with Content Security Policy that prohibits unsafe-eval. ' +
  9.                         'The template compiler cannot work in this environment. Consider ' +
  10.                         'relaxing the policy to allow unsafe-eval or pre-compiling your ' +
  11.                         'templates into render functions.'
  12.                 );
  13.         }
  14. }
复制代码
这段代码使用 try catch 语句块对 new Function('return 1') 这句代码进行错误捕获,如果有错误发生且错误的内容中包含如 'unsafe-eval' 或者 'CSP' 这些字样的信息时就会给出一个警告。
CSP全称Content Security Policy ,可以直接翻译为内容安全策略,说白了,就是为了页面内容安全而制定的一系列防护策略. 通过CSP所约束的的规责指定可信的内容来源(这里的内容可以指脚本、图片、iframe、fton、style等等可能的远程的资源)。通过CSP协定,让WEB处于一个安全的运行环境中。
如果你的策略比较严格,那么 new Function() 将会受到影响,从而不能够使用。但是将模板字符串编译成渲染函数又依赖 new Function(),所以解决方案有两个:
    1、放宽你的CSP策略2、预编译
这段代码的作用就是检测 new Function() 是否可用,并在某些极端情况下给你一个有用的提示。
接下来是:
  1. var key = options.delimiters ?
  2.         String(options.delimiters) + template :
  3.         template;
  4. if (cache[key]) {
  5.         return cache[key]
  6. }
复制代码
options.delimiters这个选项是改变纯文本插入分隔符,如果options.delimiters存在,则使用String 方法将其转换成字符串并与 template 拼接作为 key 的值,否则直接使用 template 字符串作为 key 的值,然后判断 cache[key] 是否存在,如果存在直接返回cache[key]。
这么做的目的是缓存字符串模板的编译结果,防止重复编译,提升性能,我们再看一下compileToFunctions函数的最后一句代码:
  1. return (cache[key] = res)
复制代码
这句代码在返回编译结果的同时,将结果缓存,这样下一次发现如果 cache 中存在相同的 key则不需要再次编译,直接使用缓存的结果就可以了。
接下来:
  1. // compile
  2. var compiled = compile(template, options);
  3. // check compilation errors/tips
  4. if (compiled.errors && compiled.errors.length) {
  5.         warn$$1(
  6.                 "Error compiling template:\n\n" + template + "\n\n" +
  7.                 compiled.errors.map(function(e) {
  8.                         return ("- " + e);
  9.                 }).join('\n') + '\n',
  10.                 vm
  11.         );
  12.    }
  13. if (compiled.tips && compiled.tips.length) {
  14.         compiled.tips.forEach(function(msg) {
  15.                 return tip(msg, vm);
  16.         });
  17.    }
  18. }
复制代码
compile 是引用了来自 createCompileToFunctionFn 函数的形参稍后会重点来介绍它。
在使用 compile 函数对模板进行编译后会返回一个结果 compiled,返回结果 compiled 是一个对象且这个对象可能包含两个属性 errors 和 tips 。这两个属性分别包含了编译过程中的错误和提示信息。所以上面那段代码的作用就是用来检查使用 compile 对模板进行编译的过程中是否存在错误和提示的,如果存在那么需要将其打印出来。
接下来:
  1. // turn code into functions
  2. var res = {};
  3. var fnGenErrors = [];
  4. res.render = createFunction(compiled.render, fnGenErrors);
  5. res.staticRenderFns = compiled.staticRenderFns.map(function(code) {
  6.         return createFunction(code, fnGenErrors)
  7. });
复制代码
res 是一个空对象且它是最终的返回值,fnGenErrors 是一个空数组。
在 res 对象上添加一个 render 属性,这个 render 属性,就是最终生成的渲染函数,它的值是通过 createFunction 创建出来的。

createFunction 函数源码
  1. function createFunction(code, errors) {
  2.         try {
  3.                 return new Function(code)
  4.         } catch (err) {
  5.                 errors.push({
  6.                         err: err,
  7.                         code: code
  8.                 });
  9.                 return noop
  10.         }
  11. }
复制代码
createFunction 函数接收两个参数,第一个参数 code 为函数体字符串,该字符串将通过new Function(code) 的方式创建为函数。
第二个参数 errors 是一个数组,作用是当采用 new Function(code) 创建函数发生错误时用来收集错误的。
已知,传递给 createFunction 函数的第一个参数是 compiled.render,所以 compiled.render 应该是一个函数体字符串,且我们知道 compiled 是 compile 函数的返回值,这说明:compile函数编译模板字符串后所得到的是字符串形式的函数体。
传递给 createFunction 函数的第二个参数是之前声明的 fnGenErrors 常量,也就是说当创建函数出错时的错误信息被 push 到这个数组里了。
在这句代码之后,又在 res 对象上添加了 staticRenderFns 属性:
  1. res.staticRenderFns = compiled.staticRenderFns.map(function(code) {
  2.         return createFunction(code, fnGenErrors)
  3. });
复制代码
由这段代码可知 res.staticRenderFns 是一个函数数组,是通过对compiled.staticRenderFns遍历生成的,这说明:compiled 除了包含 render 字符串外,还包含一个字符串数组staticRenderFns ,且这个字符串数组最终也通过 createFunction 转为函数。staticRenderFns 的主要作用是渲染优化,我们后面详细讲解。
最后的代码:
  1. // check function generation errors.
  2. // this should only happen if there is a bug in the compiler itself.
  3. // mostly for codegen development use
  4. /* istanbul ignore if */
  5. if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) {
  6.         warn$$1(
  7.                 "Failed to generate render function:\n\n" +
  8.                 fnGenErrors.map(function(ref) {
  9.                         var err = ref.err;
  10.                         var code = ref.code;
  11.                         return ((err.toString()) + " in\n\n" + code + "\n");
  12.                 }).join('\n'),
  13.                 vm
  14.         );
  15. }
  16. return (cache[key] = res)
复制代码
这段代码主要的作用是用来打印在生成渲染函数过程中的错误,返回结果的同时将结果缓存,接下来我们讲讲compile 的作用。
Vue编译器源码分析之compile解析
以上就是Vue编译器源码分析compileToFunctions作用详解的详细内容,更多关于Vue编译器compileToFunctions的资料请关注中国红客联盟其它相关文章!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

中国红客联盟公众号

联系站长QQ:5520533

admin@chnhonker.com
Copyright © 2001-2025 Discuz Team. Powered by Discuz! X3.5 ( 粤ICP备13060014号 )|天天打卡 本站已运行