收集一些观点


严格的模板引擎的定义,输入模板字符串 + 数据,获得渲染过的字符串。实现上,从正则替换到拼 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)
  • 脏检查:scope digest O(watcher count) + 必要 DOM 更新 O(DOM change)
  • 依赖收集:从新收集依赖 O(data change) + 必要 DOM 更新 O(DOM 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 > 脏检查 >= 依赖收集
  • 小量数据更新:依赖收集 >> Virtual DOM + 优化 > 脏检查(没法优化) > Virtual DOM 无优化
  • 大量数据更新:脏检查 + 优化 >= 依赖收集 + 优化 > Virtual DOM(没法/无需优化)>> MVVM 无优化

不要天真地觉得 Virtual DOM 就是快,diff 不是免费的,batching 么 MVVM 也能作,并且最终 patch 的时候还不是要用原生 API。在我看来 Virtual DOM 真正的价值历来都不是性能,而是它 1) 为函数式的 UI 编程方式打开了大门;2) 能够渲染到 DOM 之外的 backend,好比 ReactNative。

5. 总结

以上这些比较,更多的是对于框架开发研究者提供一些参考。主流的框架 + 合理的优化,足以应对绝大部分应用的性能需求。若是是对性能有极致需求的特殊状况,其实应该牺牲一些可维护性采起手动优化:好比 Atom 编辑器在文件渲染的实现上放弃了 React 而采用了本身实现的 tile-based rendering;又好比在移动端须要 DOM-pooling 的虚拟滚动,不须要考虑顺序变化,能够绕过框架的内置实现本身搞一个。


从风格上来讲 new 不是必要的,可是在主流的 JS 引擎里 new 出来的对象更容易优化,由于 constructor + prototype 能够映射到 hidden class。实际应用中 new 出来的对象相比 Object.create() 可能会有数倍的性能差别。


任何状况下你问『咱们应不该该用框架 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 的理由。


假设咱们有一个数组,每一个元素是一我的。你面前站了一排人。
foreach 就是你按顺序一个一个跟他们作点什么,具体作什么,随便:
people.forEach(function (dude) {
  dude.pickUpSoap();
});
复制代码
map 就是你手里拿一个盒子(一个新的数组),一个一个叫他们把钱包扔进去。结束的时候你得到了一个新的数组,里面是你们的钱包,钱包的顺序和人的顺序一一对应。
var wallets = people.map(function (dude) {
  return dude.wallet;
});
复制代码
reduce 就是你拿着钱包,一个一个数过去看里面有多少钱啊?每检查一个,你就和前面的总和加一块儿来。这样结束的时候你就知道你们总共有多少钱了。
var totalMoney = wallets.reduce(function (countedMoney, wallet) {
  return countedMoney + wallet.money;
}, 0);
复制代码

补充一个 filter 的:
你一个个钱包数过去的时候,里面钱少于 100 块的不要(留在原来的盒子里),多于 100 块的丢到一个新的盒子里。这样结束的时候你又有了一个新的数组,里面是全部钱多于 100 块的钱包:
var fatWallets = wallets.filter(function (wallet) {
  return wallet.money > 100;
});
复制代码

最后要说明一点这个类比和实际代码的一个区别,那就是 map 和 filter 都是 immutable methods,也就是说它们只会返回一个新数组,而不会改变原来的那个数。



要说简单,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 对其也已经有实验性的支持了。


nodejs写的小工具,如何使用命令行直接执行?而不是先调用js文件,具体状况往下看

  • 在 main.js 开头加上
    #!/usr/bin/env node
    复制代码

  • 在 package.json 里面添加
    "bin": {
      "mytool": "main.js"
    }
    复制代码

  • 若是你只是想本地使用,运行 npm link(至关于将一个本地包 install -g)
  • mytool 能够直接做为命令使用了。


  • 什么是DOM

    牢记:站高一个维度去理解问题 !


    为了理解DOM,咱们至少须要站在浏览器的角度来思考。


    DOM概念自己很简单,请先彻底跟着个人思路来:

    1. 普通文档(*.txt)和HTML/XML文档(*.html/*.xml)的区别仅仅是由于后者是有组织的结构化文件;
    2. 浏览器将结构化的文档以树的数据结构读入浏览器内存,并将每一个树的子节点定义为一个NODE(想象这颗树,从根节点到叶子节点都被建模为一个NODE对象);
    3. 这每一个节点(NODE)都有本身的属性(名称、类型、内容...);
    4. NODE之间有层级关系(parents、child、sibling...);
    5. 以上已经完成文档的建模工做(将文档内容以树形结构写入内存),此时再编写一些方法来操做节点(属性和位置信息),即为NODE API。

    抽象一下:

    • DOM是一种将HTML/XML文档组织成对象模型建模过程
    • DOM建模重点在于如何解析HTML/XML文档和开放符合DOM接口规范的节点操做API接口

    再抽象一下:

    • 解析文档,建模成对象模型,开放API接口。

    最后:

    • DOM:Document Object Model 文档对象模型


    再回顾下整个过程,每一个步骤均可以问本身几个问题,好比:DOM究竟是建模过程,仍是最后建的那个模型,仍是指操做节点的API接口呢,仍是...?


    以上是站在浏览器的角度思考DOM,你还能够站在浏览器设计人员、网页编码人员等角度考虑:

    • DOM跟JavaScript什么关系?
      • DOM很显然诞生在浏览器,一开始是用JS实现的;
      • 但随着DOM自己的发展,已经造成规范,你能够用任何一种语言好比Python来解析文档,生成对像树,只要知足DOM标准,包括开放标准的操做接口,那你实现的就是一个DOM。
    • DOM开放的接口如何操做?
      • JS原生接口使用。
      • JQuery高纬度封装如何使用。
    • ...

    做者:futeng连接:https://www.zhihu.com/question/34219998/answer/268568438

    相关文章
    相关标签/搜索