微信扫一扫抽红包在哪里做网站做网站和app哪类商标
微信扫一扫抽红包在哪里做网站,做网站和app哪类商标,个体工商户网站备案,广州番禺新楼盘最新房价解析模板编译template的背后发生了什么一、#x1f4d1;初识模板编译1、vue组件中使用render代替template2、模板编译总结二、✏️感受模板编译的美1、with语法#xff08;1#xff09;例子展示#x1f330;#xff08;2#xff09;知识点归纳三、#x1f4c8;编译模板1… 解析模板编译template的背后发生了什么一、初识模板编译1、vue组件中使用render代替template2、模板编译总结二、✏️感受模板编译的美1、with语法1例子展示2知识点归纳三、编译模板1、编译模板碎碎念2、编译模板过程1初始化一个npm环境2安装编译器3新建新文件4了解缩写函数5编译插值6编译表达式7编译属性和动态属性8编译条件9编译循环10编译事件11编译v-model3、模板编译总结四、组件渲染/更新过程1、初识组件渲染/更新2、组件渲染/更新过程1初次渲染过程1解析模板为render函数2触发响应式3执行render函数生成vnode2更新过程1更新过程细述2完成流程图3异步渲染3、小结五、✔️结束语依稀记得我们在vue时最上方总是有一个
template 包围着。而很多时候我们也没有很在意的去意识到
template/template 究竟是什么。在今天的这篇文章中就带大家一起来了解模板编译 template 的背后究竟发生了什么事情
一起来了解模板编译的纸短情长
一、初识模板编译
1、vue组件中使用render代替template
template 即模板。模板是 vue 开发中最常用的部分即与vue的使用关联最紧密的原理。它不是 html 它有指令、有插值、也有 JS 表达式那它到底是什么呢我们来看个例子。
在 vue 中定义一个组件通常会使用 template 模板字符串来定义一个组件。比如
Vue.component(heading,{template:xxx
})一般情况下模板的定义是上面这种情况。同时在程序编译期间模板会将 template 的这种字符串类型编译成 render 函数。 但是呢在有些复杂的情况下可能就不能用 template 函数了这个时候会考虑直接用 render 函数来定义一个组件。比如
Vue.component(heading,{render: function(createElement){return createElement(h this.level, //tag props[ //childrencreateElement(a,{attrs:{name:headerId,href:# headerId}},this is a tag)])}
})就像上面这样子我们也可以通过使用一个 render 函数来定义一个组件。
2、模板编译总结
看完上面的例子我们来做个小结✨
template即模板。这个模板会编译成 render 函数其中 render 函数用的是 with 语法。过程模板→ render 函数→ vnode →组件渲染和更新过程。vue 组件可以用 render 函数代替 template 。React 一直都用 render 没有模板这里仅作知识补充不做讲解。
二、✏️感受模板编译的美
1、with语法
1例子展示
先来了解模板编译中一个很重要的知识点 with 语法。下面先用一个例子来展示with语法与普通语法的不同。
不使用with语法执行程序时
const obj {a: 100, b: 200}console.log(obj.a)
console.log(obj.b)
console.log(obj.c) //undefined使用with语法执行程序时
//使用with能改变 {} 内自由变量的查找方式
// 将 {} 内自由变量当作 obj 的属性来查找
with(obj){console.log(a)console.log(b)console.log(c) //会报错
}2知识点归纳
看完上面with语法的例子我们来对 with 语法做一个知识点归纳。
with 语法会改变 {} 内自由变量的查找规则当作 obj 属性来查找如果在当前 {} 内找不到匹配的 obj 属性就会报错with 要谨慎使用它打破了作用域规则会让其易读性变差。
三、编译模板
1、编译模板碎碎念
在前面中我们讲过模板它不是 html 它有指令、有插值、也有JS表达式它能实现判断、也能实现循环。
试想一下模板为什么不是 html
思考一下假如你在写程序时能用 html 写出一个判断或者循环出来吗答案自然时不行的。
所以说 html 只是一个静态的标签语言你写什么它就显示什么它没有办法实现一个逻辑或者做循环和判断。
因此对于前端浏览器而言只有 JS 才能实现判断和循环等各种逻辑功能。
所以模板一定是转换为某种 JS 代码之后才进行运行的。而这个模板怎么转换成 js 代码的这个过程就称为编译模板。
那这个模板是怎么转的呢接下来我们来看下编译模板的过程。
2、编译模板过程
1初始化一个npm环境
首先先建立一个新文件可以命名为 vue-template-complier-demo 。之后用以下命令行初始化一个npm的环境
npm init -y2安装编译器
npm 安装模板编译器。命令行如下
npm install vue-template-compiler --save3新建新文件
在根目录下初始化新建一个 index.js 文件并引入 vue-template-compiler 。代码如下
//引入vue-template-compiler
const compiler require(vue-template-compiler)// 编译
const res compiler.compile(template)
console.log(res.render)接下来我们就来看下模板中的插值、表达式、属性和动态属性等等类型的编译到底是怎么样的
4了解缩写函数
以下vue源码中的缩写函数先了解将在下面的讲解中用到。
// 从 vue 源码中找到缩写函数的含义
function installRenderHelpers (target) {target._c createElement;target._o markOnce;target._n toNumber;target._s toString;target._l renderList;target._t renderSlot;target._q looseEqual;target._i looseIndexOf;target._m renderStatic;target._f resolveFilter;target._k checkKeyCodes;target._b bindObjectProps;target._v createTextVNode;target._e createEmptyVNode;target._u resolveScopedSlots;target._g bindObjectListeners;target._d bindDynamicKeys;target._p prependModifier;
}5编译插值
//引入vue-template-compiler
const compiler require(vue-template-compiler)// 插值
const template p{{message}}/p
// with(this){return createElement(p,[createTextVNode(toString(message))])}
// h - vnode
// createElement - vnode// 编译
const res compiler.compile(template)
console.log(res.render)编译以上内容打印结果如下 从上图中可以看到插值类型的模板最终被编译成一个 with 语句并且这个 with 语句的参数都指向了 this 。
同时大家可以看到里面有一个 _c _v _s。那这几个元素是什么呢
这个就是上面第四点中提到的 vue 源码中的缩写函数。 _c 对应的就是源码中的 createElement _v 对应的就是源码中的 createTextVNode _s 对应的就是源码中的 toString 。
所以以上编译后的 with 语句 with(this){return _c(p,[_v(_s(message))])} 事实上就是 with(this){return createElement(p,[createTextVNode(toString(message))])} 。
以上这个语句的意思为编译创建一个 p 元素之后呢 p 元素就没有子元素了于是就创建它的文本节点 message 同时 message 是字符串的形式存在因此要进行 toString 。
额外再补充一个知识点 createElement 其实就等于我们平常所说的 h 函数返回的是一个 虚拟DOM 节点。
以上就是一个插值模板编译的过程下面再用几个例子让大家熟悉。
6编译表达式
//引入vue-template-compiler
const compiler require(vue-template-compiler)// 表达式
const template p{{flag ? message : no message found}}/p
// with(this){return _c(p,[_v(_s(flag ? message : no message found))])}// 编译
const res compiler.compile(template)
console.log(res.render)编译以上内容打印结果如下 依据上面插值的分析方法我们来分析表达式的模板编译过程。
表达式编译后的结果返回了一个虚拟 DOM 节点同样地查询 vue 源码中的缩写函数我们可以发现 with(this){return _c(p,[_v(_s(flag ? message : no message found))])} 最终的结果等于 with(this){return createElement(p,[createTextVnode(toString(flag ? message : no message found))])} 。
先创建了一个 p 元素之后 p 元素没有子元素了于是创建文本节点最终 toString 三目表达式里面的内容。
7编译属性和动态属性
//引入vue-template-compiler
const compiler require(vue-template-compiler)// 属性和动态属性
const template div iddiv1 classcontainerimg :srcimgUrl//div// with(this){return _c(div,
// {staticClass:container,attrs:{id:div1}},
// [
// _c(img,{attrs:{src:imgUrl}})])}// 编译
const res compiler.compile(template)
console.log(res.render)编译以上内容打印结果如下 依据上面的分析方法我们来分析属性和动态属性的模板编译过程。
属性和动态属性编译后的结果返回了一个虚拟 DOM 节点同样地查询 vue 源码中的缩写函数我们可以发现 with(this){return _c(div,{staticClass:container,attrs:{id:div1}},[_c(img,{attrs:{src:imgUrl}})])} 最终的结果等于 with(this){return createElement(div,{staticClass:container,attrs:{id:div1}},[createElement(img,{attrs:{src:imgUrl}})])} 。
此时我们可以看到返回的 vnode 节点中包含 class 名字 container 。此时 div 有一个 id 选择器这个 id 选择器是该 div 的一个属性于是就通过attrs来表示。
最外层结束后里面还有一层 img 。 img 可以视其为跟 div 一样的标签于是先创建 img 元素又因为 img 绑定了一个具体的值就像是 div 里面绑定了 id 选择器。所以在创建完 img 的值之后继续用 attrs 来传递 img 所绑定的值。
8编译条件
// 条件
const template divp v-ifflag aA/pp v-elseB/p/div// with(this){return _c(div,[(flag a)?_c(p,[_v(A)]):_c(p,[_v(B)])])}编译以上内容打印结果如下 依据上面的分析方法我们来分析条件的模板编译过程。
对于条件来说首先是先创建一个 div 元素之后呢模板编译把 v-if 和 v-else 分割成一个三目表达式的方式来进行编译。
9编译循环
// 循环
const template ulli v-foritem in list :keyitem.id{{item.title}}/li/ul// with(this){return _c(ul,_l((list),function(item){return _c(li,{key:item.id},[_v(_s(item.title))])}),0)}
编译以上内容打印结果如下 依据上面的分析方法我们来分析循环的模板编译过程。
对于以上循环来说首先会创建一个 ul 元素之后查询 _l 的缩写函数我们知道它是 renderlist 所以 list 列表会被 renderList 函数进行编译。
最后渲染后的 item 被当作函数的参数进行传递并列返回对应 item 的 li 列表元素。
10编译事件
// 事件
const template button clickclickHandlersubmit/button// with(this){return _c(button,{on:{click:clickHandler}},[_v(submit)])}编译以上内容打印结果如下 依据上面的分析方法我们来分析事件的模板编译过程。
对于事件来说首先会创建一个 button 元素之后 click 即 v-on:click 会被编译成 on:{click:clickHandler} 。最后是 _v 即 createTextVNode 。创建一个 submit 的文本节点将 click 的内容提交上去。
11编译v-model
// v-model
const template input typetext v-modelname
// 主要看 input 事件
// with(this){return _c(input,{directives:[{name:model,rawName:v-model,value:(name),expression:name}],attrs:{type:text},domProps:{value:(name)},on:{input:function($event){if($event.target.composing)return;name$event.target.value}}})}编译以上内容打印结果如下 依据上面的分析方法我们来分析双向绑定v-model的模板编译过程。
对于 v-model 来说主要看的是 input 事件。 v-model 的背后绑定的是 name 和 value 这两个语法糖。之后通过 attrs 去创建 类型type 为 text 的属性。
最终是 input 事件 input 事件绑定 $event 最后 name 的值就等同于 $event.target.value 这样数据就实现了双向绑定。
3、模板编译总结
看完上述的内容我们来对模板编译做个小结
1从render函数到vnode
模板编译后是一个 render 函数执行 render 函数后返回一个 vnode
2vnode到patch和diff
基于 vnode 的基础上再执行 patch 和 diff
3模板编译工具
在平常的开发中我们可以使用 webpack 、 vue-loader 等构建工具在开发环境下编译模板。
四、组件渲染/更新过程
1、初识组件渲染/更新
讲完上完的内容我们再来讲一个与编译模板关联性很强的知识点组件渲染/更新过程。
一个组件从渲染到页面上开始再到修改 data 去触发更新数据驱动视图其背后的原理是什么又需要掌握哪些要点呢
事实上组件在渲染之前会先进行模板编译模板 template 会编译成 render 函数。
之后就是数据的监听了这就要谈到响应式数据。vue的响应式通过操作 Object.defineProperty() 去监听 getter 和 setter 方法来使得数据实时更新。
监听完数据之后就是执行 render 函数生成 vnode 。
到了 vnode 即 vdom 这一步之后会进行 patch(elem,vnode) 和 patch(vnode,newVnode) 的比较。
关于响应式原理和vdom的解读如有需要可以查看我的前两篇文章进行学习这里不再展开细述~
2、组件渲染/更新过程
组件渲染和更新过程主要经过以下三个步骤初次渲染过程→更新过程→异步渲染。
接下来就这三个步骤进行一一讲解。
1初次渲染过程
初次渲染过程即组件第一次渲染是怎么样的怎么把模板渲染到页面上。具体有以下三个步骤
解析模板为 render 函数触发响应式监听 data 属性 getter 和 setter 执行 render 函数生成 vnode 进行 patch(elem,vnode) 。
下面就这三个步骤来进行一一讲解。
1解析模板为render函数
在开发环境下解析模板为 render 函数一般是由 vue-loader 这个插件来处理的。还有一种情况就是用户直接用 cdn 的方式引入 vuejs 的文件进行本地代码练习这种情况下解析模板为 render 函数就是在浏览器环境运行的。
小知识了解完我们来看下这个步骤。
解析模板为 render 函数即解析 template 为 render 函数这个就是上述文章中说的编译模板。
2触发响应式
在编译完模板之后 render 函数有了我们来开始监听 data 属性。
监听 data 属性这个时候我们就需要触发响应式也就是渲染数据。
那在这个阶段怎么渲染数据呢
这个阶段我们需要执行 render 函数 render 函数会触发 getter 方法因为数据没有进行更新只是进行渲染。只有在进行渲染的时候才会操作 setter 方法。
3执行render函数生成vnode
最后当数据渲染完毕后就会执行第一步生成的 render 函数然后生成虚拟 DOM 节点 vnode 之后进行 patch(elem,vnode) 。
2更新过程
1更新过程细述
更新过程即 data 修改之后组件是怎么更新的。
在这个阶段呢将会修改 data 并且触发 setter 注意在此之前 data 在 getter 中已经被监听。
触发完 setter 之后重新执行 render 函数并生成 newVnode 最后进行 patch(vnode, newVnode) 的diff比较。
2完成流程图
接下来我们用一张流程图来完整的回顾渲染和更新的过程。 3异步渲染
在渲染和更新结束之后我们的程序可能还有可能会发生多个程序同时加载这就涉及到一个异步渲染问题。
异步渲染问题我们用 $nextTick 来作为例子讲解。
假设我们现在要实现一个功能当我们点击按钮时打印出列表的项数。这个时候我们大多人可能会这么操作。
templatediv idapp!-- ref的设置时为了方便后续可以用来取节点的DOM元素 --ul reful1li v-for(item, index) in list :keyindex{{item}}/li/ulbutton clickaddItem添加一项/button/div
/templatescript
export default {name: app,data() {return {list: [a, b, c]}},methods: {addItem() {this.list.push(${Date.now()})this.list.push(${Date.now()})this.list.push(${Date.now()})// 获取 DOM 元素const ulElem this.$refs.ul1console.log( ulElem.childNodes.length )}}
}
/script
此时浏览器的显示效果如下 细心的小伙伴已经发现浏览器并没有按照我们所想的打印。当页面上的列表显示 6项 内容时此时控制台只打印 3项 当显示 9项 时此时控制台直接只打印 6项 。
那这究竟时为什么呢
其实当我们点击的那一刻 data 发生变化但是 DOM 并不会立刻进行渲染。所以等到我们点击完成的时候获取的元素还是原来触发的内容而不会增添上新的内容。
那我们所期望的是当点击之后立刻触发 DOM 渲染并拿到最新的值。这个时候就需要用到 nextTick 。具体代码如下
script
export default {name: app,data() {return {list: [a, b, c]}},methods: {addItem() {this.list.push(${Date.now()})this.list.push(${Date.now()})this.list.push(${Date.now()})// 1. 异步渲染$nextTick 待 DOM 渲染完再回调// 即NextTick函数会在多次data修改完并且全部DOM渲染完再触发仅在最后触发一次// 2. 页面渲染时会将 data 的修改做整合this.$nextTick(() {// 获取 DOM 元素const ulElem this.$refs.ul1console.log( ulElem.childNodes.length )})}}
}
/script我们通过给获取 DOM 元素的代码外面再嵌套一层 $nextTick 函数来达到我们想要的效果。在此过程中当我们点击结束后 data 的值发生变化此时 $nextTick 会等待DOM全部渲染完成之后再进行回调。
最终浏览器的打印效果如下 所以也就是说 $nextTick 通过汇总 data 的修改最后再一次性更新视图。
这样可以减少 DOM 的操作次数大大的提高了性能。
3、小结
经过上述一系列的讲解我们可以把内容分割成以下两个要点
要理解清楚渲染和响应式、渲染和模板编译、渲染和vdom的关系。要理解组件渲染/更新的过程初次渲染过程→更新过程→异步渲染。
五、✔️结束语
从模板编译到组件渲染更新过程我们了解了整个 template 背后的全过程。相信通过本文的学习大家对模板编译有了一个更深的认识。
关于模板编译的内容就讲到这里啦如有不理解或文章有误欢迎评论区留言或私信我交流~ 关注公众号 星期一研究室 不定期分享学习干货更多有趣的专栏待你解锁~如果这篇文章对你有帮助记得 点个赞加个关注 再走哦~我们下期见
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/90325.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!