基于数据驱动的动态Web模板技术设计与实现
2017-01-19陈和平
刘 放,陈和平
(1.武汉科技大学计算机科学与技术学院,湖北 武汉,430065;2. 武汉科技大学智能信息处理与实时工业系统湖北省重点实验室,湖北 武汉,430065)
基于数据驱动的动态Web模板技术设计与实现
刘 放,陈和平
(1.武汉科技大学计算机科学与技术学院,湖北 武汉,430065;2. 武汉科技大学智能信息处理与实时工业系统湖北省重点实验室,湖北 武汉,430065)
随着互联网产品规模的不断扩大,前端脚本代码规模大、重用性低、维护困难、扩展性差等问题不断涌现。为此,本文分析了目前主流的Web模板技术及各自的优缺点,并结合国内浏览器的兼容性,在Living template技术思想的基础上,提出一种基于数据驱动的动态Web模板技术方案(DWTT),它能有效提高前端开发效率和代码复用性,降低程序扩展和维护的复杂度。
模板;Web应用;前端开发;数据驱动;抽象语法树
随着互联网技术与应用的不断发展,以操作DOM(document object model)元素为核心的传统前端开发方式应对日益复杂的业务需求已显得越来越力不从心。进入以人为本的Web 2.0时代后,网页不再以简单的文字和图片展示为主,丰富多样的交互形式提升了Web应用的用户体验,Web前端技术在互联网产品开发过程中的使用越来越广泛。
本文围绕数据驱动关键点,结合当前主流前端模板技术中的实现思路,分析各自的优缺点,提出基于数据驱动的动态Web模板技术(dynamic Web template technology,DWTT)的设计思路和实现方法,以便于开发者分离业务逻辑、显示逻辑和用户界面,使程序代码结构更清晰,更容易被阅读、测试、维护、替换、扩展和改进。
1 模板技术相关研究
早期的浏览器所渲染的HTML代码由后端开发人员将数据传入后端模板后拼接而成。随着Ajax技术的日渐流行,异步数据传输方式的用户体验更佳,Json格式存储量小且简洁易读等优势也日益凸显[1]。前端开发人员普遍采用字符串拼接的方式,将Ajax获取的数据手动输出到HTML。这种开发方式效率低下,代码逻辑与视图过于耦合,难以阅读和维护。随着各种浏览器对W3C标准下的新特性支持度越来越高,HTML5和CSS3等新技术不断普及[2],前端模板引擎也出现了百花齐放的局面。采用前端模板后,使得开发流程中前、后端分离更加彻底[3]。
下面对3种具有代表性的前端模板技术,即String-based模板技术、DOM-based模板技术和Living template技术,进行比较与分析。
1.1 String-based模板技术
这是一种基于字符串的模板技术,以字符串和数据为输入,通过用正则表达式将占位符替换为所需数据的方式,构建出完整的HTML字符串。由于基于字符串的模板方法依赖于innerHTML的渲染,所以会带来以下问题。
(1)安全问题:使用innerHTML构建DOM具有安全隐患[4],用于渲染的动态数据可能存在安全漏洞,如果没有经过特定的转义处理,就有可能造成XSS攻击或者CSRF攻击[5]。
(2)性能问题:使用innerHTML替换DOM效率较低,即使仅替换DOM的一个属性或文本内容,也必须通过innerHTML替换整个DOM,从而导致浏览器的重排和重绘。
(3)开发效率问题:由于是通过正则表达式匹配后在特定函数中拼接字符串,所以容易造成重复计算,而且完全移除现有的DOM,再重新渲染一遍,挂载在DOM上的事件和状态都将不复存在,从而降低了开发效率。
1.2 DOM-based模板技术
这是一种基于DOM节点的模板技术,通过innerHTML获取初始DOM结构,再通过DOM API层级从原始DOM属性中提取事件、指令、表达式和过滤器等信息,编译成Living DOM,从而完成数据Model和View的双向绑定。近年来,Google公司推出的AngularJS[6]就是DOM-based模板技术的代表。DOM-based模板技术比String-based模板技术更加灵活,功能也更加强大,达到了一定意义上的数据驱动,但其也存在以下问题。
(1)信息冗余:由于DOM-based模板技术通过innerHTML获取DOM编译节点,信息承载于属性中,造成了不必要的冗余,同时也会影响阅读,提升开发难度。一种解决办法就是通过读取属性后再进行删除处理,如此会影响性能,降低用户体验。
(2)初始节点获取问题:通过innerHTML获取初始节点,没有独立的语法解析器或词法解析器,与HTML是强依赖关系。初次进入DOM的内容是模板,渲染需要时间,所以会造成内容闪动。
1.3 Living template技术
这是一种基于动态模板思想的模板技术。Living template技术与String-based、DOM-based模板技术的最大区别是不依赖于innerHTML来渲染和提取所需信息。其主要思想是:首先,结合数据绑定技术,使用成熟的词法解析和语法解析技术,将输入的字符串解析成抽象语法树AST[7],而不是仅仅通过简单的正则表达式匹配特定语法,再进行字符串拼接;其次,通过对AST进行编译,创建具有数据动态绑定功能的Living DOM,从而避免使用innerHTML,解决了浏览器的元素闪动问题,提高了应用的安全性,其原理如图1所示。
图1 Living template原理图
从图1可知,输入的字符串通过词法解析器Lexer,生成对应的词法块。词法块通过语法解析器Parser,构建抽象语法树AST。然后将AST编译成具有动态数据绑定功能的Living DOM,从而实现View和Model的双向绑定。
2 DWTT设计与实现
由于Living template同时拥有String-based和DOM-based模板技术的优点,并可以巧妙地规避innerHTML,以较小的开销来实现局部更新,故本文基于Living template技术的思想,具体设计并实现了一种基于数据驱动的动态Web模板技术DWTT。
2.1 总体设计
DWTT总体设计如图2所示。顶层组件是DWTT的入口模块,统一管理各模块的依赖和引入,为动态模板技术和数据绑定技术提供相应接口。动态模板技术由词法解析器Lexer、语法解析器Parser、编译器Walker和过滤器Filter构成。Lexer的作用在于对字符串模板进行词法分析,通过特定的正则表达式标识每个词块的类型,生成词块对象[8]。Parser的作用在于对Lexer输出的词法对象数组进行语法分析,然后根据DWTT的模板语法,将各零散的词块拼接为有具体含义的语法对象,并根据父子级联关系输出为抽象语法树AST。Walker的作用在于将Parser构建出的AST根据不同的节点类型,通过不同的编译函数,生成对应的Living DOM,并挂载到页面中,完成组件UI的呈现。Filter可以在数据传入前根据一定的规则过滤掉一些信息,或者设置一些参数,再进行业务逻辑处理。数据绑定技术由脏检测Watch模块和指令Directive构成。Watch是实现数据驱动的核心,该模块实现脏检测的监听和更新操作,通过watch()方法来实现数据监听和回调函数的绑定,通过update()方法遍历观察者列表触发更新操作。Directive是对某个节点的特定功能增强,一般以属性的方式在节点上声明[9]。本文提出的内置指令有r-class、r-style、r-hide、on-xxx、ref等,开发者也可以根据项目需求扩展更多功能强大的指令,从而提高组件的功能性。
图2 DWTT的总体设计
2.2 详细设计
DWTT的核心在于动态模板技术和数据绑定技术,对此给出以下详细设计方案。
2.2.1 动态模板的设计
本文基于Living template思想,通过模板的纬度给出DWTT的设计方案,如图3所示。
图3 动态模板设计
字符串先经过Lexer词法解析器,根据解析模式的词法,通过特定的正则表达式匹配出特定的词块,输出由词块对象组成的数组。Lexer词法解析器拥有3种解析模式,最终都是将变量、注释、开标签“<”、闭合标签“>”、模板语法“{}”、属性等词块进行标识,解析为词法对象数组,具体结构如下:
[
{
"type": "TAG_OPEN",
"value": "input",
"pos": 0
},
{
"type": "OPEN",
"value": "if",
"pos": 7
}
]
其中,每个词法对象有3个key,其含义如下:
(1)type:用于标识词法,如STRING、NUMBER等;
(2)value:表示具体值;
(3)pos:表示该词在字符串中的位置索引。
Parser语法解析器分析输入的词法对象数组中每个词法对象的词法类型,生成对应的语法类型节点对象[10],再进行层层递归,将各语法节点挂载至对应的父语法节点下,构成结构与字符串模板一致的抽象语法树AST。通过type表示节点的词法类型,通过tag指定节点的标签命名,将节点的属性解析至attrs数组中,结构大致如下:
[
{
"type": "element",
"tag": "input",
"attrs": [{"type": "if",}]
}
]
抽象语法树AST构建完成后,进入Walker编译器,建立数据监听,生成Living DOM,并将其最终挂载在页面中。
2.2.2 数据绑定模块的设计
数据绑定模块设计如图4所示。
图4 数据绑定模块设计
本文采用脏检测的方式实现数据绑定和监听。脏检测是一种不关心如何以及何时改变数据,只关心在特定的检测阶段数据是否改变的数据监听技术。可以在批量处理完数据之后,再统一更新view,进行DOM的局部刷新。
前文提到AST构建完成后,会进入Walker编译器进行编译。DWTT将提供一个Watch模块,专门负责数据监听和修改。Watch模块对外提供两个方法:
(1) DWTT.$watch(),该方法接受两个入参,第一个是被监控的表达式,第二个是监听函数。当被监控的表达式发生变化时,监听函数就会运行。该方法返回一个watch对象。
(2) DWTT.$update(),该方法接受两个入参,第一个是需要更新的表达式,第二个是更新的值。不带参数则直接进入脏检测阶段。
当Walker编译器遇到template、express等插值节点时,会创建一个textNode,当数据变化时,修改textNode的属性textContent,并生成watch对象,返回的watch对象包含以下信息:
{
id: uid, // watch对象的唯一id
fn: function(){}, //传入的监听函数
get: function(){}, //获取被监控的表达式
set: function(){} , //用于部分数据的设置
once: false, //是否只监听一次
last: undefined, //上一次表达式的值
dirty: false, //是否为脏状态
}
在实例化元素和组件时会执行一次$update方法,触发脏检测,此阶段的执行步骤如下:
(1)标记dirty为false;
(2)遍历该UI所有通过$watch方法绑定的观察者watcher,通过get方法获取当前值并与last进行对比,如果相同则跳过,否则将last改为当前值,并运行fn方法,将dirty值设为true;
(3)编译检测一轮后,如果dirty值为true,则重新进入步骤(1),否则进入步骤(4);
(4)完成脏检测。
Living DOM生成并挂载于页面中,再通过r-model、on-click等方式自动触发$update方法进而触发脏检测,从而实现数据绑定。
2.3 DWTT的实现
DWTT选择JavaScript语言来实现,通过bower实现前端工作流,按照CommonJS规范进行模块化开发。
2.3.1 顶层组件模块
顶层组件模块是整个DWTT的入口,其核心逻辑如下:
/*newC1.实例化,data参数在definition中*/
var DWTT = function(definition, options){
this.$refs = {};
/*newC2.将模板加载*/
template = this.template;
/*newC3.将模板解析为AST语法树*/
template = new Parser(template).parse();
/*newC4.处理计算属性*/
this.computed = handleComputed();
/*newC5.触发config事件*/
this.$emit("$config");
/*newC6.编译AST语法树为LivingDOM*/
this.group = this.$compile(namespace);
/*newC7.触发第一次脏检测*/
if(!this.$parent) this.$update();
/*newC8.触发init事件*/
this.$emit("$init");
if( this.init ) this.init(this.data);}
首先将传入组件的数据和事件进行整合,然后对组件的字符串模板进行词法和语法解析,并构建抽象语法树AST,完成后触发config事件,执行config()方法。然后进入Walker编译阶段,通过Watch模块对组件进行数据绑定,通过$watch()方法将依赖属性加入观察者列表,构建Living DOM,完成后触发init事件,宣告初始化完成并执行init()方法。
2.3.2 词法解析器Lexer
Lexer的作用在于对字符串模板进行词法分析,标识每个词块的类型,其主要设计见图5。
图5 Lexer的设计
Lexer词法解析器拥有3种解析模式。JST解析模式主要是为了适应公司互联网产品新老交替的过渡,TAG解析模式是通过script标签获取字符串模板从而方便统一管理,Init解析模式的解析语法和TAG解析模式的类似,区别在于前者从组件定义处而不是script标签处获取字符串模板。Lexer通过判断字符串模板的标识来决定采用哪种解析方式。以JST解析模式下的JST_STRING类型为例:
JST_STRING:[/′([^′]*)′|″([^″]*)″/, function(all, one, two){′
return{type: ′STRING′, value:one‖two‖″″}
}, ′JST′]
各个词法类型均以特定的正则表达式匹配,从而生成词法对象数组传递给Parser。
2.3.3 语法解析器Parser
Parser根据自定义语法,将零散的词块拼接为有具体含义的语法对象,并输出为AST。对词法对象数组中的每个对象进行遍历操作,然后进入解析流程。根据Lexer得出的词法type,对各个词法对象进行不同的处理从而得到一棵由7种语法节点对象组成的AST,其主要流程见图6。
图6 Parser的流程
7种语法节点类型为:
(1)if:条件判断节点,如{#if} {#elseif} {#else} {/if};
(2)list:循环列表节点,如{#list arr as item}{/list};
(3)expression:表达式,如{100+hello};
(4)element:元素节点,包含component组件;
(5)attribute:属性节点,如r-model;
(6)text:文本节点,字符串中包含的文本;
(7)template:模板节点,如{hello}。
2.3.4 编译器Walker
Walker的作用在于将Parser构建出的AST编译为Living DOM。首先将AST遍历到单个节点级别,根据AST节点类型进入不同的编译函数。本文设计了7种编译函数,和AST的7种语法节点类型相对应,分别为:walkers.if、walkers.list、walkers.expression、walkers.element、walkers.attribute、walkers.text和walkers.template。每一个编译函数都有自己独特的逻辑处理,比如walkers.expression方法,对模板进行数据绑定,其主要逻辑如下:
walkers.expression = function(ast, options){
var node = document.createTextNode("");
//将表达式加入watcher队列,进行监听
this.$watch(ast, function(newval){
//绑定更新执行的函数,即DOM局部更新
dom.text(node, "" + (newval) );},
{init: true})
return node;}
编译函数执行完毕后,再通过DOM操作将Living DOM挂载到页面中,从而完成整个编译过程,将数据驱动的组件UI呈现于用户界面。
3 实验分析
本文根据模板技术类型的不同,选取了采用String-based的doT和Mustache,采用DOM-based的Angular、Vue与本文提出的基于数据驱动的动态Web模板DWTT进行性能对比实验。数据驱动模板技术的性能测试核心在于对字符串模板中内建DSL的解析速度。实验测试环境为Win7,CPU为i5-4200,主频为2.5 GHz,系统内存为4 GB。
实验数据为1000个简单对象组成的数组集合。首先编写各模板技术对应的测试用例,将实验数据集传入各模板并渲染200次,记录执行时间。然后使用Highchart将执行所需时间的测试结果通过可视化的方式呈现,如图7所示。
由图7可知,性能最好的是doT,但由于其无法实现数据驱动,功能性较差。DWTT与同样采用脏检测技术的Angular相比具有性能上的绝对优势。由于Vue使用的是依赖追踪技术,通过采用ES5标准的Object.defineProperty方法,给变量的set和get函数添加检测依赖于该变量的watcher对象,所以其性能得到提升,但是Vue会带来兼容性问题,故本文未采用依赖追踪技术,而是采用脏检测技术进行数据绑定。
图7 模板引擎性能测试结果
4 结语
本文研究了各主流模板技术的设计思想和技术实现思路后,在Living template的基础上,具体设计并实现了一种基于数据驱动的动态Web模板技术DWTT,并进行了性能对比实验。DWTT能有效提高前端开发效率和代码复用性,并在实际运用中获得了较好的体验和反馈。当然,DWTT也存在不足之处,通过脏检测进行数据监听,当变量间依赖关系过于复杂时会导致计算量较大,对性能有一定的影响。此类问题可考虑依赖追踪技术来进一步改善。
[1] 孙光明, 王硕. 基于JSON的Ajax数据通信快速算法[J]. 计算机应用与软件, 2015, 32(1):263-266.
[2] Hoy M B. HTML5: a new standard for the Web[J]. Medical Reference Services Quarterly, 2011,
30(1):50-55.
[3] 杨颖莹.高性能Web框架的分析与应用[D].北京:北京邮电大学, 2012.
[4] Lin A W, Barceló P. String solving with word equations and transducers: towards a logic for analysing mutation XSS[J]. ACM SIGPLAN Notices, 2016, 51(1):123-136.
[5] You J X, Guo F. Improved CSRFGuard for CSRF attacks defense on Java EE platform[C]// International Conference on Computer Science and Education. IEEE, August 22-24, 2014:1115-1120.
[6] Jain N, Mangal P, Mehta D. AngularJS: a modern MVC framework in JavaScript[J].Journal of Global Research in Computer Science,2014,5(12):17-23.
[7] 李郑,李姝,王俊,等. 基于抽象语法树分析的版本控制分支合并算法[J]. 计算机系统应用, 2015, 24(3):139-146.
[8] Sulzmann M, van Steenhoven P. A flexible and efficient ML Lexer tool based on extended regular expression submatching[C]//Compiler Construction: Proceedings of 23rd International Conference, CC 2014, Held as Part of the European Joint Conferences on Theory and Practice of Software. Springer Berlin Heidelberg, 2014:174-191.
[9] Ruebbelke L, Ford B. AngularJS in action[M]. Shelter Island: Manning Publications Co., 2015.
[10]Rosa R, Zabokrtsky Z. KLcpos3-a language similarity measure for delexicalized parser transfer[C]//Proceedings of the 53rd Annual Meeting of the Association for Computational Linguistics and the 7th International Joint Conference on Natural Language Processing.Beijing, July 26-31, 2015:243-249.
[责任编辑 尚 晶]
Design and implementation of data-driven dynamic Web template technology
LiuFang,ChenHeping
(1. College of Computer Science and Technology, Wuhan University of Science and Technology, Wuhan 430065, China;2. Hubei Province Key Laboratory of Intelligent Information Processing and Real-time Industrial System, Wuhan University of Science and Technology, Wuhan 430065, China)
With the continuous expansion of Internet Web products, such problems as large scale, low reusability, difficulty in organizing and maintaining, poor expansibility and so on are emerging in the front-end script codes. So this paper studies the current mainstream Web template technologies and analyzes their merits and demerits. In light of the compatibility of domestic browsers, a data-driven dynamic Web template technology named as DWTT is put forward based on the thoughts of Living template technology. DWTT can improve the front-end development efficiency and code reusability, and reduce the complexity of program extension and maintenance.
template; Web application; front-end development; data-driven; abstract syntax tree
2016-11-28
国家自然科学基金资助项目(61100133).
刘 放(1992-),男,武汉科技大学硕士生. E-mail:brizer@163.com
陈和平(1956-),男,武汉科技大学教授,博士. E-mail:chp@wust.edu.cn
10.3969/j.issn.1674-3644.2017.01.014
TP31
A
1674-3644(2017)01-0070-06