将Node List转换为数组(Array)


querySelectorAll方法返回一个类数组对象称为node list。这些数据结构被称为“类数组”,因为他们看似数组却没有类似mapforeach这样的数组方法。这是一个快速、安全、可重用的方法将node list转换为DOM元素的数组:

<code><span class="kr">const</span> <span class="nx">nodelist</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="s1">'div'</span><span class="p">);</span>
<span class="kr">const</span> <span class="nx">nodelistToArray</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="nx">apply</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="nx">nodelist</span><span class="p">);</span>


<span class="c1">//之后 ..</span>

<span class="nx">nodelistToArray</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(...);</span>
<span class="nx">nodelistToArray</span><span class="p">.</span><span class="nx">map</span><span class="p">(...);</span>
<span class="nx">nodelistToArray</span><span class="p">.</span><span class="nx">slice</span><span class="p">(...);</span>


<span class="c1">//等...</span>
</code>

apply方法可以在指定this时以数组形式向方法传递参数。MDN规定apply可以接受类数组对象,恰巧就是querySelectorAll方法所返回的内容。如果我们不需要指定方法内this的值时传null0即可。返回的结果即包含所有数组方法的DOM元素数组。

如果你正在用ES2015你可以使用展开运算符 ...

<code><span class="kr">const</span> <span class="nx">nodelist</span> <span class="o">=</span> <span class="p">[...</span><span class="nb">document</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="s1">'div'</span><span class="p">)];</span> <span class="c1">// 返回一个真正的数组</span>

<span class="c1">//之后 ..</span>

<span class="nx">nodelist</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(...);</span>
<span class="nx">nodelist</span><span class="p">.</span><span class="nx">map</span><span class="p">(...);</span>
<span class="nx">nodelist</span><span class="p">.</span><span class="nx">slice</span><span class="p">(...);</span>


<span class="c1">//等...

还能 Array.from(document.querySelectorAll('div'))
</span></code>
e4ea604f8097922a068674194a662b0c

声明式编程和命令式编程的比较


Imperative-vs-Declarative

英文原文:Imperative vs Declarative

先统一一下概念,我们有两种编程方式:命令式和声明式。

我们可以像下面这样定义它们之间的不同:

  • 命令式编程:命令“机器”如何去做事情(how),这样不管你想要的是什么(what),它都会按照你的命令实现。
  • 声明式编程:告诉“机器”你想要的是什么(what),让机器想出如何去做(how)。

声明式编程和命令式编程的代码例子

举个简单的例子,假设我们想让一个数组里的数值翻倍。

我们用命令式编程风格实现,像下面这样:

var numbers = [1,2,3,4,5]
var doubled = []
for(var i = 0; i &lt; numbers.length; i++) {
  var newNumber = numbers[i] * 2
  doubled.push (newNumber)
}
console.log (doubled) //=&gt; [2,4,6,8,10]

我们直接遍历整个数组,取出每个元素,乘以二,然后把翻倍后的值放入新数组,每次都要操作这个双倍数组,直到计算完所有元素。

而使用声明式编程方法,我们可以用 Array.map 函数,像下面这样:

var numbers = [1,2,3,4,5]
var doubled = numbers.map (function (n) {
  return n * 2
})
console.log (doubled) //=&gt; [2,4,6,8,10]

map利用当前的数组创建了一个新数组,新数组里的每个元素都是经过了传入map的函数(这里是function (n) { return n*2 })的处理。

map函数所做的事情是将直接遍历整个数组的过程归纳抽离出来,让我们专注于描述我们想要的是什么(what)。注意,我们传入map的是一个纯函数;它不具有任何副作用(不会改变外部状态),它只是接收一个数字,返回乘以二后的值。

在一些具有函数式编程特征的语言里,对于 list 数据类型的操作,还有一些其他常用的声明式的函数方法。例如,求一个list里所有值的和,命令式编程会这样做:

var numbers = [1,2,3,4,5]
var total = 0 for(var i = 0; i &lt; numbers.length; i++) {
  total += numbers[i]
}
console.log (total) //=&gt; 15

而在声明式编程方式里,我们使用reduce函数:

var numbers = [1,2,3,4,5]
var total = numbers.reduce (function (sum, n) {
  return sum + n
});
console.log (total) //=&gt; 15

reduce函数利用传入的函数把一个list运算成一个值。它以这个函数为参数,数组里的每个元素都要经过它的处理。每一次调用,第一个参数(这里是sum)都是这个函数处理前一个值时返回的结果,而第二个参数(n)就是当前元素。这样下来,每此处理的新元素都会合计到sum中,最终我们得到的是整个数组的和。

同样,reduce函数归纳抽离了我们如何遍历数组和状态管理部分的实现,提供给我们一个通用的方式来把一个list合并成一个值。我们需要做的只是指明我们想要的是什么

声明式编程很奇怪吗?

如果你之前没有听说过mapreduce函数,你的第一感觉,我相信,就会是这样。作为程序员,我们非常习惯去指出事情应该如何运行。“去遍历这个list”,“if 这种情况 then 那样做”,“把这个新值赋给这个变量”。当我们已经知道了如何告诉机器该如何做事时,为什么我们需要去学习这种看起来有些怪异的归纳抽离出来的函数工具?

在很多情况中,命令式编程很好用。当我们写业务逻辑,我们通常必须要写命令式代码,没有可能在我们的专项业务里也存在一个可以归纳抽离的实现。

但是,如果我们花时间去学习(或发现)声明式的可以归纳抽离的部分,它们能为我们的编程带来巨大的便捷。首先,我可以少写代码,这就是通往成功的捷径。而且它们能让我们站在更高的层面是思考,站在云端思考我们想要的是什么,而不是站在泥里思考事情该如何去做。

声明式编程语言:SQL

也许你还不能明白,但有一个地方,你也许已经用到了声明式编程,那就是SQL。

你可以把 SQL 当做一个处理数据的声明式查询语言。完全用SQL写一个应用程序?这不可能。但如果是处理相互关联的数据集,它就显的无比强大了。

像下面这样的查询语句:

SELECT * from dogs
INNER JOIN owners
WHERE dogs.owner_id = owners.id

如果我们用命令式编程方式实现这段逻辑:

//dogs = [{name: 'Fido', owner_id: 1}, {...}, ... ]
//owners = [{id: 1, name: 'Bob'}, {...}, ...] var dogsWithOwners = []
var dog, owner
for(var di=0; di &lt; dogs.length; di++) {
  dog = dogs[di]
  for(var oi=0; oi &lt; owners.length; oi++) {
    owner = owners[oi]
    if (owner &amp;&amp; dog.owner_id == owner.id) {
      dogsWithOwners.push ({
        dog: dog,
        owner: owner
      })
    }
  }}
}

我可没说SQL是一种很容易懂的语言,也没说一眼就能把它们看明白,但基本上还是很整洁的。

SQL代码不仅很短,不不仅容易读懂,它还有更大的优势。因为我们归纳抽离了how,我们就可以专注于what,让数据库来帮我们优化how。

我们的命令式编程代码会运行的很慢,因为需要遍历所有list里的每个狗的主人。

而SQL例子里我们可以让数据库来处理how,来替我们去找我们想要的数据。如果需要用到索引(假设我们建了索引),数据库知道如何使用索引,这样性能又有了大的提升。如果在此不久之前它执行过相同的查询,它也许会从缓存里立即找到。通过放手how,让机器来做这些有难度的事,我们不需要掌握数据库原理就能轻松的完成任务。

声明式编程:d3.js

另外一个能体现出声明式编程的真正强大之处地方是用户界面、图形、动画编程。

开发用户界面是有难度的事。因为有用户交互,我们希望能创建漂亮的动态用户交互方式,通常我们会用到大量的状态声明和很多相同作用的代码,这些代码实际上是可以归纳提炼出来的。

d3.js 里面一个非常好的声明时归纳提炼的例子就是它的一个工具包,能够帮助我们使用JavaScript和SVG来开发交互的和动画的数据可视化模型。

第一次(或第5次,甚至第10 =次)你开发d3程序时可能会头大。跟SQL一样,d3是一种可视化数据操作的强大通用工具,它能提供你所有how方法,让你只需要说出你想要什么。

下面是一个例子(我建议你看一下这个演示)。这是一个d3可视化实现,它为data数组里的每个对象画一个圆。为了演示这个过程,我们每秒增加一个圆。

里面最有趣的一段代码是:

//var data = [{x: 5, y: 10}, {x: 20, y: 5}]
var circles = svg.selectAll('circle')
                    .data(data)

circles.enter().append('circle')
           .attr('cx', function(d) { return d.x })
           .attr('cy', function(d) { return d.y })
           .attr('r', 0)
        .transition().duration(500)
          .attr('r', 5)

没有必要完全理解这段代码都干了什么(你需要一段时间去领会),但关键点是:

首先我们收集了svg里所有的圆,然后把data数组数据绑定到对象里。

D3对每个圆都绑定了那些点数据有一个关系表。最初我们只有两个点,没有圆,我们使用.enter()方法获取数据点。这里,我们的意图是画一个圆,中心是xy,初始值是0 ,半秒后变换成半径为5

为什么我说这很有意思?

从头再看一遍代码,想一想,我们是在声明我们想要的图案是什么样子,还是在说如何作图。你会发现这里根本没有关于how的代码。我们只是在一个相当高的层面描述我们想要的是什么

我要画圆,圆心在 data 数据里,当增加新圆时,用动画表示半径的增加。

这太神奇了,我们没有写任何循环,这里没有状态管理。画图操作通常是很难写,很麻烦,很让人讨厌,但这里,d3归纳提取了一些常用的操作,让我们专注于描述我们想要的是什么

现在再看,d3.js很容易理解吗?不是,它绝对需要你花一段时间去学习。而学习的过程基本上需要你放弃去指明如何做事的习惯,而去学会如何描述我想要的是什么。

最初,这可能是很困难的事,但经过一些时间的学习后,一些神奇的事情发生了——你变得非常非常有效率了。通过归纳提取how,d3.js能让你真正的专注说明你想要看到的是什么,让你在一个个更高的层面解决问题,解放你的创作力。

声明式编程的总结

声明式编程让我们去描述我们想要的是什么,让底层的软件/计算机/等去解决如何去实现它们。

在很多情况中,就像我们看到的一样,声明式编程能给我们的编程带来真正的提升,通过站在更高层面写代码,我们可以更多的专注于what,而这正是我们开发软件真正的目标。

问题是,程序员习惯了去描述how,这让我们感觉很好很舒服——强力——能够控制事情的发生发展,不放走任何我们不能看见不能理解的处理过程。

有时候这种紧盯着how不放的做法是没问题的。如果我需要对代码进行更高性能的优化,我需要对what进行更深一步的描述来指导how。有时候对于某个业务逻辑没有任何可以归纳提取的通用实现,我们只能写命令式编程代码。

但大多数时候,我们可以、而且应该寻求声明式的写代码方式,如果没有发现现成的归纳提取好的实现,我们应该自己去创建。起初这会很难,必定的,但就像我们使用SQL和D3.js, 我们会长期从中获得巨大的回报!

非常感谢 @srbaker, @maniacyak 和 @jcoglan 对这篇文章的的建议和补充。

50cd8b1044a55209c0f0ddb79717100e

js之事件冒泡和事件捕获详细介绍


(1)冒泡型事件:事件按照从最特定的事件目标到最不特定的事件目标(document对象)的顺序触发。

IE 5.5: div -> body -> document

IE 6.0: div -> body -> html -> document

Mozilla 1.0: div -> body -> html -> document -> window

(2)捕获型事件(event capturing):事件从最不精确的对象(document 对象)开始触发,然后到最精确(也可以在窗口级别捕获事件,不过必须由开发人员特别指定)。

(3)DOM事件流:同时支持两种事件模型:捕获型事件和冒泡型事件,但是,捕获型事件先发生。两种事件流会触及DOM中的所有对象,从document对象开始,也在document对象结束。

DOM事件模型最独特的性质是,文本节点也触发事件(在IE中不会)。

支持W3C标准的浏览器在添加事件时用addEventListener(event,fn,useCapture)方法,基中第3个参数useCapture是一个Boolean值,用来设置事件是在事件捕获时执行,还是事件冒泡时执行。而不兼容W3C的浏览器(IE)用attachEvent()方法,此方法没有相关设置,不过IE的事件模型默认是在事件冒泡时执行的,也就是在useCapture等于false的时候执行,所以把在处理事件时把useCapture设置为false是比较安全,也实现兼容浏览器的效果。

事件捕获阶段:事件从最上一级标签开始往下查找,直到捕获到事件目标(target)。
事件冒泡阶段:事件从事件目标(target)开始,往上冒泡直到页面的最上一级标签。

假设一个元素div,它有一个下级元素p。
<div>
<p>元素</p>
</div>
这两个元素都绑定了click事件,如果用户点击了p,它在div和p上都触发了click事件,那这两个事件处理程序哪个先执行呢?事件顺序是什么?

两种模型
以前,Netscape和Microsoft是不同的实现方式。

Netscape中,div先触发,这就叫做事件捕获。

Microsoft中,p先触发,这就叫做事件冒泡。

两种事件处理顺序刚好相反。IE只支持事件冒泡,Mozilla, Opera 7 和 Konqueror两种都支持,旧版本的Opera’s 和 iCab两种都不支持 。

事件捕获
当你使用事件捕获时,父级元素先触发,子级元素后触发,即div先触发,p后触发。

事件冒泡
当你使用事件冒泡时,子级元素先触发,父级元素后触发,即p先触发,div后触发。

W3C模型
W3C模型是将两者进行中和,在W3C模型中,任何事件发生时,先从顶层开始进行事件捕获,直到事件触发到达了事件源元素。然后,再从事件源往上进行事件冒泡,直到到达document。

程序员可以自己选择绑定事件时采用事件捕获还是事件冒泡,方法就是绑定事件时通过addEventListener函数,它有三个参数,第三个参数若是true,则表示采用事件捕获,若是false,则表示采用事件冒泡。

ele.addEventListener(‘click’,doSomething2,true)

true=捕获

false=冒泡

传统绑定事件方式
在一个支持W3C DOM的浏览器中,像这样一般的绑定事件方式,是采用的事件冒泡方式。

ele.onclick = doSomething2

IE浏览器
如上面所说,IE只支持事件冒泡,不支持事件捕获,它也不支持addEventListener函数,不会用第三个参数来表示是冒泡还是捕获,它提供了另一个函数attachEvent。

ele.attachEvent(“onclick”, doSomething2);

附:事件冒泡(的过程):事件从发生的目标(event.srcElement||event.target)开始,沿着文档逐层向上冒泡,到document为止。

事件的传播是可以阻止的:
• 在W3c中,使用stopPropagation()方法
• 在IE下设置cancelBubble = true;
在捕获的过程中stopPropagation();后,后面的冒泡过程也不会发生了~
3.阻止事件的默认行为,例如click <a>后的跳转~
• 在W3c中,使用preventDefault()方法;
• 在IE下设置window.event.returnValue = false;
4.哇,终于写完了,一边测试一边写的额,不是所有的事件都能冒泡,例如:blur、focus、load、unload,(这个是从别人的文章里摘过来的,我没测试)。

清除行内元素之间HTML空白的几种解决方案


问题来了,HTML源码中行内元素之间的空白有时候显示在屏幕上那是相当的讨厌。
当然,有一些技巧(方法)可以用来清除他们:比如粗暴地完全删除空白,或者其他的方法:

解决方案1: font-size:0;
最好的方法是在外层元素上设置font-size:0;同时在内层元素上指定字体具体的大小。

复制代码

代码如下:

ul.inline-block-list { /* 比如 ul 或者 ol元素 */
font-size: 0;
}
ul.inline-block-list li {
font-size: 14px; /* 设置具体的字体大小 */
}

为了抵消外层元素的字体属性,在内层元素必须指定 font-size 属性,当然这很简单。
假若代码是一种复杂的嵌套关系,那么你可能不好去计算或指定这些字体属性,但在大多数情况下,这就是你想要的效果!

解决方案2: HTML 注释
这种方法比较渣,但是效果也不错。使用HTML的注释标记顶替元素之间的空白:

复制代码

代码如下:

<ul>
<li>Item content</li><!–
–><li>Item content</li><!–
–><li>Item content</li>
</ul>

一个字来形容: 渣.如果用2个字来形容,那就是”渣渣”,用3个字来形容,”解决了”。

解决方案3: 指定margin属性值为负数
和方案2类似,这个也比较渣。可以使用行内元素的margin属性来抵消空白:

复制代码

代码如下:

ul.inline-block-list li {
margin-left: -4px;
}

这是最糟糕的解决方案了,因为你必须根据具体情况去计算,有时候还不对。你应该尽量避免这样做。

虽然这些方案都不是很理想,但是如果不这样处理,那你的HTML代码结构可能就非常混乱,成为标准的垃圾代码。
因为行内元素非常好用,所以这并不是一个小心避免的雷区,作为开发人员,学会处理这种空白问题也是很重要的。

fe00d7ccced1258b1002aac3dda642d2

一个对前端模板技术的全面总结


此文的写作耗时很长,称之为雄文不为过,小心慢用

此文缘由

其实从发布regularjs之后,我发现在google搜索regularjs 不是给我这个画面

regular-search-1

<!– more –>就是给我这个画面

regular-search-2

突然发现取名字真是个大学问,当时就基本预计到了会有不明真相的朋友认为它只是一个照搬angularjs的家伙,对于这点,有兴趣的朋友可以看下【为什么要造Regularjs这个轮子】

而在这个文章,我不会直截了当去与angular做直接的对比,而是从最基本原理开始对现有的模板解决方案进行一个全面的分类,同时会给出它们的一些或优或劣的特性,这些特性基本都是本质性的,即它不为维护者的水平高低和勤勉与否所限制,所以是具有客观性的。


什么是模板解决方案?

你可以先简单的理解为模板引擎。

事实上前端的模板解决方案已经从 “选出一个好用的模板好难” 发展到了 “从这么多模板中选一个好难的”的阶段,Template-Engine-Chooser!似乎也开始无法跟上节奏了。再加上目前Dom-based的模板技术的崛起(angularjs, knockout等),渐渐让这个领域有乱花渐欲迷人眼的感觉。

这篇文章会对当今前端界的三种截然不同的模板方案做一个全面的对比,它们分别是

  1. String-based 模板技术 (基于字符串的parse和compile过程)
  2. Dom-based 模板技术 (基于Dom的link或compile过程)
  3. 杂交的Living templating 技术 (基于字符串的parse 和 基于dom的compile过程)

同种类型的模板技术的可能性都是相同的,即同样身为dom-based的vuejs如果愿意可以发展为angularjs的相同功能层级。

(注: 其实这么说作者后续思考后觉得并不是很妥当,因为决定这类框架的还有重要一环就是它们的数据管理层:,比如是基于脏检查还是基于setter和getter,就会有截然不同的定位)

另外需要注意的是任何一种类型的模板技术都是不可被替代的,它们甚至可以结合使用,并且很长一段时间内还会继续共存下去。

除此之外另外一种奇葩模板技术本文也会提到即react,了解后你会发现它的特性更接近于Living templating。

在进入介绍之前,我们需要先过一下不得不说的 InnerHTML,它是本文的关键因素。

innerHTML

我不认为还需要从innerHTML的细节讲起,我们对它太熟悉了,那就直接从优劣开始讲吧!

innerHTML 毫无疑问是好的

innerHTML正是成为 web 标准 前,它当之无愧的已经是大家公认的事实标准,这是因为:

1 . 它便于书写并且直观

想象下你必须添加如下的html到你的文档里

<code class=" language-javascript"><span class="token operator">&lt;</span>h2 title<span class="token operator">=</span><span class="token string">"header"</span><span class="token operator">&gt;</span>title<span class="token operator">&lt;</span><span class="token operator">/</span>h2<span class="token operator">&gt;</span>
<span class="token operator">&lt;</span>p<span class="token operator">&gt;</span>content<span class="token operator">&lt;</span><span class="token operator">/</span>p<span class="token operator">&gt;</span></code>

直接使用 innerHTML

<code class=" language-javascript">node<span class="token punctuation">.</span>innerHTML <span class="token operator">=</span> <span class="token string">"&lt;h2 title="</span>header<span class="token string">"&gt;title&lt;/h2&gt;&lt;p&gt;content&lt;/p&gt;"</span></code>

在对比使用Dom API

<code class=" language-javascript"><span class="token keyword">var</span> header <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createElement<span class="token punctuation">(</span></span><span class="token string">'h2'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> content <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createElement<span class="token punctuation">(</span></span><span class="token string">'p'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
h2<span class="token punctuation">.</span><span class="token function">setAttribute<span class="token punctuation">(</span></span><span class="token string">'title'</span><span class="token punctuation">,</span> <span class="token string">'header'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
h2<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token string">'title'</span><span class="token punctuation">;</span>
p<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token string">'content'</span><span class="token punctuation">;</span>
node<span class="token punctuation">.</span><span class="token function">appendChild<span class="token punctuation">(</span></span>header<span class="token punctuation">)</span><span class="token punctuation">;</span>
node<span class="token punctuation">.</span><span class="token function">appendChild<span class="token punctuation">(</span></span>content<span class="token punctuation">)</span><span class="token punctuation">;</span></code>

innerHTML 毫无疑问赢得了这张比赛.

尽管部分框架例如mootools:Element 提供了高效的API来帮助你创建dom结构,innerHTML仍然会是大多数人的最佳选择

2 . 它很快,特别在old IE

随着浏览器的发展,这个测试可能越来越不符合实际,innerHTMLDom Level 1创建dom结构的差距正变得原来越小

3. 它完成进行了String -> Dom的转换

这个论点有点拗口,事实上后续要提到的两类模板技术都是因为这个特点而与其有了强依赖


然而我们又清楚的被告知:

The recommended way to modify the DOM is to use the DOM Level 1 API. ——Chapter 15 of “Javascript: The Definitive Guide_”

为什么?

innerHTML 有时候又是不听话的

1. 安全问题

innerHTML 具有安全隐患.,例如:

<code class=" language-javascript">document<span class="token punctuation">.</span>body<span class="token punctuation">.</span>innerHTML <span class="token operator">=</span> <span class="token string">"&lt;img src=x   onerror='alert(xss)'/&gt;"</span></code>

我知道像你这样优秀的程序员不会写出这样的代码,但当html片段不完全由你来控制时(比如从远程服务器中),这会成为一个可能引爆的炸弹。

2. 它很慢

等等,你刚才说了它很快! 是的,但是如果你仅仅为了替换一个属性而用innerHTML替换了所有的Dom节点,这显然不是一个明智的决定,因为你深知这是低效的。所以说:

Context is everything

所有离开背景谈的性能、功能、性功能都是伪科学

3. 它很笨

它会完全移除所有现有的Dom,并重新渲染一遍,包括事件和状态都以不复存在,这点利用innerHTML来进行render的框架(例如Backbone)的开发者应该深有体会,为了减少损失,不能不把View拆的越来越细,从而抱着看似“解耦完美”的架构体系进入了维护的深渊。

注: 其实react的最大贡献就是它差不多是提供了一个更smart的innerHTML解决方案。

4. 有可能会创建出意料之外的节点.

由于html的parser非常的“友好”, 以至于它接受并不规范的写法,从而创建出意料之外的结构,而开发者得不到错误提示。


好了,到现在为止,我们大概了解了innerHTML这个朝夕相处的小伙伴,接下来我们正式聊一聊模板技术,首先我们从最常见的“String-based templating”开始

String-based templating

基于字符串的模板引擎最大的功劳就是把你从大量的夹带逻辑的字符串拼接中解放出来了,由于它的完全基于字符串的特性,它拥有一些无可替代的优势。

It is essentially a way to address the need to populate an HTML view with data in a better way than having to write a big, ugly string concatenation expression. — cited from http://www.dehats.com/drupal/?q=node/107

示例

  1. mustache及其衍生: 弱逻辑
  2. Dust.js: 强逻辑 (推荐)
  3. doT.js: 超级快

基本原理

string-based

如上图所示,我们发现字符串模板强依赖于innerHTML(渲染), 因为它的输出物就是字符串。由于这篇文章的重点不在这里,我们不会再对它们如何使用作深究。

优点

  1. 快速的初始化时间: 很多angular的簇拥者在奚落String-based templating似乎遗漏了这一点。
  2. 同构性: 完全的dom-independent,即可作为用服务器端和浏览器端(客官先不要急着搬phantomjs哈).
  3. 更强大的语法支持:因为它们都是不是自建DSL就是基于JavaScript语法,Parser的灵活性与受限于HTML的Dom-based模板技术不可同日而语

缺点

  1. 安全隐患: 见innerHTML
  2. 性能问题:见 innerHTML.
  3. 不够聪明: 见innerHTML(呵呵),除此之外render之后数据即与view完全分离。

尽管在这几年的发展之下,由于异常激烈的竞争,基于字符串的前端模板技术变得越来越快,但是它们显然大部分都遗漏了一些问题

  1. 大侠们你们没有考虑进把输出字符串加载到Dom的时间,这才是真正瓶颈之一
  2. 不在相同功能前提下的对比有意义么?

Dom-based Template Engine

近几年,借着Angularjs的东风,Dom-based的模板技术开始大行其道,与此同时也出现了一些优秀的替代者,就我们国人而言,近的就有@尤小右Vuejs司徒大大avalonjs。看仓库就可以发现风格也是完全不同:1) 一个简洁优雅 2)一个奔放不羁

示例

  1. Angularjs: 都28000star了还需多说么
  2. Knockout: 在此领域内,对Web前端而言是鼻祖级的

大致流程

dom-based

Dom-based的模板技术事实上并没有完整的parse的过程(先抛开表达式不说),如果你需要从一段字符串创建出一个view,你必然通过innerHTML来获得初始Dom结构. 然后引擎会利用Dom API(attributes, getAttribute, firstChild… etc)层级的从这个原始Dom的属性中提取指令、事件等信息,继而完成数据与View的绑定,使其”活动化”。

所以Dom-based的模板技术更像是一个数据与dom之间的“链接”和*“改写”*过程。

注意,dom-based的模板技术不一定要使用innerHTML,比如所有模板都是写在入口页面中时, 但是此时parse过程仍然是浏览器所为。

优点

  1. 是活动的: 完成compile之后,data与View仍然保持联系,即你可以不依赖与手动操作Dom API来更新View
  2. 运行时高效的: 可以实现局部更新
  3. 指令等强大的附属物帮助我们用声明式的方式开发APP

缺点

  1. 部分请见innerHTML
  2. 没有独立的Parser,必须通过innerHTML(或首屏)获取初始节点,即它的语法是强依赖与HTML,这也导致它有潜在的安全问题
  3. 信息承载于属性中,这个其实是不必要和冗余的。 部分框架在读取属性后会通过诸如removeAttribute的方式移除它们,其实这个不一定必要,而且其实并无解决它们Dom强依赖的特性,比如如果你查看[angular的todomvc]的节点,你会发现它的输出是这样的:angular-todo
  4. FOUC(Flash of unstyled content):即内容闪动,这个无需多说了,只怪它初次进入dom的内容并不是最终想要的内容。

Living Template Engine

String-based 和 Dom-based的模板技术都或多或少的依赖与innerHTML, 它们的区别是一个是主要是为了Rendering 一个是为了 Parsing 提取信息

所以为什么不结合它们两者来完全移除对innerHTML的依赖呢?

事实上,值得庆幸的是,已经有几个现实例子在这么做了。

例子

  1. htmlbar: 运行在handlebar之后的二次编译
  2. ractivejs: 独立
  3. Regularjs 独立, 本文作者结精之一

基本原理

Living-Template

就如图中所示,parse和compile的过程分别类似于String-based 模板技术 和 Dom-based模板技术。

下面来完整讲述下这两个过程

1 . Parsing

首先我们使用一个内建DSL来解析模板字符串并输出AST。

例如,在regularjs中,下面这段简单的模板字符串

<code class=" language-javascript"><span class="token operator">&lt;</span>button <span class="token punctuation">{</span><span class="token punctuation">{</span>#<span class="token keyword">if</span> <span class="token operator">!</span>isLogin<span class="token punctuation">}</span><span class="token punctuation">}</span> on<span class="token operator">-</span>click<span class="token operator">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span>this<span class="token punctuation">.</span><span class="token function">login<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">}</span> <span class="token punctuation">{</span><span class="token punctuation">{</span><span class="token operator">/</span><span class="token keyword">if</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token operator">&gt;</span>
  <span class="token punctuation">{</span><span class="token punctuation">{</span>isLogin<span class="token operator">?</span> <span class="token string">'Login'</span><span class="token punctuation">:</span> <span class="token string">'Wellcome'</span><span class="token punctuation">}</span><span class="token punctuation">}</span>
<span class="token operator">&lt;</span><span class="token operator">/</span>button<span class="token operator">&gt;</span>'</code>

会被解析为以下这段数据结构

<code class=" language-javascript"><span class="token punctuation">[</span>
  <span class="token punctuation">{</span>
    <span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"element"</span><span class="token punctuation">,</span>
    <span class="token string">"tag"</span><span class="token punctuation">:</span> <span class="token string">"button"</span><span class="token punctuation">,</span>
    <span class="token string">"attrs"</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>
      <span class="token punctuation">{</span>
        <span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"if"</span><span class="token punctuation">,</span>
        <span class="token string">"test"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>
          <span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"expression"</span><span class="token punctuation">,</span>
          <span class="token string">"body"</span><span class="token punctuation">:</span> <span class="token string">"(!_d_['isLogin'])"</span><span class="token punctuation">,</span>
          <span class="token string">"constant"</span><span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
          <span class="token string">"setbody"</span><span class="token punctuation">:</span> <span class="token boolean">false</span>
        <span class="token punctuation">}</span><span class="token punctuation">,</span>
        <span class="token string">"consequent"</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>
          <span class="token punctuation">[</span>
            <span class="token punctuation">{</span>
              <span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"attribute"</span><span class="token punctuation">,</span>
              <span class="token string">"name"</span><span class="token punctuation">:</span> <span class="token string">"on-click"</span><span class="token punctuation">,</span>
              <span class="token string">"value"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>
                <span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"expression"</span><span class="token punctuation">,</span>
                <span class="token string">"body"</span><span class="token punctuation">:</span> <span class="token string">"_c_['login']()"</span><span class="token punctuation">,</span>
                <span class="token string">"constant"</span><span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
                <span class="token string">"setbody"</span><span class="token punctuation">:</span> <span class="token boolean">false</span>
              <span class="token punctuation">}</span>
            <span class="token punctuation">}</span>
          <span class="token punctuation">]</span>
        <span class="token punctuation">]</span><span class="token punctuation">,</span>
        <span class="token string">"alternate"</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token string">"children"</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>
      <span class="token punctuation">{</span>
        <span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"expression"</span><span class="token punctuation">,</span>
        <span class="token string">"body"</span><span class="token punctuation">:</span> <span class="token string">"_d_['isLogin']?'Login':'Wellcome'"</span><span class="token punctuation">,</span>
        <span class="token string">"constant"</span><span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
        <span class="token string">"setbody"</span><span class="token punctuation">:</span> <span class="token boolean">false</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">]</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">]</span>
</code>

这个过程有以下特点

  1. 灵活强大的语法,因为它与基于字符串的模板一般,DSL是自治的,完全不依赖与html,你可以想像下dom-based的模板的那些语法相关的指令,事实上它们甚至无法表达上述那段简单的模板的逻辑。
  2. Living模板技术需要同时处理dsl元素xml元素来实现最终视图层的活动性,即它们是dom-aware的,而在字符串模板中其实xml元素完全可以无需关心,它们被统一视为文本元素

2 Compiler

结合特定的数据模型(在regularjs中,是一个裸数据), 模板引擎层级游历AST并递归生成Dom节点(不会涉及到innerHTML). 与此同时,指令、事件和插值等binder也同时完成了绑定,使得最终产生的Dom是与Model相维系的,即是活动的.

事实上,Living template的compile过程相对与Dom-based的模板技术更加纯粹, 因为它完全的依照AST生成,而不是在原Dom上的改写。

以上面的模板代码的一个插值为例:{{isLogin? 'Login': 'Wellcome'}}。一旦regularjs的引擎遇到这段模板与代表的语法元素节点,会进入如下函数处理

<code class=" language-javascript"><span class="token comment" spellcheck="true">// some sourcecode from regularjs
</span>walkers<span class="token punctuation">.</span>expression <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span>ast<span class="token punctuation">)</span><span class="token punctuation">{</span>
  <span class="token keyword">var</span> node <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createTextNode<span class="token punctuation">(</span></span><span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  this<span class="token punctuation">.</span>$<span class="token function">watch<span class="token punctuation">(</span></span>ast<span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span>newval<span class="token punctuation">)</span><span class="token punctuation">{</span>
    dom<span class="token punctuation">.</span><span class="token function">text<span class="token punctuation">(</span></span>node<span class="token punctuation">,</span> <span class="token string">""</span> <span class="token operator">+</span> <span class="token punctuation">(</span>newval <span class="token operator">==</span> <span class="token keyword">null</span><span class="token operator">?</span> <span class="token string">""</span><span class="token punctuation">:</span> <span class="token function">String<span class="token punctuation">(</span></span>newval<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span>
  <span class="token keyword">return</span> node<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code>

正如我们所见, 归功于$watch函数,一旦表达式发生改变,文本节点也会随之改变,这一切其实与angularjs并无两样(事实上regularjs同样也是基于脏检查)

与Dom-based 模板技术利用Dom节点承载信息所不同的是,它的中间产物AST 承载了所有Compile过程中需要的信息(语句, 指令, 属性…等等). 这带来几个好处

  1. 轻量级, 在Dom中进行读写操作是低效的.
  2. 可重用的.
  3. 可序列化 , 你可以在本地或服务器端预处理这个过程。
  4. 安全, 因为安全不需要innerHTML帮我们生成初始Dom

如果你查看Living Template的输出,你会发现是这样的

regular-todo

只有需要的内容被输出了

_总结Living templating _

我们可以发现Living templating几乎同时拥有String-based和Dom-based模板技术的优点

利用一个如字符串模板的自定义DSL来描述结构来达到了语法上的灵活性,并在Parse后承载信息(AST)。而在Compile阶段,利用AST和Dom API来完成View的组装,在组装过程中,我们同样可以引入Dom-based模板技术的诸如Directive等优良的种子。

living template’s 近亲 —— React

React当然也可以称之为一种模板解决方案,它同样也巧妙规避了innerHTML,不过却使用的是截然不同的策略:react使用一种virtual dom 的技术,它也同样基于脏检查,不过与众不同的是,它的脏检查发生在view层面,即发生在virtual dom上,从而可以以较小的开销来实现局部更新。

Example

<code class=" language-javascript"><span class="token keyword">var</span> MyComponent <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">createClass<span class="token punctuation">(</span></span><span class="token punctuation">{</span>
 render<span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
   <span class="token keyword">if</span> <span class="token punctuation">(</span>this<span class="token punctuation">.</span>props<span class="token punctuation">.</span>first<span class="token punctuation">)</span> <span class="token punctuation">{</span>
     <span class="token keyword">return</span> <span class="token operator">&lt;</span>div className<span class="token operator">=</span><span class="token string">"first"</span><span class="token operator">&gt;</span><span class="token operator">&lt;</span>span<span class="token operator">&gt;</span>A Span<span class="token operator">&lt;</span><span class="token operator">/</span>span<span class="token operator">&gt;</span><span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span><span class="token punctuation">;</span>
   <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
     <span class="token keyword">return</span> <span class="token operator">&lt;</span>div className<span class="token operator">=</span><span class="token string">"second"</span><span class="token operator">&gt;</span><span class="token operator">&lt;</span>p<span class="token operator">&gt;</span>A Paragraph<span class="token operator">&lt;</span><span class="token operator">/</span>p<span class="token operator">&gt;</span><span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span><span class="token punctuation">;</span>
   <span class="token punctuation">}</span>
 <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code>

同样的逻辑使用regularjs描述

<code class=" language-javascript"><span class="token punctuation">{</span><span class="token punctuation">{</span>#<span class="token keyword">if</span> first<span class="token punctuation">}</span><span class="token punctuation">}</span>
  <span class="token operator">&lt;</span>div className<span class="token operator">=</span><span class="token string">"first"</span><span class="token operator">&gt;</span><span class="token operator">&lt;</span>span<span class="token operator">&gt;</span>A Span<span class="token operator">&lt;</span><span class="token operator">/</span>span<span class="token operator">&gt;</span><span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
<span class="token punctuation">{</span><span class="token punctuation">{</span>#<span class="token keyword">else</span><span class="token punctuation">}</span><span class="token punctuation">}</span>
  <span class="token operator">&lt;</span>div className<span class="token operator">=</span><span class="token string">"second"</span><span class="token operator">&gt;</span><span class="token operator">&lt;</span>p<span class="token operator">&gt;</span>A Paragraph<span class="token operator">&lt;</span><span class="token operator">/</span>p<span class="token operator">&gt;</span><span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span><span class="token punctuation">;</span>
<span class="token punctuation">{</span><span class="token punctuation">{</span><span class="token operator">/</span><span class="token keyword">if</span><span class="token punctuation">}</span><span class="token punctuation">}</span>
</code>

仁者见仁智者见智, 反正我倾向于使用模板来描述结构,而不是杂糅Virtual dom和js语句。你呢?

值得一提的是,由于React的特性,它两次render之间,内部节点的替换是无法预计的(参考这里),所以无法有效的保留信息,所以它也有大量的关于id的placeholder存在。你可以同样查看react-todomvc生成的节点

一个全面的对照表

Contrast /Solutions String-based templating Dom-based templating Living templating
例子 Mustache,Dustjs Angularjs, Vuejs Regularjs 、Ractivejs、htmlbars
语法灵活性 ♦♦♦ ♦♦
活动性 X ♦♦♦ ♦♦♦
性能 初始: ♦♦♦
更新: ♦
初始: ♦
更新: ♦♦♦
初始: ♦
更新: ♦♦♦
安全性 ♦♦ ♦♦♦♦♦
Dom 无关 ♦♦♦♦♦ X ♦♦
SVG support(*1) X ♦♦ ♦♦♦

1. 任何一类无法被另一类全面替代 2. 它们并不是无法同时存在的,比如你可以使用字符串模板来生成Dom-based的模板需要的模板字符串。

参考资料

  1. Template Engines by @Sendhil
  2. string-templating-considered-harmful
38aec9ba75724b89e741ed1e81611d8d

关于模板引擎的工作方式和性能?


1、工作原理 —— 拼字符串成为代码。
那最简单的 JSP 举例(因为这货第一次运行会被编译,比较好说事儿)
如图。
<%%>内的是模板内容
<div></div> 是页面内容
当JSP运行被,编译为Servlet Class后,其实做了黄字标注的操作
<%%> 被去掉,内部即为正常的JAVA代码
<div></div> 被加引号成为字符串(回车换行也被变为转义字符)
输出字符串内容。

2、流程 —— 访问时,转换模板代码为目标源码执行,或者第一次运行时转为目标源码(缓存),后续直接调用源码运行。

所以,不管怎么样模板转换与目标代码执行肯定要消耗资源(只是消耗多点少点问题),肯定会比不用模板直接输出 HTML 来的有压力。

3、依赖性
从1原理可知,哪方实现的模板引擎,就依赖哪方。
在server端实现模板引擎时,依赖server端。
在客户端实现依赖客户端。
所以,只要在客户端实现一种模板解析方式(引擎),用来读取模板内容,分析并转为客户端可执行的程序源码,并运行,就可以脱离服务端,在浏览器端渲染页面,而不依赖服务端。

4、浏览器端模板 —— 依据原理,JS 可作:

剩下的无非是怎么用正则或者其他字符串处理手段将模板字符串拼接为js源码字符串。
并用 eval 或者 new Funciton 等手段,执行拼接后源码。

此代码可实时执行,或者如同jsp一般,第一次运行时执行模板->源码拼接,然后将拼接后源码缓存,之后执行模板不再进行重复拼接工作。

当然,此类优化手段很多,可以自己慢慢玩。

5、前端模板是否好
这是仁者见仁智者见智的事情,就不细说了,需要根据具体项目情况综合考虑。
无非就是全前置,前后各半和全后置模板这几种。
一般来说,前端模板会分担一部分服务器压力,毕竟把一(小)部分计算分摊到了客户端上。
但是可能会给(半前置)模板统一管理和更新上带来点问题。
也会给客户端增加(些许)压力。
或者会造成SEO、不支持JS浏览器如何显示等体验性问题需要解决。

6、前端模板类型
这里只根据你说的服务端模板,给出对应前端模板实现思路。
此类模板开源的有的是,GitHub 搜去吧。

Read more

高性能JavaScript模板引擎原理解析


随着 web 发展,前端应用变得越来越复杂,基于后端的 javascript(Node.js) 也开始崭露头角,此时 javascript 被寄予了更大的期望,与此同时 javascript MVC 思想也开始流行起来。javascript 模板引擎作为数据与界面分离工作中最重要一环,越来越受开发者关注,近一年来在开源社区中更是百花齐放,在 Twitter、淘宝网、新浪微博、腾讯QQ空间、腾讯微博等大型网站中均能看到它们的身影。

本文将用最简单的示例代码描述现有的 javascript 模板引擎的原理,包括新一代 javascript 模板引擎 artTemplate 的特性实现原理,欢迎共同探讨。

Read more

js模板引擎介绍搜集


js模板引擎越来越多的得到应用,如今已经出现了几十种js模板引擎,国内各大互联网公司也都开发了自己的js模板引擎(淘宝的kissy template,腾讯的artTemplate,百度的baiduTemplate等),如何从这么多纷繁的模板引擎中选择一款适合自己的呢,笔者最近对主流的js模板引擎(mustache,doT,juicer,artTemplate,baiduTemplate,Handlebars,Underscore)做了一番调研,分享出来希望对大家有用。
从这几个指标来比较js模板引擎:
1 文件大小 – 影响网络传输时间
2 执行速度(性能) – 影响响应速度,涉及模板解析和渲染
3 语法简明/易用/灵活/自定义操作符 – 影响开发效率和维护难度
4 错误处理/调试 – 影响开发效率和维护难度
5 安全(XSS) – 是否防止XSS

Read more