Vue.js 源码分析(三) 基础篇 模板渲染 el、emplate、render属性详解
Vue有三个属性和模板有关,官网上是这样解释的:
el ;提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标
template ;一个字符串模板作为 Vue 实例的标识使用。模板将会 替换 挂载的元素。挂载元素的内容都将被忽略,除非模板的内容有分发插槽。
render ;字符串模板的代替方案,允许你发挥 JavaScript 最大的编程能力。该渲染函数接收一个 createElement 方法作为第一个参数用来创建 VNode。
简单说一下,就是:
Vue内部会判断如果没有render属性则把template属性的值作为模板,如果template不存在则把el对应的DOM节点的outerHTML属性作为模板,经过一系列正则解析和流程生成一个render函数,最后通过with(this){}来执行。
也就是说template的优先级大于el。
render的参数是Vue内部的$createElement函数(位于4486行),它的可扩展性更强一些,在一些项目的需求中,可以用很简单的代码得到一个模板。例如Vue实战9.3里介绍的例子,有兴趣可以看看
render可以带3个参数,分别如下:
tag ;元素的标签名,也可以是组件名
data ;该VNode的属性,是个对象
children ;子节点,是个数组
其中参数2可以省略的,在4335行做了修正,最后执行_createElement()函数,如下:
function createElement ( //第4335行
context,
tag,
data,
children,
normalizationType,
alwaysNormalize
) {
if (Array.isArray(data) || isPrimitive(data)) { //如果data是个数组或者是基本类型
normalizationType = children;
children = data; //修正data为children
data = undefined; //修正data为undefined
}
if (isTrue(alwaysNormalize)) {
normalizationType = ALWAYS_NORMALIZE;
}
return _createElement(context, tag, data, children, normalizationType) //最后执行_createElement创建一个虚拟VNode
}
例如下面三个Vue实例,分别用el、template和rentder指定模板,它们的输出是一样的
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://cdn.bootcss.com/vue/2.5.16/vue.js"></script>
<title>Document</title>
</head>
<body>
<div id="app1">{{message}}</div>
<div id="app2"></div>
<div id="app3"></div>
<script>
var data={message:'you are so annoying'}
new Vue({el:'#app1',data}) //用el做模板
new Vue({el:'#app2',data,template:"<div>{{message}}</div>"}) //用template做模板
new Vue({el:'#app3',data,render:function(h){return h('div',this.message)}}) //直接用render函数指定模板
</script>
</body>
</html>
、浏览器显示结果:

可以看到输出是一摸一样的
Vue实例后会先执行_init()进行初始化,快结束时会判断是否有el属性,如果存在则调用$mount进行挂载,$mount函数如下:
writer by:大沙漠 QQ:22969969
Vue.prototype.$mount = function ( //定义在10861行
el,
hydrating
) {
el = el && query(el); /* istanbul ignore if */
if (el === document.body || el === document.documentElement) {
"development" !== 'production' && warn(
"Do not mount Vue to <html> or <body> - mount to normal elements instead."
);
return this
} var options = this.$options;
// resolve template/el and convert to render function
if (!options.render) { //如果render属性不存在
var template = options.template; //则尝试获取template属性并将其编译成render
if (template) {
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
template = idToTemplate(template);
/* istanbul ignore if */
if ("development" !== 'production' && !template) {
warn(
("Template element not found or is empty: " + (options.template)),
this
);
}
}
} else if (template.nodeType) {
template = template.innerHTML;
} else {
{
warn('invalid template option:' + template, this);
}
return this
}
} else if (el) { //如果templtate不存在但是el存在,则获取调用getOuterHTML()函数获取el的outerHTML属性,getOuterHTML()定义在10933行,也就是末尾,用户获取DOM的outerHTML
template = getOuterHTML(el);
}
if (template) {
/* istanbul ignore if */
if ("development" !== 'production' && config.performance && mark) {
mark('compile');
} var ref = compileToFunctions(template, {
shouldDecodeNewlines: shouldDecodeNewlines,
shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this); //这里调用compileToFunctions()将template解析成一个render函数,并返回
var render = ref.render;
var staticRenderFns = ref.staticRenderFns;
options.render = render;
options.staticRenderFns = staticRenderFns; /* istanbul ignore if */
if ("development" !== 'production' && config.performance && mark) {
mark('compile end');
measure(("vue " + (this._name) + " compile"), 'compile', 'compile end');
}
}
}
return mount.call(this, el, hydrating)
};
compileToFunctions函数是由createCompiler()返回的(这里有点绕,研究代码的时候在里面绕了好几天),我把大致主体贴出来,如下:
var baseOptions ={} //编译的配置项 第9802行
function createCompileToFunctionFn(compile){
var cache = Object.create(null);
return return function compileToFunctions(template, options, vm) { //编译时先执行这里
/**/
compile(template,options)
/**/
}
}
function createCompilerCreator(baseCompile){
return function(baseOptions){
function compile(template, options) {/**/}
return {
compile: compile,
compileToFunctions: createCompileToFunctionFn(compile) //难点:匿名函数返回的值中又调用了createCompileToFunctionFn函数
}
}
}
var createCompiler = createCompilerCreator(function(){ //传入一个匿名函数
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} //最后返回一个对象
})
var ref$1 = createCompiler(baseOptions);
var compileToFunctions = ref$1.compileToFunctions; //编译的入口文件
是不是有点晕呢,我举一个例子就能看明白了,如下:
function show(show){ //shou函数也直接返回一个匿名函数,带一个参数
return function(info){
show(info) //show通过作用域链就可以访问到参数的show函数了
}
}
var info=show(function(info){
console.log(info)
}) //这里执行show函数,传入一个匿名函数
info({name:'gsz'}) //控制台输出:{name: "gsz"}
Vue内部看得晦涩是因为传参的时候都注明了一个函数名,其实这个函数名是可以忽略的,这样看起来会更清晰一点 注:这样设计是为了跨平台一些代码的复用和存放吧,代码结构在node下更好理解一点
compileToFunctions函数内部会调用parse()将模板经过一系列的正则解析,用一个AST对象保存,然后调用generate()做静态节点标记,最后调用generate生成一个render函数
以上面的第一个Vue实例来说,parse()解析后的AST对象如下:

、再通过generate()后生成如下一个对象,其中render就是最终要执行的render函数了

compileToFunctions函数返回值是一个对象,以上面的第一个vue实例为例,返回后的信息如下:
{
render:"(function anonymous() {with(this){return _c('div',{attrs:{"id":"app1"}},[_v(_s(message))])}})", //最终渲染出来的render函数
staticRenderFns:Function[] //如果是静态节点,则保存到这里
}
以后分析到每个API时这里会单独分析的
最后在mountcomponent()函数内会以当前Vue实例为上下文,执行该render函数(在2739行),此时就会完成渲染watch的收集,并生成虚拟VNode,最后调用_update()方法生成真实DOM节点。
Vue.js 源码分析(三) 基础篇 模板渲染 el、emplate、render属性详解的更多相关文章
- Vue.js 源码分析(十七) 指令篇 v-if、v-else-if和v-else 指令详解
v-if 指令用于条件性地渲染一块内容.这块内容只会在指令的表达式返回true值的时候被渲染. v-else-if,顾名思义,充当 v-if 的“else-if 块”,可以连续使用: 也可以使用 v- ...
- Vue.js 源码分析(八) 基础篇 依赖注入 provide/inject组合详解
先来看看官网的介绍: 简单的说,当组件的引入层次过多,我们的子孙组件想要获取祖先组件的资源,那么怎么办呢,总不能一直取父级往上吧,而且这样代码结构容易混乱.这个就是这对选项要干的事情 provide和 ...
- Vue.js 源码分析(十三) 基础篇 组件 props属性详解
父组件通过props属性向子组件传递数据,定义组件的时候可以定义一个props属性,值可以是一个字符串数组或一个对象. 例如: <!DOCTYPE html> <html lang= ...
- Vue.js 源码分析(四) 基础篇 响应式原理 data属性
官网对data属性的介绍如下: 意思就是:data保存着Vue实例里用到的数据,Vue会修改data里的每个属性的访问控制器属性,当访问每个属性时会访问对应的get方法,修改属性时会执行对应的set方 ...
- Vue.js 源码分析(二) 基础篇 全局配置
Vue.config是一个对象,包含Vue的全局配置,可以在启动应用之前修改下列属性,如下: ptionMergeStrategies ;自定义合并策略的选项silent ...
- Vue.js 源码分析(九) 基础篇 生命周期详解
先来看看官网的介绍: 主要有八个生命周期,分别是: beforeCreate.created.beforeMount.mounted.beforeupdate.updated .beforeDes ...
- Vue.js 源码分析(七) 基础篇 侦听器 watch属性详解
先来看看官网的介绍: 官网介绍的很好理解了,也就是监听一个数据的变化,当该数据变化时执行我们的watch方法,watch选项是一个对象,键为需要观察的数据名,值为一个表达式(函数),还可以是一个对象, ...
- Vue.js 源码分析(十一) 基础篇 过滤器 filters属性详解
Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化.过滤器可以用在两个地方:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持).过滤器应该被添加在 JavaScrip ...
- Vue.js 源码分析(十) 基础篇 ref属性详解
ref 被用来给元素或子组件注册引用信息.引用信息将会注册在父组件的 $refs 对象上.如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素:如果用在子组件上,引用就指向组件实例,例如: ...
随机推荐
- js移动端自适应动态设置html的fontsize
JS设计移动端页面时会遇到自适应问题,大多数都知道用rem来设置页面的比例大小,下面就来说几种常见的html中的fontsize设置方法: 1.使用flexible.js插件库. 淘宝就是利用这个来 ...
- 华为mate10 pro内置浏览器出现的令人头疼的样式兼容问题
问题描述: 下图红色框区域内容在华为mate10 pro(以下简称mate10)内置浏览器中整体向左偏移,没有居中,其它手机浏览器都无该问题,如下图 问题分析 经过一番追根溯源,我发现是 bo ...
- WPF ListBox 隐藏滑块
<ListBox ScrollViewer.VerticalScrollBarVisibility = "Disabled"; </ListBox>
- 用Python进行数据清洗,这7种方法你一定要掌握
前言 文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者 | 常国珍.赵仁乾.张秋剑 来源 |<Python数据科学:技术 ...
- 用CSS伪类实现毛玻璃效果
效果图: 思路: 内容容器需要一个before伪类,负责引用跟背景容器相同的背景图,还需要一个after伪类,用来加上半透明的白色背景.这两个伪类的层级需都为-1,而内容容器本身的层级为1,这样做可以 ...
- CSS语法规范一
CSS语法规范 CSS规则由两个主要的部分构成:选择器以及一条或多条声明. p{ color: red; font-size: 12px; } CSS代码风格 样式格式书写 紧凑格式 h3 {colo ...
- jqgrid后台处理搜索
jqgrid后台处理搜索, 如果点击jqgrid自带的搜索,则向后台传递“_search”参数,和searchField.searchOper.searchString三个值.如下所示: string ...
- [转]C++字符串操作函数_tcschr、_tcsrchr
原文出处:https://blog.csdn.net/eickandy/article/details/50083169 C++标准库函数提供了字符和字符串的操作函数,并提供了其UNICODE版本,本 ...
- yum工具及源码包
目录 yum工具及源码包 yum yum源 yum实战案例 yum全局配置文件 制作本地yum仓库 构建企业级yum仓库 源码包 yum工具及源码包 yum yum是RedHat以及CentOS中的软 ...
- 计算机基础 python入门
1.计算机基础 计算机组成: 输入输出设备内. 存储器 .cpu .电源 .显卡 中央处理器(cpu) 处理各种数据 相当于人的大脑 内存 存储数据 相当于临时记忆 硬盘 存储数据 相当于人的永久记忆 ...