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

目录
  • 引言
    • Vue.prototype.$mount函数体
    • 源码出处
    • options.delimiters & options.comments
    • compileToFunctions函数逐行分析
    • createFunction 函数源码

引言

Vue编译器源码分析

接上篇文章我们来分析: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
}, this);

在此传递给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('
') > 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中,不仅仅是 a 标签的 href 属性值,任何属性值都存在这个问题。

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

options.delimiters & options.comments

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

现在我们已经搞清楚了这些配置选项是什么意思,那接下来我们把目光放在《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 &amp;&amp; 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 &amp;&amp; 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 的作用。

Vue编译器源码分析之compile解析

以上就是Vue编译器源码分析compileToFunctions作用详解的详细内容,更多关于Vue编译器compileToFunctions的资料请关注我们其它相关文章!

(0)

相关推荐

  • vue编译器util工具使用方法示例

    目录 makeMap源码: isReservedTag 源码: pluckModuleFunction 源码: isReserved 源码: makeMap源码: function makeMap(str, expectsLowerCase) { var map = Object.create(null); var list = str.split(','); for (var i = 0; i < list.length; i++) { map[list[i]] = true; } retur

  • Vue编译器AST抽象语法树源码分析

    目录 引言 baseCompile主要核心代码 如何写一个程序来识别 Token parse 函数解析模板字符串 引言 接上篇  Vue编译器源码分析compile 解析 baseCompile主要核心代码 // `createCompilerCreator` allows creating compilers that use alternative // parser/optimizer/codegen, e.g the SSR optimizing compiler. // Here we

  • Vue编译器解析compile源码解析

    目录 引言 解析 compile compile 源码 配置选项 属性分别解析 finalOptions添加warn 方法 两个特殊的属性处理 引言 在上篇文章 Vue编译器源码分析compileToFunctions作用中我们介绍到了,在 compileToFunctions 方法中: // compile var compiled = compile(template, options); 而真正的编译工作是依托于 compile 函数,接下来我们详细解析 compile . 解析 comp

  • Vue编译器optimize源码分析

    目录 引言 optimize 源码之旅 markStatic$1源码 isStatic源码 复杂点的 回归到markStatic$1 markStaticRoots 源码 引言 接上文 parseHTML 函数源码解析 chars.end.comment钩子函数 上一章节我们讲到通过解析将template转成AST(抽象语法树),接下来继续对模型树优化,进行静态标注.那么问题来了,什么是静态标注?为什么要静态标注. 在源码的注释中我们找到了下面这段话: /** * Goal of the opt

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

    目录 引言 Vue.prototype.$mount函数体 源码出处 options.delimiters & options.comments compileToFunctions函数逐行分析 createFunction 函数源码 引言 Vue编译器源码分析 接上篇文章我们来分析:compileToFunctions的作用. 经过前面的讲解,我们已经知道了 compileToFunctions 的真正来源你可能会问为什么要弄的这么复杂?为了搞清楚这个问题,我们还需要继续接触完整的代码. 下面

  • nginx源码分析线程池详解

    nginx源码分析线程池详解 一.前言 nginx是采用多进程模型,master和worker之间主要通过pipe管道的方式进行通信,多进程的优势就在于各个进程互不影响.但是经常会有人问道,nginx为什么不采用多线程模型(这个除了之前一篇文章讲到的情况,别的只有去问作者了,HAHA).其实,nginx代码中提供了一个thread_pool(线程池)的核心模块来处理多任务的.下面就本人对该thread_pool这个模块的理解来跟大家做些分享(文中错误.不足还请大家指出,谢谢) 二.thread_

  • java集合类源码分析之Set详解

    Set集合与List一样,都是继承自Collection接口,常用的实现类有HashSet和TreeSet.值得注意的是,HashSet是通过HashMap来实现的而TreeSet是通过TreeMap来实现的,所以HashSet和TreeSet都没有自己的数据结构,具体可以归纳如下: •Set集合中的元素不能重复,即元素唯一 •HashSet按元素的哈希值存储,所以是无序的,并且最多允许一个null对象 •TreeSet按元素的大小存储,所以是有序的,并且不允许null对象 •Set集合没有ge

  • JAVA 枚举单例模式及源码分析的实例详解

    JAVA 枚举单例模式及源码分析的实例详解 单例模式的实现有很多种,网上也分析了如今实现单利模式最好用枚举,好处不外乎三点: 1.线程安全 2.不会因为序列化而产生新实例 3.防止反射攻击但是貌似没有一篇文章解释ENUM单例如何实现了上述三点,请高手解释一下这三点: 关于第一点线程安全,从反编译后的类源码中可以看出也是通过类加载机制保证的,应该是这样吧(解决) 关于第二点序列化问题,有一篇文章说枚举类自己实现了readResolve()方法,所以抗序列化,这个方法是当前类自己实现的(解决) 关于

  • Java集合框架源码分析之LinkedHashMap详解

    LinkedHashMap简介 LinkedHashMap是HashMap的子类,与HashMap有着同样的存储结构,但它加入了一个双向链表的头结点,将所有put到LinkedHashmap的节点一一串成了一个双向循环链表,因此它保留了节点插入的顺序,可以使节点的输出顺序与输入顺序相同. LinkedHashMap可以用来实现LRU算法(这会在下面的源码中进行分析). LinkedHashMap同样是非线程安全的,只在单线程环境下使用. LinkedHashMap源码剖析 LinkedHashM

  • nginx源码分析configure脚本详解

    nginx源码分析--configure脚本 一.前言 在分析源码时,经常可以看到类似 #if (NGX_PCRE) .... #endif 这样的代码段,这样的设计可以在不改动源码的情况下,通过简单的定义宏的方式来实现功能的打开与关闭,但是在nginx/src目录下始终没有找到宏 NGX_PCRE 对应的 #define 语句. 在之前介绍event模块的时候,讲到init_cycle函数中对cycle进行了初始化,其中很重要一步操作就是讲包含所有module信息的数组拷贝到这个cycle对应

  • jQuery源码分析之Callbacks详解

    代码的本质突出顺序.有序这一概念,尤其在javascript--毕竟javascript是单线程引擎. javascript拥有函数式编程的特性,而又因为javascript单线程引擎,我们的函数总是需要有序的执行.优秀代码常常 把函数切割成各自的模块,然后在某一特定条件下执行,既然这些函数是有序的执行,那么我们为什么不编写一个统一管理的对象,来帮助我们管理这些函数--于是,Callbacks(回调函数)诞生. 什么是Callbacks javascript中充斥着函数编程,例如最简单的wind

  • Django restframework 源码分析之认证详解

    前言 最近学习了 django 的一个 restframework 框架,对于里面的执行流程产生了兴趣,经过昨天一晚上初步搞清楚了执行流程(部分方法还不太清楚),于是想详细的总结一下当来一个请求时,在该框架里面是如何执行的? 启动项目时 昨天在调试django时,发现在 APIView 中打的断点没有断下来,而是打在 View 中的断点断下来了,调试了很多次,最后发现,在 django 项目启动时,会首先加载 urls 中的文件,执行 views 中类的 as_view方法,其实是继承自 API

  • 通过JDK源码分析关闭钩子详解

    关闭钩子 用户关闭关闭程序,需要做一些善后的清理工作,但问题是,某些用户不会按照推荐的方法关闭应用程序,肯能导致善后工作无法进行.像tomcat调用server的start方法启动容器,然后会逐级调用start.当发出关闭命令是会启动关闭功能,但是关闭可能会有一些意外产生,导致应用程序没有进入到我们制定的关闭方法去.如何解决这个问题呢,使得即使有意外也能正常进入关闭流程. 好在java提供了一种优雅的方式去解决这种问题.使得关闭的善后处理的代码能执行.java的关闭钩子能确保总是执行,无论用户如

  • Vue.js源码分析之自定义指令详解

    前言 除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令. 官网介绍的比较抽象,显得很高大上,我个人对自定义指令的理解是:当自定义指令作用在一些DOM元素或组件上时,该元素在初次渲染.插入到父节点.更新.解绑时可以执行一些特定的操作(钩子函数() 自定义指令有两种注册方式,一种是全局注册,使用Vue.directive(指令名,配置参数)注册,注册之后所有的Vue实例都可以使用,另一种是局部注册,在创建Vue实例时通过directives属性创建局部指

随机推荐