严格的模板引擎的定义,输入模板字符串 + 数据,获得渲染过的字符串。实现上,从正则替换到拼 function 字符串到正经的 AST 解析各类各样,但从定义上来讲都是差很少的。字符串渲染的性能其实也就在后端比较有意义,毕竟每一次渲染都是在消耗服务器资源,但在前端,用户只有一个,几十毫秒的渲染时间跟请求延迟比起来根本不算瓶颈。却是前端的后续更新是字符串模板引擎的软肋,由于用渲染出来的字符串整个替换 innerHTML 是一个效率很低的更新方式。因此这样的模板引擎现在在纯前端情境下已经再也不是好的选择,意义更可能是在于方便先后端共用模板。html
相比之下 Angular 是 DOM-based templating,直接解析 live DOM 来提取绑定,若是是字符串模板则是先转化成 live DOM 再解析。数据更新的时候直接经过绑定作局部更新。其余 MVVM 如 Knockout, Vue, Avalon 同理。缺点是没有现成的服务端渲染,要作服务端渲染基本等于重写一个字符串模板引擎。不过其实也不难,由于 DOM-based 的模板都是合法的 HTML,直接用现成的 HTML parser 预处理一下,后面的工做就相对简单了。前端
框架里面也有在前端解析字符串模板到静态 AST 再生成 live DOM 作局部更新的,好比 Ractive 和 Regular。这一类的实现由于解析到 AST 的这步已经在框架内部完成了,因此作服务端渲染几乎是现成的,另外也能够在构建时进行预编译。vue
而后说说 React,JSX 根本就不是模板,它就是带语法糖的手写 AST,而且把语法糖的处理放到了构建阶段。由于运行时不须要解析,因此 virtual DOM 能够每次渲染都从新生成整个 AST,在客户端用 diff + patch,在服务端则直接 serialize 成字符串。全部其余 virtual DOM 类方案同理。像 virtual-dom,mithril 之类的连语法糖都不带。html5
最后说下个人见解:若是是静态内容为主,那就直接服务端渲染好了,首屏加载速度快。若是是动态的应用界面,那就不该该用拼模板的思路去作,而是用作应用的架构(MV*,组件树)思路去作。3. MVVM vs. Virtual DOMnode
相比起 React,其余 MVVM 系框架好比 Angular, Knockout 以及 Vue、Avalon 采用的都是数据绑定:经过 Directive/Binding 对象,观察数据变化并保留对实际 DOM 元素的引用,当有数据变化时进行对应的操做。MVVM 的变化检查是数据层面的,而 React 的检查是 DOM 结构层面的。MVVM 的性能也根据变更检测的实现原理有所不一样:Angular 的脏检查使得任何变更都有固定的 O(watcher count) 的代价;Knockout/Vue/Avalon 都采用了依赖收集,在 js 和 DOM 层面都是 O(change):能够看到,Angular 最不效率的地方在于任何小变更都有的和 watcher 数量相关的性能代价。可是!当全部数据都变了的时候,Angular 其实并不吃亏。依赖收集在初始化和数据变化的时候都须要从新收集依赖,这个代价在小量更新的时候几乎能够忽略,但在数据量庞大的时候也会产生必定的消耗。git
MVVM 渲染列表的时候,因为每一行都有本身的数据做用域,因此一般都是每一行有一个对应的 ViewModel 实例,或者是一个稍微轻量一些的利用原型继承的 "scope" 对象,但也有必定的代价。因此,MVVM 列表渲染的初始化几乎必定比 React 慢,由于建立 ViewModel / scope 实例比起 Virtual DOM 来讲要昂贵不少。这里全部 MVVM 实现的一个共同问题就是在列表渲染的数据源变更时,尤为是当数据是全新的对象时,如何有效地复用已经建立的 ViewModel 实例和 DOM 元素。假如没有任何复用方面的优化,因为数据是 “全新” 的,MVVM 实际上须要销毁以前的全部实例,从新建立全部实例,最后再进行一次渲染!这就是为何题目里连接的 angular/knockout 实现都相对比较慢。相比之下,React 的变更检查因为是 DOM 结构层面的,即便是全新的数据,只要最后渲染结果没变,那么就不须要作无用功。es6
Angular 和 Vue 都提供了列表重绘的优化机制,也就是 “提示” 框架如何有效地复用实例和 DOM 元素。好比数据库里的同一个对象,在两次前端 API 调用里面会成为不一样的对象,可是它们依然有同样的 uid。这时候你就能够提示 track by uid 来让 Angular 知道,这两个对象实际上是同一份数据。那么原来这份数据对应的实例和 DOM 元素均可以复用,只须要更新变更了的部分。或者,你也能够直接 track by $index 来进行 “原地复用”:直接根据在数组里的位置进行复用。在题目给出的例子里,若是 angular 实现加上 track by $index 的话,后续重绘是不会比 React 慢多少的。甚至在 dbmonster 测试中,Angular 和 Vue 用了 track by $index 之后都比 React 快: dbmon (注意 Angular 默认版本无优化,优化过的在下面)github
顺道说一句,React 渲染列表的时候也须要提供 key 这个特殊 prop,本质上和 track-by 是一回事。shell
4. 性能比较也要看场合数据库
在比较性能的时候,要分清楚初始渲染、小量数据更新、大量数据更新这些不一样的场合。Virtual DOM、脏检查 MVVM、数据收集 MVVM 在不一样场合各有不一样的表现和不一样的优化需求。Virtual DOM 为了提高小量数据更新时的性能,也须要针对性的优化,好比 shouldComponentUpdate 或是 immutable data。
不要天真地觉得 Virtual DOM 就是快,diff 不是免费的,batching 么 MVVM 也能作,并且最终 patch 的时候还不是要用原生 API。在我看来 Virtual DOM 真正的价值历来都不是性能,而是它 1) 为函数式的 UI 编程方式打开了大门;2) 能够渲染到 DOM 之外的 backend,好比 ReactNative。
5. 总结
以上这些比较,更多的是对于框架开发研究者提供一些参考。主流的框架 + 合理的优化,足以应对绝大部分应用的性能需求。若是是对性能有极致需求的特殊状况,其实应该牺牲一些可维护性采起手动优化:好比 Atom 编辑器在文件渲染的实现上放弃了 React 而采用了本身实现的 tile-based rendering;又好比在移动端须要 DOM-pooling 的虚拟滚动,不须要考虑顺序变化,能够绕过框架的内置实现本身搞一个。任何状况下你问『咱们应不该该用框架 X 换掉框架 Y』,这都不是单纯的比较 X 和 Y 的问题,而得先问如下问题:
1. 现有的项目已经开发了多久?代码量多大? 2. 现有的项目是否已经投入生产环境? 3. 现有的项目是否遇到了框架相关的问题,好比开发效率、可维护性、性能?换框架是否能解决这些问题?
(1) 事关替换的成本,(2) 事关替换的风险,(3) 事关替换的收益。把这些具体信息放在台面上比较,才有可能得出一个相对靠谱的结论。
--- (1) 跟 (2) 要具体状况具体分析,因此就不谈了。至于 (3),如下是 Vue 有而 ko 没有的:
更好的性能,CLI,Webpack 深度整合,热更新,模板预编译,中文文档,官方路由方案,官方大规模状态管理方案,服务端渲染,render function / JSX 支持,Chrome 开发者插件,更多的社区组件和教程,尤为是中文内容。
这里没有什么说 ko 很差的意思。做为前端 mvvm 的鼻祖,ko 对 Vue 的设计显然有不少启发,可是今天的 Vue 在各方面都实实在在地比 ko 强。若是上新项目,我想不出什么继续用 ko 的理由。
people.forEach(function (dude) {
dude.pickUpSoap();
});
复制代码
var wallets = people.map(function (dude) {
return dude.wallet;
});
复制代码
var totalMoney = wallets.reduce(function (countedMoney, wallet) {
return countedMoney + wallet.money;
}, 0);
复制代码
var fatWallets = wallets.filter(function (wallet) {
return wallet.money > 100;
});
复制代码
要说简单,async 是最简单的,只是在 callback 上加了一些语法糖而已。在不是很复杂的用例下够用了,前提是你已经习惯了 callback 风格的写法。
then.js 上手也是比较简单的,由于也是基于 callback 和 continuation passing,并不引入额外的概念,比起 async,链式 API 更流畅,我的挺喜欢的。我挺久之前写过一个在 Node 里面跑 shell 命令的小工具,思路差很少:www.npmjs.org/package/she…
Callback-based 方案的最大问题在于异常处理,每一个 callback 都得额外接受一个异常参数,发生异常就得一个一个日后传,异常发生后的定位很麻烦。
ES6 Promise, Q, Bluebird 核心都是 Promise,缺点嘛就是必须引入这个新概念而且要用就得全部的地方都用 Promise。对于 Node 的原生 API,须要进行二次封装。Q 和 Bluebird 都是在实现 Promise A+ 标准的基础上提供了一些封装和帮助方法,好比 Promise.map 来进行并行操做等等。Promise 的一个问题就是性能,而 Bluebird 号称速度是全部 Promise 库里最快的。ES6 Promise 则是把 Promise 的包括进 js 标准库里,这样你就不须要依赖第三方实现了。
关于 Promise 可以如何改进异步流程,建议阅读:www.html5rocks.com/en/tutorial…
co 是 TJ 大神基于 ES6 generator 的异步解决方案。要理解 co 你得先理解 ES6 generator,这里就不赘述了。co 最大的好处就是能让你把异步的代码流程用同步的方式写出来,而且能够用 try/catch:
co(function *(){
try {
var res = yield get('http://badhost.invalid');
console.log(res);
} catch(e) {
console.log(e.code) // ENOTFOUND
}
})()
复制代码
但用 co 的一个代价是 yield 后面的函数必须返回一个 Thunk 或者一个 Promise,对于现有的 API 也得进行必定程度的二次封装。另外,因为 ES6 generator 的支持状况,并非因此地方都能用。想用的话有两个选择:
1. 用支持 ES6 generator 的引擎。好比 Node 0.11+ 开启 --harmony flag,或者直接上 iojs;
2. 用预编译器。好比 Babel (babeljs.io/) , Traceur (github.com/google/trac…) 或是 Regenerator (github.com/facebook/re…) 把带有 generator 的 ES6 代码编译成 ES5 代码。
(延伸阅读:基于 ES6 generator 还能够模拟 go 风格的、基于 channel 的异步协做:Taming the Asynchronous Beast with CSP in JavaScript)
可是 generator 的本意毕竟是为了能够在循环过程当中 yield 进程的控制权,用 yield 来表示 “等待异步返回的值” 始终不太直观。所以 ES7 中可能会包含相似 C# 的 async/await :
async function showStuff () {
var data = await loadData() // loadData 返回一个 Promise
console.log(data) // data 已经加载完毕
}
async function () {
await showStuff() // async 函数默认返回一个 Promise, 因此能够 await 另外一个 async 函数
// 这里 showStuff 已经执行完毕
}
复制代码
能够看到,和用 co 写出来的代码很像,但语意上更清晰。由于本质上 ES7 async/await 就是基于 Promise + generator 的一套语法糖。深刻阅读:ES7 async functions
想要今天就用 ES7 async/await 也是能够的!Babel 的话能够用配套的 asyncToGenerator transform: babeljs.io/docs/usage/…
Traceur 和 Regenerator 对其也已经有实验性的支持了。
#!/usr/bin/env node
复制代码
"bin": {
"mytool": "main.js"
}
复制代码
牢记:站高一个维度去理解问题 !
为了理解DOM,咱们至少须要站在浏览器的角度来思考。
DOM概念自己很简单,请先彻底跟着个人思路来:
抽象一下:
再抽象一下:
最后:
再回顾下整个过程,每一个步骤均可以问本身几个问题,好比:DOM究竟是建模过程,仍是最后建的那个模型,仍是指操做节点的API接口呢,仍是...?
以上是站在浏览器的角度思考DOM,你还能够站在浏览器设计人员、网页编码人员等角度考虑:
做者:futeng连接:https://www.zhihu.com/question/34219998/answer/268568438