资讯专栏INFORMATION COLUMN

Vue编译器源码分析compileToFunctions作用详解

3403771864 / 474人阅读

  这篇文章主要讲述compileToFunctions的作用。

  我们现在就compileToFunctions 的真弄明白为什么要弄的这么复杂?现在我们看看下面完整代码。

  compileToFunctions是如何把模板字符串template编译成渲染函数render的。

  Vue.prototype.$mount函数体

  回归到Vue.prototype.$mount函数体内。

  var ref = compileToFunctions(template, {
  shouldDecodeNewlines: shouldDecodeNewlines,
  shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
  delimiters: options.delimiters,
  comments: options.comments
  }, th

is);

  在上面可以看出,在此传递给compileToFunctions的第一个参数就是模板字符串template,而第二个参数则是一个配置选项options。

  先说说这些配置选项中的属性!

  shouldDecodeNewlines

  源码出处

  // check whether current browser encodes a char inside attribute values
  var div;
  function getShouldDecode(href) {
  div = div || document.createElement('div');
  div.innerHTML = href ? "<a href=\"\n\"/>" : "<div a=\"\n\"/>";
  return div.innerHTML.indexOf('&#10;') > 0
  }
  // #3663: IE encodes newlines inside attribute values while other browsers don't
  var shouldDecodeNewlines = inBrowser ? getShouldDecode(false) : false;
  // #6828: chrome encodes content in a[href]
  var shouldDecodeNewlinesForHref = inBrowser ? getShouldDecode(true) : false;

  上面代码想表达什么?

  其实大致表达是在我们innerHTML获取内容时,换行符和制表符则转化成&#10和&#9。在IE浏览器中,这个将不会成为问题。

  但这会对Vue的编译器在对模板进行编译后的结果有影响,如何不出现这就要Vue需要知道在什么时候要做兼容工作,如果 shouldDecodeNewlines 为 true,意味着 Vue 在编译模板的时候,要对属性值中的换行符或制表符做兼容处理。而shouldDecodeNewlinesForHref为true 意味着Vue在编译模板的时候,要对a标签的 href 属性值中的换行符或制表符做兼容处理。

  options.delimiters & options.comments

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

1.jpg

2.jpg

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

  compileToFunctions函数逐行分析

  function createCompileToFunctionFn(compile) {
  var cache = Object.create(null);
  return function compileToFunctions(
  template,
  options,
  vm
  ) {
  options = extend({}, options);
  var warn$$1 = options.warn || warn;
  delete options.warn;
  /* istanbul ignore if */
  {
  // detect possible CSP restriction
  try {
  new Function('return 1');
  } catch (e) {
  if (e.toString().match(/unsafe-eval|CSP/)) {
  warn$$1(
  'It seems you are using the standalone build of Vue.js in an ' +
  'environment with Content Security Policy that prohibits unsafe-eval. ' +
  'The template compiler cannot work in this environment. Consider ' +
  'relaxing the policy to allow unsafe-eval or pre-compiling your ' +
  'templates into render functions.'
  );
  }
  }
  }
  // check cache
  var key = options.delimiters ?
  String(options.delimiters) + template :
  template;
  if (cache[key]) {
  return cache[key]
  }
  // compile
  var compiled = compile(template, options);
  // check compilation errors/tips
  {
  if (compiled.errors && compiled.errors.length) {
  warn$$1(
  "Error compiling template:\n\n" + template + "\n\n" +
  compiled.errors.map(function(e) {
  return ("- " + e);
  }).join('\n') + '\n',
  vm
  );
  }
  if (compiled.tips && compiled.tips.length) {
  compiled.tips.forEach(function(msg) {
  return tip(msg, vm);
  });
  }
  }
  // turn code into functions
  var res = {};
  var fnGenErrors = [];
  res.render = createFunction(compiled.render, fnGenErrors);
  res.staticRenderFns = compiled.staticRenderFns.map(function(code) {
  return createFunction(code, fnGenErrors)
  });
  // check function generation errors.
  // this should only happen if there is a bug in the compiler itself.
  // mostly for codegen development use
  /* istanbul ignore if */
  {
  if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) {
  warn$$1(
  "Failed to generate render function:\n\n" +
  fnGenErrors.map(function(ref) {
  var err = ref.err;
  var code = ref.code;
  return ((err.toString()) + " in\n\n" + code + "\n");
  }).join('\n'),
  vm
  );
  }
  }
  return (cache[key] = res)
  }
  }

  注意compileToFunctions函数是接收三个参数的,第三个参数是当前Vue实例。

  首先:

  options = extend({}, options);
  var warn$$1 = options.warn || warn;
  delete options.warn;

  通过extend 把 options 配置对象上的属性扩展一份到新对象上,定义warn$$1变量。warn是一个错误信息提示的函数。

  接下来:

  // detect possible CSP restriction
  try {
  new Function('return 1');
  } catch (e) {
  if (e.toString().match(/unsafe-eval|CSP/)) {
  warn$$1(
  'It seems you are using the standalone build of Vue.js in an ' +
  'environment with Content Security Policy that prohibits unsafe-eval. ' +
  'The template compiler cannot work in this environment. Consider ' +
  'relaxing the policy to allow unsafe-eval or pre-compiling your ' +
  'templates into render functions.'
  );
  }
  }

  上面代码体现出语句问题就是使用 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() 是否可用,并在某些极端情况下给你一个有用的提示。

  接下来是:

  var key = options.delimiters ?
  String(options.delimiters) + template :
  template;
  if (cache[key]) {
  return cache[key]
  }

  options.delimiters这个选项是改变纯文本插入分隔符,如果options.delimiters存在,则使用String 方法将其转换成字符串并与 template 拼接作为 key 的值,否则直接使用 template 字符串作为 key 的值,然后判断 cache[key] 是否存在,如果存在直接返回cache[key]。

  这么做的目的是缓存字符串模板的编译结果,防止重复编译,提升性能,我们再看一下compileToFunctions函数的最后一句代码:

  return (cache[key] = res)

  这句代码在返回编译结果的同时,将结果缓存,这样下一次发现如果 cache 中存在相同的 key则不需要再次编译,直接使用缓存的结果就可以了。

  接下来:

 

 // compile
  var compiled = compile(template, options);
  // check compilation errors/tips
  if (compiled.errors && compiled.errors.length) {
  warn$$1(
  "Error compiling template:\n\n" + template + "\n\n" +
  compiled.errors.map(function(e) {
  return ("- " + e);
  }).join('\n') + '\n',
  vm
  );
  }
  if (compiled.tips && compiled.tips.length) {
  compiled.tips.forEach(function(msg) {
  return tip(msg, vm);
  });
  }
  }

  compile 是引用了来自 createCompileToFunctionFn 函数的形参稍后会重点来介绍它。

  在使用 compile 函数对模板进行编译后会返回一个结果 compiled,返回结果 compiled 是一个对象且这个对象可能包含两个属性 errors 和 tips 。这两个属性分别包含了编译过程中的错误和提示信息。所以上面那段代码的作用就是用来检查使用 compile 对模板进行编译的过程中是否存在错误和提示的,如果存在那么需要将其打印出来。

  接下来:

  // turn code into functions
  var res = {};
  var fnGenErrors = [];
  res.render = createFunction(compiled.render, fnGenErrors);
  res.staticRenderFns = compiled.staticRenderFns.map(function(code) {
  return createFunction(code, fnGenErrors)
  });

  res 是一个空对象且它是最终的返回值,fnGenErrors 是一个空数组。

  在 res 对象上添加一个 render 属性,这个 render 属性,就是最终生成的渲染函数,它的值是通过 createFunction 创建出来的。

  createFunction 函数源码

  function createFunction(code, errors) {
  try {
  return new Function(code)
  } catch (err) {
  errors.push({
  err: err,
  code: code
  });
  return noop
  }
  }

  createFunction 函数接收两个参数,第一个参数 code 为函数体字符串,该字符串将通过new Function(code) 的方式创建为函数。

  第二个参数 errors 是一个数组,作用是当采用 new Function(code) 创建函数发生错误时用来收集错误的。

  已知,传递给 createFunction 函数的第一个参数是 compiled.render,所以 compiled.render 应该是一个函数体字符串,且我们知道 compiled 是 compile 函数的返回值,这说明:compile函数编译模板字符串后所得到的是字符串形式的函数体。

  传递给 createFunction 函数的第二个参数是之前声明的 fnGenErrors 常量,也就是说当创建函数出错时的错误信息被 push 到这个数组里了。

  在这句代码之后,又在 res 对象上添加了 staticRenderFns 属性:

  res.staticRenderFns = compiled.staticRenderFns.map(function(code) {
  return createFunction(code, fnGenErrors)
  });

  由这段代码可知 res.staticRenderFns 是一个函数数组,是通过对compiled.staticRenderFns遍历生成的,这说明:compiled 除了包含 render 字符串外,还包含一个字符串数组staticRenderFns ,且这个字符串数组最终也通过 createFunction 转为函数。staticRenderFns 的主要作用是渲染优化,我们后面详细讲解。

  最后的代码:

  // check function generation errors.
  // this should only happen if there is a bug in the compiler itself.
  // mostly for codegen development use
  /* istanbul ignore if */
  if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) {
  warn$$1(
  "Failed to generate render function:\n\n" +
  fnGenErrors.map(function(ref) {
  var err = ref.err;
  var code = ref.code;
  return ((err.toString()) + " in\n\n" + code + "\n");
  }).join('\n'),
  vm
  );
  }
  return (cache[key] = res)

  上面代码主要是渲染函数过程在打印中的错误,且同时将结果村存储下来,接下来我们讲讲compile 的作用。


文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/127790.html

相关文章

  • Vue原理】Compile - 源码版 之 从新建实例到 compile结束的主要流程

    摘要:页面这个实例,按理就需要解析两次,但是有缓存之后就不会理清思路也就是说,其实内核就是不过是经过了两波包装的第一波包装在中的内部函数中内部函数的作用是合并公共和自定义,但是相关代码已经省略,另一个就是执行第二波包装在中,目的是进行缓存 写文章不容易,点个赞呗兄弟 专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究基于 ...

    CODING 评论0 收藏0
  • 聊聊Vue.js的template编译

    摘要:具体可以查看抽象语法树。而则是带缓存的编译器,同时以及函数会被转换成对象。会用正则等方式解析模板中的指令等数据,形成语法树。是将语法树转化成字符串的过程,得到结果是的字符串以及字符串。里面的节点与父节点的结构类似,层层往下形成一棵语法树。 写在前面 因为对Vue.js很感兴趣,而且平时工作的技术栈也是Vue.js,这几个月花了些时间研究学习了一下Vue.js源码,并做了总结与输出。 文...

    gnehc 评论0 收藏0
  • vue源码阅读之数据渲染过程

    摘要:图在中应用三数据渲染过程数据绑定实现逻辑本节正式分析从到数据渲染到页面的过程,在中定义了一个的构造函数。一、概述 vue已是目前国内前端web端三分天下之一,也是工作中主要技术栈之一。在日常使用中知其然也好奇着所以然,因此尝试阅读vue源码并进行总结。本文旨在梳理初始化页面时data中的数据是如何渲染到页面上的。本文将带着这个疑问一点点追究vue的思路。总体来说vue模版渲染大致流程如图1所...

    AlphaGooo 评论0 收藏0
  • Vue编程三部曲之将template编译成AST示例详解

      知道吗?Vue.js 有 2 个版本,一个是Runtime + Compiler版本,另一个是Runtime only版本。Runtime + Compiler版本是包含编译代码的,简单来说就是Runtime only版本不包含编译代码的,在运行时候,需要借助 webpack 的 vue-loader 事先把模板编译成 render 函数。  假如在你需要在客户端编译模板 (比如传入一个字符串...

    3403771864 评论0 收藏0
  • vue源码分析系列之入口文件分析

    摘要:中引入了中的中引入了中的中,定义了的构造函数中的原型上挂载了方法,用来做初始化原型上挂载的属性描述符,返回原型上挂载的属性描述符返回原型上挂载与方法,用来为对象新增删除响应式属性原型上挂载方法原型上挂载事件相关的方法。 入口寻找 入口platforms/web/entry-runtime-with-compiler中import了./runtime/index导出的vue。 ./r...

    kgbook 评论0 收藏0

发表评论

0条评论

3403771864

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<