教程目录
1.手把手教你从零写一个简单的 VUE
2.手把手教你从零写一个简单的 VUE--模板篇

Hello,我又回来了,上一次的文章教会了大家如何书写一个简单 VUE,里面实现了VUE 的数据驱动视图渲染模板,更新到页面的过程,简单的带大家了解了类似 VUE 这样子的数据驱动视图框架的工作流程,今天我来给大家讲一讲作为一个前端框架最为核心的部分---模板,代码还是放在文章的最后,请随意下载

模板的分类

在介绍我们实现的模板语言之前,我们先来了解下,现在市面上比较流行的模板语言:

PHP/ASP/JSP风格

   <%if(list.length ){%>   <ol>
<%for(n = 0; n < list.length; ++n ){%>
<li>
<%=list[n]%>
</li>
<%}%>
</ol>
<%}%>

这种是最接近于 js 变成语言的语法,比较直观,但是由于存在< >的分隔符,对 IDE不太友好,不太好进行格式化处理

mustcache风格

    {{#if list.length}}
<ol>
{{#each list item}}
<li>
{{item}}
</li>
{{/each}}
</ol>
{{/if}}

这种是artTemplate默认的语法,高级语法有限,通常难自定义拓展

DSL风格语法

 <ol dsl-if="list.length">
<li dsl-for="item in list"> </li>
</ol>

首先介绍下什么是DSLDSL全称是Domain Specific Language/DSL领域专用语言,其基本思想是求专不求全,用于解决一个类型,一个领域的问题。比如Vue里面的v-xxxVue称之为指令,其实就是一个DSL,用于解决模板语法等问题,这种模板由于在html语法里面相当于标签的属性,所以对IDE友好,不会影响格式化操作。

Vue的模板语法相当于结合了 DSL语法和 mustcache风格, 逻辑控制部分使用DSL语法,输出展示部分使用 mustcache风格

模板引擎设计思路

下面是这个模板引擎的思路:

字符串模板语法定义

首先我们要定义一种模板语法,按照上一节的说明,我们使用DSL风格语法,下面是我们测试用的模板

我们采用最简单的将模板写在script标签的配置方式,可以看到我们定义了几个DSL,分别是dsl-if,dsl-for,dsl-html,分别用于判断,循环和直接输出 html,还有使用mustcache作为字符串输出语法。当然这个只是一个简单的模板DSL语言,主要为了讲解思路,真正的模板需要更加多的模板语法,具体可以参照 VUE文档

模板解析成为 AST

首先解释下什么是AST,AST 全称为abstract syntax tree(抽象语法树),是源代码的抽象语法结构的树状表现形式,每种源码都可以被抽象成为AST,比如我们常用的 js,css,json 等,都可以解析成为 AST
把模板解析成为AST,就是将模板的 html 结构进行解析,变成一颗附带结构、关系、属性的抽象树,这样做方便与后面我们多次对模板进行处理,减少了多次解析字符串带来的损耗,同时变成一颗树的数据结构之后更加方便于我们的遍历,关于AST的优点缺点大家可以执行搜索,这里就不展开说明了
上面的字符串模板解析完成之后,会变成以下的一个AST

可以看到字符串模板变成了一个object数组,每个 obj 代表一个节点,里面包含了这个 obj 的属性,类型,父子关系,用到的DSL等等。这个可以看成是我们的模板的一个中间态,为我们进行进一步处理打下了基础。

AST 转换成为 模板函数

联系上一篇文章,其实模板函数的构造都大同小异,基本是都是通过拼接函数字符串,然后通过Function对象转换成一个函数,变成一个函数之后,只要传入对应的数据,函数就会返回一个模板数据渲染好的 html 字符串。下面是例子中通过AST

这是个函数体,然后使用new Function,就变成一个真正的函数了,至于这个函数体的解释,我将放在下面具体实现进行讲解

数据与模板函数结合生成 html

由于本文主要是讲模板的实现,因此数据部分还是使用延续上一篇文章的绑定,在初始化或者数据发生改变的时候,响应的函数会对数据所关联的模板函数进行重新调用,生成新的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 生成模板函数

生成模板函数的思路就是递归遍历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 呗~~

手把手教你从零写一个简单的 VUE--模板篇的更多相关文章

  1. 手把手教你从零写一个简单的 VUE

    本系列是一个教程,下面贴下目录~1.手把手教你从零写一个简单的 VUE2.手把手教你从零写一个简单的 VUE--模板篇 今天给大家带来的是实现一个简单的类似 VUE 一样的前端框架,VUE 框架现在应 ...

  2. 手把手教你用vue-cli构建一个简单的路由应用

    上一章说道:十分钟上手-搭建vue开发环境(新手教程)https://www.jianshu.com/p/0c6678671635 开发环境搭建好之后,那么开始新添加一些页面,构建最基本的vue项目, ...

  3. 手把手教你用redis实现一个简单的mq消息队列(java)

    众所周知,消息队列是应用系统中重要的组件,主要解决应用解耦,异步消息,流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构.目前使用较多的消息队列有 ActiveMQ,RabbitMQ,Zero ...

  4. 【良心保姆级教程】java手把手教你用swing写一个学生的增删改查模块

    很多刚入门的同学,不清楚如何用java.swing去开发出一个系统? 不清楚如何使用java代码去操作数据库进行增删改查一些列操作,不清楚java代码和数据库(mysql.sqlserver)之间怎么 ...

  5. 用Python写一个简单的Web框架

    一.概述 二.从demo_app开始 三.WSGI中的application 四.区分URL 五.重构 1.正则匹配URL 2.DRY 3.抽象出框架 六.参考 一.概述 在Python中,WSGI( ...

  6. 如何写一个简单的http服务器

    最近几天用C++写了一个简单的HTTP服务器,作为学习网络编程和Linux环境编程的练手项目,这篇文章记录我在写一个HTTP服务器过程中遇到的问题和学习到的知识. 服务器的源代码放在Github. H ...

  7. 如何写一个简单的shell

    如何写一个简单的shell 看完<UNIX环境高级编程>后我就一直想写一个简单的shell来作为练习,因为有事断断续续的写了好几个月,如今写了差不多来总结一下. 源代码放在了Github: ...

  8. 分享:计算机图形学期末作业!!利用WebGL的第三方库three.js写一个简单的网页版“我的世界小游戏”

    这几天一直在忙着期末考试,所以一直没有更新我的博客,今天刚把我的期末作业完成了,心情澎湃,所以晚上不管怎么样,我也要写一篇博客纪念一下我上课都没有听,还是通过强大的度娘完成了我的作业的经历.(当然作业 ...

  9. 一步一步写一个简单通用的makefile(三)

    上一篇一步一步写一个简单通用的makefile(二) 里面的makefile 实现对通用的代码进行编译,这一章我将会对上一次的makefile 进行进一步的优化. 优化后的makefile: #Hel ...

随机推荐

  1. LeetCode-016-最接近的三数之和

    最接近的三数之和 题目描述:给定一个包括 n 个整数的数组 nums 和 一个目标值 target.找出 nums 中的三个整数,使得它们的和与 target 最接近.返回这三个数的和.假定每组输入只 ...

  2. docker基础命令和操作

    前言 之前在部署个人网站的时候,需要打包maven,在生产环境,需要使用到docker去做服务器和端口的守护. 于是在查阅了相关资料,学习了docker一些基本命令行操作,包括对镜像的查看,修改和添加 ...

  3. 分布式 PostgreSQL 集群(Citus)官方示例 - 多租户应用程序实战

    如果您正在构建软件即服务 (SaaS) 应用程序,您可能已经在数据模型中内置了租赁的概念. 通常,大多数信息与租户/客户/帐户相关,并且数据库表捕获这种自然关系. 对于 SaaS 应用程序,每个租户的 ...

  4. tensorflow源码解析之framework-function

    目录 什么是function FunctionDef 函数相关类 关系图 涉及的文件 迭代记录 1. 什么是function 在讲解function的概念之前,我们要先回顾下op.op是规定了输入和输 ...

  5. 2022年官网下安装MongoDB最全版与官网查阅方法(5.0.6)

    一.下载安装 1.百度搜索,找到官网,或直接访问:https://www.mongodb.com/ 2.寻找下载位置,双击下载. 3.找到本地位置,双击执行,进入欢迎界面,选择next. 4.勾选协议 ...

  6. pd.cut和pd.qcut()之间的区别

  7. 017tcpflow的简单用法

    tcpflow tcpflow是服务器上经常使用的一个小程序,它能够捕获tcp的数据流,并将其存储为方便分析和调试的格式.每一条tcp流都会被存储到独立的文件中,因此,典型的tcp流将会被分别存储为进 ...

  8. pygame.update()与pygame.flip()的区别

    flip函数将重新绘制整个屏幕对应的窗口. update函数仅仅重新绘制窗口中有变化的区域. 如果仅仅是几个物体在移动,那么他只重绘其中移动的部分,没有变化的部分,并不进行重绘.update比flip ...

  9. Golang 包了解以及程序的执行

    Golang 包了解以及程序的执行 引言  Go 语言是使用包来组织源代码的,包(package)是多个 Go 源码的集合,是一种高级的代码复用方案.Go 语言中为我们提供了很多内置包,如 fmt.o ...

  10. 『现学现忘』Docker基础 — 34、DockerFile文件详解

    目录 1.DockerFile文件说明 2.Dockerfile构建过程解析 (1)Docker容器构建三步骤 (2)Dockerfile文件的基本结构 (3)Dockerfile注意事项 (4)Do ...