一个前端js模板引擎的实现和优化

84
12520日星期日

Upload: tblanlan

Post on 01-Jul-2015

27.350 views

Category:

Technology


10 download

TRANSCRIPT

12年5月20日星期日

一个前端 Javascript 模板引擎的实现与优化

流火 @ TaobaoUEDBlog: http://benben.cc

12年5月20日星期日

Juicer 的诞生

1 循环 {@each}⋯{@/each}

2 判断 {@if}⋯{@else if}⋯{@else}⋯{@/if}

3 变量(支持自定义函数)${varname|function}

4 注释 {# comment here}

5 辅助循环 {@each i in range(0,9)}

support node.js only 5kb

12年5月20日星期日

静态页面 含有丰富交互的应用 服务端后端提供纯数据(接口化)前端负责将数据生成页面

12年5月20日星期日

MVC 需要模板

MVC最早是在SmallTalk语言的开发过程中总结出的一种设

计模式,MVC分别代表了"模型"、"视图"和"控制",目的就

是让不同的开发角色在大中型项目中各司其职。在网络应

用程序的开发中,可以用下图来表示各概念之间的关系。

12年5月20日星期日

模板引擎的分类

置换型模板引擎。

置换就是将规定好的文本标记替换为目标内容。这种模板引擎实现简单,除了标签替换之外,很少

支持诸如子模板引用、流程控制等。正如上面所说,我们几乎天天都在使用这种模板引擎。可以

说,置换型模板引擎的思想是整个模板引擎界的基础。

解释型模板引擎。

解释型模板引擎的原理还是标记置换。只不过有了解释器的存在,可以支持更加复杂的标记和语

法。

编译型模板引擎。

编译型模板引擎是相对于解释型模板引擎来说的,实际上定名为编译型模板引擎也不太合适,但我

也没想到别的好名字,哈!

12年5月20日星期日

模板引擎的基本机理就是替换(转换),将指定的标签转换为

需要的业务数据;将指定的伪语句按照某种流程来变换输出。

模板引擎基本原理

The gist of JavaScript templates is that you can take an HTML fragment interpolated with template variables and combine it with a JavaScript object, replacing those tem- plate variables with values from the object. Overall, JavaScript templating works in much the same way as templating libraries in other languages, such as PHP’s Smarty, Ruby’s ERB, and Python’s string formatting.

- Javascript Web Application

12年5月20日星期日

JSON Template

12年5月20日星期日

JSON Template

12年5月20日星期日

JSON TemplateTemplateEngine

12年5月20日星期日

JSON TemplateTemplateEngine

12年5月20日星期日

JSON TemplateTemplateEngine

12年5月20日星期日

JSON TemplateTemplateEngine

HTMLFragment

12年5月20日星期日

JSON TemplateTemplateEngine

HTMLFragment

water juicer fruits

12年5月20日星期日

var html = '<span class="name">' + json.name + ' (blog: ' + json.blog + ')</span>';

<span class="name">流火 (blog: ued.taobao.com)</span>

var json = { name: "liuhuo", blog: "ued.taobao.com"};

拼字符串

12年5月20日星期日

拼字符串的进化

var tpl = '<span class="name">{name} (blog: {blog})</span>';

var html = sub(tpl, json);

12年5月20日星期日

function sub(str,data) { return str .replace(/{(.*?)}/igm,function($,$1) { return data[$1]?data[$1]:$; });}

拼字符串的进化

var tpl = '<span class="name">{name} (blog: {blog})</span>';

var html = sub(tpl, json);

12年5月20日星期日

YUI.Lang.sub

12年5月20日星期日

现实中的使用场景

12年5月20日星期日

12年5月20日星期日

Mustache

Kissy template

jQuery tmpl

nTenjin

...micro template

ejsdoT yayaTemplate

artTemplatehandlebar

12年5月20日星期日

12年5月20日星期日

is it nessary ? are u sure ?

如无必要,勿增实体

12年5月20日星期日

12年5月20日星期日

Security

12年5月20日星期日

SecurityXSS escape

12年5月20日星期日

Syntax

SecurityXSS escape

12年5月20日星期日

Syntax

SecurityXSS escape

easy to write & read

12年5月20日星期日

Syntax Performance

SecurityXSS escape

easy to write & read

12年5月20日星期日

Syntax Performance

SecurityXSS escape

easy to write & read as faster as you can

12年5月20日星期日

Syntax Performance

SecurityXSS escape

Error Handling

easy to write & read as faster as you can

12年5月20日星期日

Syntax Performance

SecurityXSS escape

Error Handling

easy to write & read as faster as you can

robustness

12年5月20日星期日

语法

the templating syntax for most libraries is very similar, if not identical.

- Javascript Web Application

1. 纯粹派

2. KISS派

3. 中立派

* 资本主义的无限制进展,无疑的要促进反资本主义,即共产主义。—— 《人生十论》

纯粹派(以ejs、nTenjin、doT为代表):追求纯粹,希望通过原生的 Javascript 语法来写模板,这类模板

引擎的实现一定是编译型的,但是它们的写法往往让人抓狂。

KISS派(以Mustache、Handlebar为代表):追求 KISS / Logic less, 这注定了这类模板引擎的实现是解释型

的,也就注定了它们性能是有瓶颈的。

12年5月20日星期日

立场

中立派,站在理性的角度重视性能

浏览器对JavaScript有两个限制:调用栈大小限制和长时间运行

脚本限制。不同浏览器对脚本运行时间的限制不一样。Firefox为

10秒,Safari为5秒,Chrome没有单独的限制。一般情况下,

JavaScript运行时间不要超过100ms,如果超过100ms,用户就

会感觉失去了对界面的控制。

100ms * 100w = 27.777hours

12年5月20日星期日

<ul>    {@each data.list as it,k}        {@if it.show}            <li>${it.name} (index: ${k})</li>        {@/if}    {@/each}    {@each data.blah as it}        <li>            num: ${it.num}            {@if it.num==3}                {@each it.inner as it2}                    <li>${it2.time}</li>                {@/each}            {@/if}        </li>    {@/each}</ul>

<ul>    {{#each list as it,k}}        {{#if it.show}}            <li>{{it.name}} (index: {{k}})</li>        {{/if}}    {{/each}}    {{#each blah as it}}        <li>            num: {{it.num}}            {{#if it.num==3}}                {{#each it.inner as it2}}                    <li>{{it2.time}}</li>                {{/each}}            {{/if}}        </li>    {{/each}}</ul>

<ul>    {{#list}}        {{#show}}            <li>{{name}} (index: {{index}})</li>        {{/show}}    {{/list}}    {{#blah}}        <li>            num: {{num}}            ...            {{#inner}}                <li>{{time}}</li>            {{/inner}}            ...        </li>    {{/blah}}</ul>

<ul>    <% for(var i=0;i<list.length;i++) { %>        <% if(list[i].show) { %>            <li><%= list[i].name %> (index: <%= i %>)</li>        <% } %>    <% } %>    <% for(var i=0;i<blah.length;i++) { %>        <li>            num: <%= blah[i].num %>            <% if(blah[i].num==3) { %>                           <% for(var j=0;j<blah[i].inner.length;j++) { %>                    <li><%= blah[i].inner[j].time %></li>                <% } %>            <% } %>        </li>    <% } %></ul>

<ul>    <?js for(var i=0;i<it.list.length;i++) { ?>        <?js if(it.list[i].show) { ?>            <li>${it.list[i].name} (index: ${i})</li>        <?js } ?>    <?js } ?>    <?js for(var i=0;i<it.blah.length;i++) { ?>        <li>            num: ${it.blah[i].num}            <?js if(it.blah[i].num==3) { ?>                           <?js for(var j=0;j<it.blah[i].inner.length;j++) { ?>                    <li>${it.blah[i].inner[j].time}</li>                <?js } ?>            <?js } ?>        </li>    <?js } ?></ul>

语法

12年5月20日星期日

var data={    list:[        {name:'liuhuo',show:true},        {name:'benben',show:false},        {name:'taobao',show:true}    ],    blah:[        {num:1},        {num:2},        {num:3,inner:[            {'time':'15:00'},            {'time':'16:00'},            {'time':'17:00'},            {'time':'18:00'}        ]},        {num:4}    ]};

JSON

12年5月20日星期日

var data={    list:[        {name:'liuhuo',show:true},        {name:'benben',show:false},        {name:'taobao',show:true}    ],    blah:[        {num:1},        {num:2},        {num:3,inner:[            {'time':'15:00'},            {'time':'16:00'},            {'time':'17:00'},            {'time':'18:00'}        ]},        {num:4}    ]};

JSON 预期结果

<ul>    <li>liuhuo (index: 0)</li>    <li>benben (index: 1)</li>    <li>taobao (index: 2)</li>    <li>num: 1</li>    <li>num: 2</li>    <li>num: 3</li>    <li>15:00</li>    <li>16:00</li>    <li>17:00</li>    <li>18:00</li>    <li>num: 4</li></ul>

12年5月20日星期日

12年5月20日星期日

juicer

http://jsfiddle.net/paulguo/LpvwH/1/

<ul>    {@each data.list as it,k}        {@if it.show}            <li>${it.name} (index: ${k})</li>        {@/if}    {@/each}    {@each data.blah as it}        <li>            num: ${it.num}            {@if it.num==3}                {@each it.inner as it2}                    <li>${it2.time}</li>                {@/each}            {@/if}        </li>    {@/each}</ul>

12年5月20日星期日

kissy

http://jsfiddle.net/paulguo/yLg4Y/1/

<ul>    {{#each list as it,index}}        {{#if it.show}}            <li>{{it.name}} (index: {{index}})</li>        {{/if}}    {{/each}}    {{#each blah as it}}        <li>            num: {{it.num}}            {{#if it.num==3}}                {{#each it.inner as it2}}                    <li>{{it2.time}}</li>                {{/each}}            {{/if}}        </li>    {{/each}}</ul>

12年5月20日星期日

kissy

http://jsfiddle.net/paulguo/yLg4Y/1/

<ul>    {{#each list as it,index}}        {{#if it.show}}            <li>{{it.name}} (index: {{index}})</li>        {{/if}}    {{/each}}    {{#each blah as it}}        <li>            num: {{it.num}}            {{#if it.num==3}}                {{#each it.inner as it2}}                    <li>{{it2.time}}</li>                {{/each}}            {{/if}}        </li>    {{/each}}</ul>

12年5月20日星期日

mustache

http://jsfiddle.net/paulguo/VCH9k/1/

<ul>    {{#list}}        {{#show}}            <li>{{name}} (index: {{index}})</li>        {{/show}}    {{/list}}    {{#blah}}        <li>            num: {{num}}            {{#inner}}                <li>{{time}}</li>            {{/inner}}        </li>    {{/blah}}</ul>

12年5月20日星期日

mustache

http://jsfiddle.net/paulguo/VCH9k/1/

<ul>    {{#list}}        {{#show}}            <li>{{name}} (index: {{index}})</li>        {{/show}}    {{/list}}    {{#blah}}        <li>            num: {{num}}            {{#inner}}                <li>{{time}}</li>            {{/inner}}        </li>    {{/blah}}</ul>

12年5月20日星期日

micro

http://jsfiddle.net/paulguo/KsDhC/1/

<ul>    <% for(var i=0;i<list.length;i++) { %>        <% if(list[i].show) { %>            <li><%= list[i].name %> (index: <%= i %>)</li>        <% } %>    <% } %>    <% for(var i=0;i<blah.length;i++) { %>        <li>            num: <%= blah[i].num %>            <% if(blah[i].num==3) { %>                           <% for(var j=0;j<blah[i].inner.length;j++) { %>                    <li><%= blah[i].inner[j].time %></li>                <% } %>            <% } %>        </li>    <% } %></ul>

12年5月20日星期日

micro

http://jsfiddle.net/paulguo/KsDhC/1/

<ul>    <% for(var i=0;i<list.length;i++) { %>        <% if(list[i].show) { %>            <li><%= list[i].name %> (index: <%= i %>)</li>        <% } %>    <% } %>    <% for(var i=0;i<blah.length;i++) { %>        <li>            num: <%= blah[i].num %>            <% if(blah[i].num==3) { %>                           <% for(var j=0;j<blah[i].inner.length;j++) { %>                    <li><%= blah[i].inner[j].time %></li>                <% } %>            <% } %>        </li>    <% } %></ul>

12年5月20日星期日

<ul>    <?js for(var i=0;i<it.list.length;i++) { ?>        <?js if(it.list[i].show) { ?>            <li>${it.list[i].name} (index: ${i})</li>        <?js } ?>    <?js } ?>    <?js for(var i=0;i<it.blah.length;i++) { ?>        <li>            num: ${it.blah[i].num}            <?js if(it.blah[i].num==3) { ?>                           <?js for(var j=0;j<it.blah[i].inner.length;j++) { ?>                    <li>${it.blah[i].inner[j].time}</li>                <?js } ?>            <?js } ?>        </li>    <?js } ?></ul>

nTenjin

http://jsfiddle.net/paulguo/W7eVV/1/

12年5月20日星期日

<ul>    <?js for(var i=0;i<it.list.length;i++) { ?>        <?js if(it.list[i].show) { ?>            <li>${it.list[i].name} (index: ${i})</li>        <?js } ?>    <?js } ?>    <?js for(var i=0;i<it.blah.length;i++) { ?>        <li>            num: ${it.blah[i].num}            <?js if(it.blah[i].num==3) { ?>                           <?js for(var j=0;j<it.blah[i].inner.length;j++) { ?>                    <li>${it.blah[i].inner[j].time}</li>                <?js } ?>            <?js } ?>        </li>    <?js } ?></ul>

nTenjin

http://jsfiddle.net/paulguo/W7eVV/1/

12年5月20日星期日

性能 - jsperf

12年5月20日星期日

http://jsperf.com/javascript-template-engine/15

性能 - jsperf

12年5月20日星期日

性能 - 最大化渲染测试

12年5月20日星期日

性能 - 最大化渲染测试

12年5月20日星期日

性能 - 最大化渲染测试

12年5月20日星期日

Security

var json={! output:'<script>alert("XSS");</script>'};

12年5月20日星期日

Security

var json={! output:'<script>alert("XSS");</script>'};

document.write

12年5月20日星期日

Security

var json={! output:'<script>alert("XSS");</script>'};

document.write $(node).html()

12年5月20日星期日

Security

var json={! output:'<script>alert("XSS");</script>'};

juicer.to_html('${output}',json);//输出:&lt;script&gt;alert("XSS");&lt;/script&gt;

juicer.to_html('$${output}',json);//输出:<script>alert("XSS");</script>

document.write $(node).html()

12年5月20日星期日

template

12年5月20日星期日

template reusable functioncompile

12年5月20日星期日

template reusable functioncompiled template

compile

12年5月20日星期日

template reusable function html codecompiled template

compile render

12年5月20日星期日

var json={    list:[        {name:"benben"},        {name:"liuhuo"}    ]};

var tpl='{@each data.list as value, key} $${value.name} {@/each}';var compiled_tpl=juicer(tpl);

12年5月20日星期日

function anonymous(data) {    var data = data || {};    var out = '';    out += '';    for (var i0 = 0, l = data.list.length; i0 < l; i0++) {        var value = data.list[i0];        var key = i0;        out += '';        out += ((value.name));        out += '';    }    out += '';    return out;}

12年5月20日星期日

一些性能优化点

12年5月20日星期日

一些性能优化点

using += instead of array.push

12年5月20日星期日

When evaluating this code, four steps are taken:

1. A temporary string is created in memory.2. The concatenated value "onetwo" is assigned to the temporary string. 3. The temporary string is concatenated with the current value of str. 4. The result is assigned to str.

- Hign Performance Javascript

str += "one" + "two";

12年5月20日星期日

This dramatic improvement results from avoiding repeatedly allocating memory for and copying progressively larger and larger strings. When joining an array, the browser allocates enough memory to hold the complete string, and never copies the same part of the final string more than once.

- Hign Performance Javascript

str = ["one", "two"].join(‘’);

12年5月20日星期日

12年5月20日星期日

Browser string optimizations have changed the string concatenation picture.

Firefox was the first browser to optimize string concatenation. Beginning with version 1.0, the array technique is actually slower than using the plus operator in all cases. Other browsers have also optimized string concatenation, so Safari, Opera, Chrome, and

Internet Explorer 8 also show better performance using the plus operator. Internet Explorer prior to version 8 didn’t have such an optimization, and so the array technique is always faster than the plus operator.

— Writing Efficient JavaScript: Chapter 7 – Even Faster Websites

12年5月20日星期日

// ECMA-262, section 15.5.4.6function StringConcat() {  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {    throw MakeTypeError("called_on_null_or_undefined", ["String.prototype.concat"]);  }  var len = %_ArgumentsLength();  var this_as_string = TO_STRING_INLINE(this);  if (len === 1) {    return this_as_string + %_Arguments(0);  }  var parts = new InternalArray(len + 1);  parts[0] = this_as_string;  for (var i = 0; i < len; i++) {    var part = %_Arguments(i);    parts[i + 1] = TO_STRING_INLINE(part);  }  return %StringBuilderConcat(parts, len + 1, "");}

The V8 engine (used in Google Chrome) uses this code to do string concatenation:

12年5月20日星期日

avoid using with block

一些性能优化点

12年5月20日星期日

12年5月20日星期日

var person = { name: "Nicholas", age: 30};

function displayInfo(){ var count = 5; with(person){ alert(name + " is " + age); alert("Count is " + count); }}

displayInfo();

The with construct introduces an extra scope for the script engine to search through whenever a variable is referenced. This alone produces a minor performance decrease. However, the contents of that scope are not known at compile time, meaning that the compiler cannot optimize for it, in the same way as it can with normal scopes (such as those created by functions).

12年5月20日星期日

cache the compiled template.

一些性能优化点

12年5月20日星期日

JavaScript, like many scripting languages, allows you to take a string containing code and execute it from within running code. There are four standard ways to accomplish this: eval(), the Function() constructor, setTimeout(), and setInterval(). Each of these functions allows you to pass in a string of JavaScript code and have it executed.

- Hign Performance Javascript

Cache The Compiled Template

12年5月20日星期日

Juicer 的使用方法.

12年5月20日星期日

12年5月20日星期日

12年5月20日星期日

12年5月20日星期日

{@each list as it, k}

<span class=”{@if k+2>list.length}notdot{@/if}”>...</span>

{@/each}

12年5月20日星期日

http://taobao.com/s?q=%E6%B7%98%E5%AE%9D

json={query:’淘宝’,...}

http://taobao.com/s?q=${query|encodeURIComponent}

12年5月20日星期日

12年5月20日星期日

12年5月20日星期日

12年5月20日星期日

juicer(‘${over}’, {over:‘thanks!’});

Q & A

12年5月20日星期日