教程目录
1.手把手教你从零写一个简单的 VUE
2.手把手教你从零写一个简单的 VUE--模板篇css
Hello,我又回来了,上一次的文章教会了你们如何书写一个简单 VUE,里面实现了VUE 的数据驱动视图渲染模板,更新到页面的过程,简单的带你们了解了相似 VUE 这样子的数据驱动视图框架的工做流程,今天我来给你们讲一讲做为一个前端框架最为核心的部分---模板,代码仍是放在文章的最后,请随意下载html
在介绍咱们实现的模板语言以前,咱们先来了解下,如今市面上比较流行的模板语言:前端
<%if(list.length ){%> <ol> <%for(n = 0; n < list.length; ++n ){%> <li> <%=list[n]%> </li> <%}%> </ol> <%}%>
这种是最接近于 js 变成语言的语法,比较直观,可是因为存在< >
的分隔符,对 IDE不太友好,不太好进行格式化处理vue
{{#if list.length}} <ol> {{#each list item}} <li> {{item}} </li> {{/each}} </ol> {{/if}}
这种是artTemplate
默认的语法,高级语法有限,一般难自定义拓展git
DSL
风格语法<ol dsl-if="list.length"> <li dsl-for="item in list"> </li> </ol>
首先介绍下什么是DSL
, DSL
全称是Domain Specific Language/DSL
领域专用语言,其基本思想是求专不求全,用于解决一个类型,一个领域的问题。好比Vue
里面的v-xxx
,Vue
称之为指令
,其实就是一个DSL,用于解决模板语法等问题,这种模板因为在html
语法里面至关于标签的属性,因此对IDE
友好,不会影响格式化操做。github
Vue
的模板语法至关于结合了 DSL
语法和 mustcache风格, 逻辑控制部分使用DSL
语法,输出展现部分使用 mustcache风格正则表达式
下面是这个模板引擎的思路:算法
首先咱们要定义一种模板语法,按照上一节的说明,咱们使用DSL风格语法
,下面是咱们测试用的模板
咱们采用最简单的将模板写在script
标签的配置方式,能够看到咱们定义了几个DSL
,分别是dsl-if
,dsl-for
,dsl-html
,分别用于判断,循环和直接输出 html,还有使用mustcache
做为字符串输出语法。固然这个只是一个简单的模板DSL
语言,主要为了讲解思路,真正的模板须要更加多的模板语法,具体能够参照 VUE
文档json
首先解释下什么是AST
,AST 全称为abstract syntax tree(抽象语法树)
,是源代码的抽象语法结构的树状表现形式,每种源码均可以被抽象成为AST
,好比咱们经常使用的 js,css,json 等,均可以解析成为 AST
把模板解析成为AST
,就是将模板的 html 结构进行解析,变成一颗附带结构、关系、属性的抽象树,这样作方便与后面咱们屡次对模板进行处理,减小了屡次解析字符串带来的损耗,同时变成一颗树的数据结构以后更加方便于咱们的遍历,关于AST
的优势缺点你们能够执行搜索,这里就不展开说明了
上面的字符串模板解析完成以后,会变成如下的一个AST
能够看到字符串模板变成了一个object数组,每一个 obj 表明一个节点,里面包含了这个 obj 的属性,类型,父子关系,用到的DSL
等等。这个能够当作是咱们的模板的一个中间态,为咱们进行进一步处理打下了基础。segmentfault
联系上一篇文章,其实模板函数的构造都大同小异,基本是都是经过拼接函数字符串,而后经过Function
对象转换成一个函数,变成一个函数以后,只要传入对应的数据,函数就会返回一个模板数据渲染好的 html 字符串。下面是例子中经过AST
这是个函数体,而后使用new Function
,就变成一个真正的函数了,至于这个函数体的解释,我将放在下面具体实现进行讲解
因为本文主要是讲模板的实现,所以数据部分仍是使用延续上一篇文章的绑定,在初始化或者数据发生改变的时候,响应的函数会对数据所关联的模板函数进行从新调用,生成新的html,从新进行渲染。
模板的开发思路咱们就在上面都说明了,主要总结下就是将字符串模板变成 ast,ast 变成模板函数,而后就能够结合数据进行 html 生成及渲染了
首先说明下本教程的方法是对思路的实现,并不是彻底使用 vue 的实现方法,vue 是一个完整的框架,里面涉及的东西比较多,咱们的实现是为了让你们更好的了解 vue 的原理,而非彻底实现
AST
部分1.模板预处理:
因为字符串模板是人为处理的,所以书写的时候可能会出现标签不配对,标签未关闭等问题,所以咱们要先作些预处理,来去除这些干扰,作法有不少种,好比经过一些语法分析的工具进行解析,这里咱们使用一种比较简单的方式进行处理,代码以下(/src/core/render.js
):
能够看到我建立了一个div
标签,而后将字符串模板放进去里面,这样子浏览器会对模板进行解析处理,而后咱们再经过innerHTML
去除先后空格以后拿出来,这样就对字符串模板进行了处理。
备注:咱们按照 vue 的规则,一个模板只有一个根节点,因此咱们取了childNodes[0]
2.生成 ast:
上面咱们对字符串模板进行了预处理,接下去咱们要将字符串模板转换成ast,代码比较长,你们有兴趣能够看下/src/compiler/ast/parse.js
,下面说下解析思路
解析经过正则表达式配合 String.replace(regExt,fn)
,正则表达式为/<(?:"[^"]*"['"]*|'[^']*'['"]*|[^'">])+>/g
,解析出来标签和标签上面的属性,而后按照需求进行存储,就生成 ast
生成模板函数的思路就是递归遍历ast 树,对不一样的类型节点,不一样的NSL
,调用不一样的生成函数,最后组合成为模板函数字符串,代码以下(/src/compiler/compiler-helper.js):
能够看到,处理的函数对 DSL
还有不一样的标签类型进行处理,而后都返回了一个辅助函数的调用,好比_i,_f,_c
等等,这里的辅助函数是在模板函数被调用的时候才真正的被调用的,下面咱们举例说明一个辅助函数_c
这个辅助函数的功能是用于生成节点,能够看到调用了这个函数以后,对应的 ast 里面的节点被真正生成,变成dom
节点,而且会把孩子节点进行插入,经过不少辅助函数的递归嵌套调用,最终模板函数一调用,就能够结合数据渲染出来真实的dom节点
下面说一个比较细的知识点,就是辅助函数的调用,咱们知道上面的辅助函数调用在生成的时候,其实都是字符串,而后经过new Function
让他变成真正的函数,那么问题就来了,咱们知道new Function
是的做用域和运行时的代码是隔离的,是调用不到外面的_c,_f
等辅助函数的,那是如何实现调用的呢,这里用了一个咱们不多使用的关键字with
,这个关键字在不少书籍里面都不推荐使用,由于他的做用是修改with
包含代码块的做用域,若是滥用会致使代码的逻辑不可控,可是在模板函数里面这个关键字有奇效,他能够方便的规定把当前的代码做用域传到模板函数里面,从而使得模板函数里面能够调用到运行时做用域的函数。你们能够看下上一小节生成的模板函数字符串,会发现就是用整个with(that){}
包裹起来的,在模板函数运行时,将当前做用域直接传入便可,代码以下:
至此,咱们已经生成了模板函数,经过传入数据运行模板函数,就能够生成 dom,代码以下:
能够咱们直接把compiler_helper
附带上 data 做为做用域,直接调用了模板函数,就能够生成dom,再结合咱们第一篇文章写的数据监听,就能够实现简单的数据驱动视图
至此,咱们的VUE模板的基本实现已经介绍完成了,这里主要是介绍如何去实现一个模板引擎的思路,因此功能上上面的实现不是完整的,只是实现了一些简单的语法,你们能够下下代码继续补充。
细心的人可能会发现,咱们上面的模板有个问题,就是若是改了数据中的其中一个数值,那么整个模板都得从新编译,从新渲染,这实际上是很是损耗性能的,这其实就是我下一篇文章要讲的,模板渲染的效率问题,先提出几个关键词 虚拟dom,diff 算法,最小化渲染
,吊吊你们的胃口,哈哈,下一篇文章我会进行全面的介绍,相信学习完下一篇文章,你们会对现有市面上的数据驱动框架的模板部分有个全面的了解~下一篇文章更加精彩哦~~求关注
最后附上源码点我点我,各位客官给个 star 呗~~