iTunes api的使用说明

最近公司需要跟踪App Store里面应用的舆情,需要获取应用的评论,等其他一些信息。在网上查了下iTunes的接口很少。大体可以分成三种类型:

  • 搜索接口(官方提供)
  • 内容聚合接口(官方提供)
  • 通过抓包获取的iTunes页面内的接口(非官方)

通过这三种接口,可以大体获取App的一些信息。然后通过定时采集,进行监控和分析。

搜索接口

主要的调用文档:点击这里

如:

可以通过关键字获取iTunes里面所有的内容

1
https://itunes.apple.com/search?term=jack+johnson

具体的使用还有很多,就不在这个文档里赘述了。

内容聚合接口

官方只是提供一个RSS的链接生成器:https://rss.itunes.apple.com/us/?urlDesc=%2Fgenerator

  • 畅销 App 排名
  • 畅销 iPad App 排名
  • 付费 iPad App 排名
  • 免费 App 排名
  • 免费 iPad App 排名
  • 收费 App 排名
  • 新的付费应用程序
  • 新的免费应用程序

如果后缀xml则返回xml的数据,json则返回json数据

获取的数据还是比较全局,但是只能获取前100个数据。

1
2
3
https://itunes.apple.com/cn/rss/topgrossingipadapplications/limit=10/genre=6021/xml
https://itunes.apple.com/cn/rss/topgrossingipadapplications/limit=10/genre=6021/json

但是除了RSS生成器里面生成的链接以外还有其他RSS订阅:

评论接口:
通过这个接口可以获取前500个评论

1
https://itunes.apple.com/cn/rss/customerreviews/page=1/id=490655927/sortby=mostrecent/json

iTunes应用的内部接口

以上是官方提供的接口,如果无法满足获取信息的需求,那只能Hack iTunes应用的接口了,可以通过抓包工具来获取。

以Charles为例子:

因为iTunes的接口为https的,所以需要配置https的Charles的证书

具体的操作可以参考:http://www.charlesproxy.com/documentation/proxying/ssl-proxying/

这次主要是抓取了评论接口:

1
https://itunes.apple.com/WebObjects/MZStore.woa/wa/userReviewsRow?id=490655927&displayable-kind=11&startIndex=0&endIndex=100&sort=1&appVersion=current

通过这个方式可以获取应用的所有评论,当然也可以获取其他的信息。

在使用这个接口的时候:httpt头必须要带上cookies信息和use-agentx信息。

否则会展示一个跳转iTunes应用的页面。

调试node.js应用的内存泄漏[译]

原文:http://www.toptal.com/nodejs/debugging-memory-leaks-node-js-applications

有一次,我开着一辆内部是V8双涡轮增压发动机的奥迪,其性能令人难以置信。凌晨3点,我行驶在芝加哥附近没有人的IL-80高速公路,时速达到140MPH。从那时起,“V8”这个术语成为我对高性能的认识。

Node.js是一个建立在Chrome的JavaScript引擎V8之上的易于快速构建可伸缩的网络应用的平台。

尽管奥迪的V8非常强大,但仍然限于用你的油箱的容量大小。这同样适用于谷歌的V8引擎 - 在node.js背后的JavaScript引擎。有很多因素使Node.js的性能是难以置信的,适用于许多应用案例,但是你总是受限于内存堆的大小。当你的Node.js应用程序需要处理更多的请求时,你有两个选择:就是规模垂直或水平扩展。水平扩展意味着你必须运行多个并发的应用程序实例。如果做得对,你最终能够服务更多的请求。垂直扩展意味着你要为你的应用实例提高应用程序的内存使用情况和可用的性能或增加资源。

http://assets.toptal.io/uploads/blog/image/91676/toptal-blog-image-1442927311924-3c41bb7d7b1a8306609bcbe5570ccc63.jpg

最近有为我的Toptal(一个开放外包的平台)客户修复一个运行在Node.js上应用程序的内存泄漏问题。该应用程序是一个能够在每一分钟处理成千上万请求的API服务器。最初的应用几乎占据了600MB的RAM,所以我们决定采取热API端来重新实现它们。当你需要服务于许多请求开销变得非常昂贵。

对于新的API,我们选择的restify以及本地的MongoDB的驱动程序和Kue后台服务。听起来像是一个非常轻量级的栈,对不对?其实不然。在高峰负荷新的应用程序实例可以消耗高达270MB的RAM。因此起两个每1X的Heroku Dyno应用实的想法破灭了。

Node.js 内存泄露调试军火库

Memwatch

如果你搜索“如何在node里发现泄露”这个第一个你或许会搜到的工具就是memwatch。原始的包很久以前就被遗弃,不再保留。但是你可以很容易地找到在GitHub上的fork的库的更新版本。该模块是有用的,如果它看到堆增长超过5个连续的垃圾收集,它可以触发泄漏事件。

Heapdump

伟大的工具,它允许开发人员使用Chrome开发人员工具对Node.js采取堆快照后对其进行检查。

Node-inspector

即使是被更为有用的heapdump替代,但它任然可以让你连接到运行的应用程序,采取堆转储,甚至调试,并重新编译在程序运行中。

针对“node-inspector” 的说明

不幸的是,将不能连接到被在Heroku运行生产的应用中,因为不允许信号被发送到运行的进程。然而,Heroku的是不是唯一的托管平台。

为了在实战中体验node-inspector,我们将使用的RESTify编写一个简单的Node.一点点内存泄漏的根源放在js应用程序中。这里所有的实验都是用Node.js v0.12.7和已编译的V8 v3.28.71.19。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var restify = require('restify');
var server = restify.createServer();
var tasks = [];
server.pre(function(req, res, next) {
tasks.push(function() {
return req.headers;
});
// Synchronously get user from session, maybe jwt token
req.user = {
id: 1,
username: 'Leaky Master',
};
return next();
});
server.get('/', function(req, res, next) {
res.send('Hi ' + req.user.username);
return next();
});
server.listen(3000, function() {
console.log('%s listening at %s', server.name, server.url);
});

这个应用有个很简单且明显的内存泄漏。这个tasks的数组在应用的生命周期内引发变慢,并最终导致崩溃。这个问题是,不仅仅是闭包泄漏,而是波及了整个请求对象。

在V8里使用了stop-the-world这种GC策略,因此,这意味着越多的对象在内存中驻留,回收的时间就越长。在下面的日志你可以清楚地看到,在应用程序的生命的开始,平均回收内存是20毫秒,但几十万的请求后,则需要大约230ms。如果这时访问我们的应用程序将不得不等待230ms之久,因为正在GC。你也可以看到每隔几秒钟调用GC,这意味着每隔几秒钟,用户访问我们的应用程序会遇到问题。随着延迟时间增长,直到程序崩溃。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[28093] 7644 ms: Mark-sweep 10.9 (48.5) -> 10.9 (48.5) MB, 25.0 ms [HeapObjectsMap::UpdateHeapObjectsMap] [GC in old space requested].
[28093] 7717 ms: Mark-sweep 10.9 (48.5) -> 10.9 (48.5) MB, 18.0 ms [HeapObjectsMap::UpdateHeapObjectsMap] [GC in old space requested].
[28093] 7866 ms: Mark-sweep 11.0 (48.5) -> 10.9 (48.5) MB, 23.2 ms [HeapObjectsMap::UpdateHeapObjectsMap] [GC in old space requested].
[28093] 8001 ms: Mark-sweep 11.0 (48.5) -> 10.9 (48.5) MB, 18.4 ms [HeapObjectsMap::UpdateHeapObjectsMap] [GC in old space requested].
...
[28093] 633891 ms: Mark-sweep 235.7 (290.5) -> 235.7 (290.5) MB, 357.3 ms [HeapObjectsMap::UpdateHeapObjectsMap] [GC in old space requested].
[28093] 635672 ms: Mark-sweep 235.7 (290.5) -> 235.7 (290.5) MB, 331.5 ms [HeapObjectsMap::UpdateHeapObjectsMap] [GC in old space requested].
[28093] 637508 ms: Mark-sweep 235.7 (290.5) -> 235.7 (290.5) MB, 357.2 ms [HeapObjectsMap::UpdateHeapObjectsMap] [GC in old space requested].

当在启动一个Node.js的应用时加上–trace_gc这个标示,就可以逐行打印这些日志。

1
node --trace_gc app.js

假设我们已经在这个Node.js的应用设置标示。在连接node-inspector到应用程序之前,我们需要把SIGUSR1信号发送到正在运行的进程。如果在群集中运行Node.js,请确保您连接到其中一个从属进程。

1
kill -SIGUSR1 $pid # Replace $pid with the actual process ID

通过这样做,我们正在让Node.js的应用(准确地说是V8)进入调试模式。在这种模式下,应用自动将打开的端口5858V8调试协议

我们下一步运行node-inspector连接到运行应用的调试接口,并打开8080端口的web接口

1
2
3
$ node-inspector
Node Inspector v0.12.2
Visit http://127.0.0.1:8080/?ws=127.0.0.1:8080&port=5858 to start debugging.

如果应用程序在生产中运行,并且有一个防火墙,我们可以通过隧道远程端口8080到本地主机:

1
ssh -L 8080:localhost:8080 admin@example.com

现在,你可以打开你的Chrome网络浏览器即可获得完全的访问连接到远程生产应用程序的Chrome开发工具。不幸的是,Chrome开发人员工具将不能在其他浏览器。

让我们发现泄漏

在V8内存泄漏是不是真正的内存泄漏就像我们从C / C++应用程序的理解的那样。在JavaScript变量为空值时并不消失,他们只是“被遗忘”。我们的目标是要找到这些被遗忘的变量,并使这些内存释放。

在Chrome开发工具里我们有多个profiler。我们在记录堆分配而运行,并随着时间的推移需要多堆快照特别感兴趣。这给了我们一个明确的窥视到哪些对象正在泄漏。

开始录制堆分配,让我们模拟使用Apache基准在我们的主页上50个并发用户。

http://assets.toptal.io/uploads/blog/image/91677/toptal-blog-image-1442927477717.3-7c74dfceddd0955f27de9129eedf4261.jpg

1
ab -c 50 -n 1000000 -k http://example.com/

在建立新的快照之前,V8将执行标记 - 清除垃圾收集,所以我们肯定知道有没有旧垃圾中的快照。

修复运行中的泄漏

在收集堆分配快照3分钟后,我们得到类似下面的结果

http://assets.toptal.io/uploads/blog/image/91678/toptal-blog-image-1442927547865.5-d0c4450569bc2a285b3007eca57dfa25.jpg

我们可以清楚地看到,在堆里面有一些巨大的数组,很多IncomingMessage,ReadableState,ServerResponse和域对象。让我们分析下泄漏源。

在从20秒到40秒选择堆差异的图表中,我们只看到它是20秒后开始查看对象。这样,您就可以排除出所有正常的数据。

保持关注每种类型的对象在系统中的数目,我们扩大过滤从20多岁到1分钟。我们可以看到数组已经相当大了,并持续增长。在“(array)”,我们可以看到有很多的对象“(object properties)”具有同等的距离。这些对象是我们的内存泄漏的源头。

此外,我们也可以看到,“(closure)”的对象快速增长。

这可能是很方便的看这些字符串。根据字符串列表中也有很多的“Hi Leaky Master”的短语。这些可能给我们一些线索了。

在我们的例子中,我们知道,字符串“Hi Leaky Master”只能根据“GET/”路由进行组装。

果你打开从属的路径,你会看到这个字符串是通过req莫名其妙地引用,那么就创建的上下文的闭包把这一切都加入到巨型数组里。

http://assets.toptal.io/uploads/blog/image/91679/toptal-blog-image-1442927578118.2-af56af07dcf63ead26df3f9b9cd1aa78.jpg

所以在这一点上,我们知道有一些闭包的巨大数组。让我们通过这些闭包真正实时的得到源标签里面的名字。

http://assets.toptal.io/uploads/blog/image/91680/toptal-blog-image-1442927612747.1-102c5cf7bb68bcfc12181ab69333a520.jpg

在我们编辑完代码之后,在运行期内我们能按CTRL+S来保存和重新编译代码!

现在我们再来另外录一个堆分配的快照来看下闭包引起的内存保留。

很明显,那些SomeKindOfClojure()就是我们的问题所在。SomeKindOfClojure()闭包正在添加到一些命名任务到全局空间的数组。

很容易看出,这个数组仅仅是没有用处的。我们可以把它注释掉。但是,我们如何释放已被占用的内存?很容易,我们只分配一个空数组到任务中,然后下一个请求会被覆盖,再则内存将在下一个GC事件之后被释放。

http://assets.toptal.io/uploads/blog/image/91681/toptal-blog-image-1442927629279.4-24b223edd5e9b530625bd595fc50c4d1.jpg

多比自由啦!(出处《哈利波特》)

V8的垃圾回收的生命周期

http://assets.toptal.io/uploads/blog/image/91682/toptal-blog-image-1442928276284-e3da8ab85a898674f8f39088768b617a.jpg

那么,V8 JS本身并没有内存泄漏,只有被遗忘的变量。

V8堆被分成几个不同的空间:

新空间:这个空间相对较小,并且具有1MB-8MB的大小。多数的对象都在这里分配。

  • 老的指针空间:有可能有指向其他对象的对象。如果对象存活足够长的新空间也提升为老的指针空间。
  • 老的数据空间:只包含原始数据如字符串,封装的数据和未封装的doubles的数组。在新的空间存在的GC的时间足够长的对象迁移到这里。
  • 大对象空间:它的对象是太大,以适应在其他空间都在这个空间中创建。每个对象都有它自己的内存mmap’ed的区域
  • 代码空间:包含由JIT编译器生成的汇编代码。
  • 单元空间,属性单元空间,map空间:这个空间包含单元,属性单元和Map。这用于简化垃圾收集。

每个空间是由页面组成的。一个页面是内存从操作系统的mmap分配的区域。每一页始终是1MB大小,除了大对象空间的页面。

V8有两个内置的垃圾收集机制:清扫,标记清扫和标记压缩。

清扫是一个非常快的垃圾回收技术,在新的空间进行对象操作。清扫的一种实现Cheney’s Algorithm。概念非常简单,新空间被分成两个相等的各半个空间:到空间和从空间。当去空间满了就发起清扫的GC。它只是从空间和所有活动对象复制到从空间或将其提升到旧空间之一,如果他们经历了两次清扫,然后被完全从空间删除。可清除速度非常快但是他们保持双大小的堆不断在内存中拷贝对象的开销。使用可清除的原因是因为大多数对象早期被回收。

标记清扫和标记压缩是另一种V8垃圾回收的控制方式。另一种满垃圾收集器。它标志着所有活节点,然后清洗全部死的节点并重新整理内存。

GC性能和调试贴士

虽然Web应用程序的高性能可能不是个大问题,你还是会希望不惜一切代价避免泄漏。在full GC的标记阶段的应用程序实际上已暂停,直到垃圾收集完成。这意味着越多的对象在堆中时间越长,执行GC时,越长的用户等待时间。

总是给闭包和函数命名

所有的闭包和功能有名字的话,很容易检查跟踪堆栈。

1
2
3
db.query('GIVE THEM ALL', function GiveThemAllAName(error, data) {
...
})

避免在热函数上使用大对象

理想情况下,你要避免热函数内部的大对象,把所有的数据装配到新的空间。所有的CPU和内存相关的操作应在后台执行。此外,还要避免触发热功能不优化,优化的热函数使用较少的内存比非优化的。

热函数应该被优化

Hot functions that run faster but also consume less memory cause GC to run less often. V8 provides some helpful debugging tools to spot non-optimized functions or deoptimized functions.
如果热函数消耗更少的内存从而减少GC的频率,这样运行速度更快。 V8提供了一些有用的调试工具,以发现没有优化的函数或非优化的函数。

避免多态性IC的热函数

内部缓存(IC)用于加快执行的一些代码块中,要么通过缓存对象属性访问obj.key或一些简单函数。

1
2
3
4
5
6
7
function x(a, b) {
return a + b;
}
x(1, 2); // monomorphic
x(1, “string”); // polymorphic, level 2
x(3.14, 1); // polymorphic, level 3

x(a,b)第一次运行时,V8创建了一个单态的内部缓存。当你调用x一秒钟,V8移除老的IC,创建一个新的多态的IC来支持整形和字符串的操作。当你再次调用IC,V8重复以上步骤创建了另一个第三级的多态IC。、

然而,层级是有限的。在IC的层级达到5级时(可以改变–max_inlining_levels标示),这个函数将成为多态并且不再可被优化的。

这里直观的理解了单态函数运行速度最快的,也消耗更小的内存占用。

不要把大对象加入内存

这一个是显而易见的,众所周知的。如果具有大的要处理的文件,例如一个大的CSV文件,一行一行的读取并小块处理而不是加载整个文件存储器到内存中。在极少的情况下,CSV单行将超过1MB,才会新分配的空间存放。

不要阻塞主服务线程

如果你有一些热API需要一些时间来处理,像一个缩放图片的API,单独把它放到一个后台的线程的服务里。CPU密集型操作会阻塞主线程强制所有其他客户等待,保持发送中请求。未处理的请求数据就堆在内存中,从而迫使完整的GC需要更长的时间来完成。

不要创建不必要的数据

我曾经在使用restify有一个奇怪的经历。如果您发送几十万请求无效的URL,那么应用程序的内存将迅速增长就高达百兆字节,直到一个full GC几秒钟后,一切都会恢复正常。原来,为每个无效的URL,restify会生成一个新的错误对象,其中包括长的堆栈跟踪。这迫使新创建的对象被分配在大对象空间里,而不是新生代空间。

获得这样的数据可以在开发过程中非常有帮助的,但显然生产环境中不需要。因此,规则很简单 - 不生成数据,除非你确实需要它。

了解你的工具

最后,但当然不是最不重要的,是要了解你的工具。有各种调试器,泄漏cathers和使用情况的图形生成器。所有这些工具可以有助于你的软件更快,更高效。

结论

理解V8的垃圾回收和代码优化如何工作以及应用性能的关键。V8对JavaScript编译到本地的执行在某些情况下编写的代码实现性能与GCC编译的应用程序相媲美。

而如果你想知道,我的Toptal客户新的API应用程序,虽然还改进的余地,但是已经工作得非常好了!

Joyent公司最近发布的Node.js的新版本,它使用的V8引擎是最新版本之一。针对Node.js的v0.12.x写了一些应用程序可能无法与新的4.x版版本兼容。然而,应用程序将在node.js中的新版本出现了巨大的性能和内存使用情况的改善.

测量javascript函数的性能[译]

原地址:http://www.sitepoint.com/measuring-javascript-functions-performance

性能总是软件中扮演了重要的角色。在网络上,性能是尤为重要,如果我们提供缓慢的网页给用户,我们的用户可以轻松更改网站去访问我们的竞争对手的网站。作为一个专业的web开发者,我们不得不把这个问题考虑在内。很多老的网络性能优化的最佳实践,就像请求最小化,使用CDN,不写渲染代码块,如今还是管用的。然而,越来越多的web应用在使用Javascript,验证我们的代码是快的是很重要的。

假如你有一个正在运行的函数,但是你怀疑这个函数运行没有足够快,同时你计划着要改进这个函数。如何证明你的假设呢?什么是当今测试函数性能的最贱实践呢?一般来说,最好实现这个任务的方式是使用函数内建的performance.now()来测量在你执行函数前后的时间。

在这篇文章我们将会讨论如何测量代码执行时间和避免一些常见的坑的技术。

Performance.now()
高精度计时接口提供的一个方法,名字叫now()的可以返回一个DOM高精度时间戳对象。提供了一个浮点小数反映当前时间毫秒到毫秒的千分之一的时间戳。分别,这些数据没有添加过多的值来提供你分析,但是两个不同的数字之间的差值可以描述所消耗多少时间。

事实上它比内置的日期对象更准确,并且是‘单调’的。意味着,简单来说,是不会受到系统的影响(像你的笔记本操作系统)周期性地校正系统时间。在更简单来说,定义的两个日期实例计算出的插值并不表示所有消耗的时间。

“单调”的数学定义式是(一个函数或数量)不同的方式进行不是增加就是减少。

另一种方式的解释,可以尝试想象下这一年之中当时钟前进或后退。例如,当时钟在的你的国家由于需要提高白天的日照,从而把时间跳过一小时,如果你创建时间实例在这个时间回退一小时之前,另一个时间实例在这个之后,将会看到不同内容就像“1小时3秒123毫秒”。如果使用两个performance.now()实例,会是”3秒123毫秒456789千分之一毫秒”。

在这个章节,我不会讲这些API的详情。所以如果你要看更多关于如何使用,我建议你去看下探索高精度计时API这个文章。

现在你已经知道了什么是高精度计时API和如何使用,让我们深入探讨一些潜在的问题。但在这之前,我们定义一个叫makeHash()的函数来用于我本文的其余部分的介绍。

1
2
3
4
5
6
7
8
9
10
function makeHash(source) {
var hash = 0;
if (source.length === 0) return hash;
for (var i = 0; i < source.length; i++) {
var char = source.charCodeAt(i);
hash = ((hash<<5)-hash)+char;
hash = hash & hash; // 转化到32的整形
}
return hash;
}

执行这个函数像如下的测量:

1
2
3
4
var t0 = performance.now();
var result = makeHash('Peter');
var t1 = performance.now();
console.log('Took', (t1 - t0).toFixed(4), 'milliseconds to generate:', result);

如果你在浏览器里运行,你应该可以看到像如下的结果:

1
Took 0.2730 milliseconds to generate: 77005292

运行的例子如下:

通过这个例子,来开始我们的讨论

陷阱 #1 - 不小心测量了不重要的事情
在这个例子里,你能在performance.now()之间只做一件事并且其他的函数调用makeHash(),并指定其值设置为一个变量的结果。我们需要执行该功能,没有别的时间。这个测量详细如下:

1
2
3
4
var t0 = performance.now();
console.log(makeHash('Peter')); // bad idea!
var t1 = performance.now();
console.log('Took', (t1 - t0).toFixed(4), 'milliseconds');

直接可以运行的代码片段demo如下:

在这个例子里,我们将会测试makeHash('Peter')调用所消耗的时间,多长在发送和打印输出在控制台。我们不知道多久这两个操作分别的时间消耗。你只知道一起的时间。它需要发送和打印输出的时间将很大程度上取决于浏览器,甚至取决于当时正在进行什么。

也许你完全知道的console.log是不可预测的消耗时间。但它是将执行多个功能同样是错误,即使各功能不涉及任何的I/O。如:

1
2
3
4
5
var t0 = performance.now();
var name = 'Peter';
var result = makeHash(name.toLowerCase()).toString();
var t1 = performance.now();
console.log('Took', (t1 - t0).toFixed(4), 'milliseconds to generate:', result);

同样,我们不知道的执行时间是如何分布的。变量赋值是toLowerCase()的调用,还是toString()的调用?

陷阱 #2 - 只测量一次
另一个通常会犯的错误只是一次测量,总体所花费的时间在这个基础上的结论。很可能是在不同的时间完全不同的。执行的时间很大程度取决于多种因素:

  • 编译器预热的时间(如 编译代码到字节码的时间)
  • 主线程是忙着做我们并没有意识到要去其他的东西,
  • 你的计算机的CPU(S)是忙于的其他事情而减慢你的整个浏览器上增量改进的重复执行的功能。

像这样:

1
2
3
4
5
6
var t0 = performance.now();
for (var i = 0; i < 10; i++) {
makeHash('Peter');
}
var t1 = performance.now();
console.log('Took', ((t1 - t0) / 10).toFixed(4), 'milliseconds to generate');

直接可以运行的代码片段demo如下:

这种方法的风险是,我们的浏览器的JavaScript引擎可能使用了子优化,这意味着第二次函数调用相同的输入,也可以从中受益记住第一输出,并简单地使用了第一次的输出。为了解决这个问题,你可以使用重复发送,在相同的输入字符串(例如:’Peter’)的多种不同的输入字符串代替。显然,随着测试与不同的输入,问题是正在测量功能需要不同的时间量。也许有些的输入导致比其他的输入更长的执行时间。

陷阱 #3 - 过分依赖的平均
在上一章节中,我们了解到,多次运行的东西,最好有不同的输入这种好的实践。但是,我们必须记住,使用不同的输入的问题是,执行可能需要比所有其它输入要长得多。因此,让我们退后一步,并发送相同的输入。假设我们发送相同的输入十次,并为每个,打印出来多久了。输出可能是这个样子:

1
2
3
4
5
6
7
8
9
10
Took 0.2730 milliseconds to generate: 77005292
Took 0.0234 milliseconds to generate: 77005292
Took 0.0200 milliseconds to generate: 77005292
Took 0.0281 milliseconds to generate: 77005292
Took 0.0162 milliseconds to generate: 77005292
Took 0.0245 milliseconds to generate: 77005292
Took 0.0677 milliseconds to generate: 77005292
Took 0.0289 milliseconds to generate: 77005292
Took 0.0240 milliseconds to generate: 77005292
Took 0.0311 milliseconds to generate: 77005292

注意为什么第一次的数据是和其他九次完全不同。最有可能的,这是因为在我们的浏览器的JavaScript引擎使用一些子优化,需要一些预热。还有一点,我们可以做,以避免这一点,但也有一些我们可以考虑,以防止有错误的结论很好的补救措施。

一种方法是计算最后九次的平均值。另一个更实际的方法是收集所有结果和计算一个中位数。基本上,它是所有的结果一字排开,排序顺序和采摘中间的一个。这个performance.now()是非常有用的,因为你会得到一个有用的数据。

使用中位值函数再尝试下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var numbers = [];
for (var i=0; i < 10; i++) {
var t0 = performance.now();
makeHash('Peter');
var t1 = performance.now();
numbers.push(t1 - t0);
}
function median(sequence) {
sequence.sort(); // note that direction doesn't matter
return sequence[Math.ceil(sequence.length / 2)];
}
console.log('Median time', median(numbers).toFixed(4), 'milliseconds');

陷阱 #4 - 在可预知的顺序中比较函数
我们已经明白,测量多次,取平均值是个好主意。此外,上一章节的例子告诉我们,可以使用优选平均的中值来代替。

现在,事实上,一个很好用的测量函数执行时间的方法是比较哪些函数更快。假设我们有两个函数,把相同类型的输入,并产生相同的结果,但在内部,他们的工作方式不同。

比方说,我们希望有一个功能,在不区分大小写情况下某个字符串是在字符串数组返回true,否则返回false。换句话说,我们不能用Array.prototype.indexOf,因为需要不区分大小写。这里有一个这样的实现

1
2
3
4
5
6
7
8
9
10
11
12
function isIn(haystack, needle) {
var found = false;
haystack.forEach(function(element) {
if (element.toLowerCase() === needle.toLowerCase()) {
found = true;
}
});
return found;
}
console.log(isIn(['a','b','c'], 'B')); // true
console.log(isIn(['a','b','c'], 'd')); // false

立即我们注意到,因为haystack.forEach循环可以使用一个早期匹配元素来改善替代。让我们尝试用好的老的写法,for循环的版本。

1
2
3
4
5
6
7
8
9
10
11
function isIn(haystack, needle) {
for (var i = 0, len = haystack.length; i < len; i++) {
if (haystack[i].toLowerCase() === needle.toLowerCase()) {
return true;
}
}
return false;
}
console.log(isIn(['a','b','c'], 'B')); // true
console.log(isIn(['a','b','c'], 'd')); // false

现在我们可以看下哪个更快。我们每个函数进行十次测量并收集测试结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
function isIn1(haystack, needle) {
var found = false;
haystack.forEach(function(element) {
if (element.toLowerCase() === needle.toLowerCase()) {
found = true;
}
});
return found;
}
function isIn2(haystack, needle) {
for (var i = 0, len = haystack.length; i < len; i++) {
if (haystack[i].toLowerCase() === needle.toLowerCase()) {
return true;
}
}
return false;
}
console.log(isIn1(['a','b','c'], 'B')); // true
console.log(isIn1(['a','b','c'], 'd')); // false
console.log(isIn2(['a','b','c'], 'B')); // true
console.log(isIn2(['a','b','c'], 'd')); // false
function median(sequence) {
sequence.sort(); // note that direction doesn't matter
return sequence[Math.ceil(sequence.length / 2)];
}
function measureFunction(func) {
var letters = 'a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z'.split(',');
var numbers = [];
for (var i = 0; i < letters.length; i++) {
var t0 = performance.now();
func(letters, letters[i]);
var t1 = performance.now();
numbers.push(t1 - t0);
}
console.log(func.name, 'took', median(numbers).toFixed(4));
}
measureFunction(isIn1);
measureFunction(isIn2);

运行下得到如下的输出:

1
2
3
4
5
6
true
false
true
false
isIn1 took 0.0050
isIn2 took 0.0150

直接可以运行的代码片段demo如下:

刚刚发生了什么?第一个功能是快三倍。这是不应该发生的!

原因很简单,但是很微妙。第一个功能,使用了haystack.forEach得益于在浏览器的JavaScript引擎的一些低级别的优化,当我们使用数组索引去做,就不会被优化。这证明了我们的观点:如果你不测量,你永远不知道!

结论
在我们试图如何使用performance.now()取得JavaScript的一个准确的执行时间,我们偶然发现了一个基准场景,我们的实证研究结果得出结论和我们的直觉完全相反。问题的关键是,如果你想写得更快的Web应用程序,JavaScript代码就需要优化。由于电脑(差不多)是活生生的东西,令人如此的不可预知。最可靠的方法就是通过衡量和比较来改进代码确保更快的执行速度。

另一个原因,我们永远不知道这些代码是更快的,因为不同的上下文中,我们有做同一件事的多种方式。在上一节中,我们执行不区分大小写字符串搜索寻找除其他26个字符串。如果我们10万名中匹配字符串,该结论可能是完全不同的。

上述例子并不完整,还有更多的陷阱,以做到心中有数。例如,测量不现实的场景或仅测量在一个JavaScript引擎。但肯定的是,谁想要写出更快,更好的Web应用程序,开发者就可以使用performance.now()这个不错的协助。最后但并非最不重要的,请记住,测试执行时间只产生了“更好的代码”的一个方面。还有内存和代码的复杂性考虑,要牢记。

现在你这么想?你有没有用这个功能来测试你的代码的性能?如果没有,你怎么在这个阶段进行?请在下面的评论中分享你的看法。让我们开始讨论!

todos用yo来实现的说明

本文档的目的是通过一个todos的例子讲下如何用yo进行前端开发,包含开发流程全过程,涵盖:开发,构建,测试,调试,部署。规范前端开发的工作流,后续会补充单元测试的部分。

中间使用到的工具和框架

服务端:

  • yo(express,一些必要的中间件)
  • npm(安装node模块)

客户端:

  • sea.js(客户端依赖包处理)
  • SPM(客户端依赖包管理)
  • gulp(项目构建,js,css,预处理,合并,压缩,部署)
  • compass(css预编译)
  • npm(工具依赖管理)

目录结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
yo.todo
|_client 客户端目录
| |_js 前端js源码目录
| |_sass sass源码目录
| |_index.js 前端js的入口文件
| |_package.json 项目构建文件(依赖包)
| |_gulpfile.js 项目构建gulp脚本
|_server 服务端目录
| |_adapters 接口适配业务代码目录
| |_interface 接口配置文件目录
| |_stub 后端桩代码目录
| |_views 视图目录
| |_app.js 应用入口文件
| |_staticConfig.js 静态文件路径配置
| |_package.json 项目构建文件(服务端端)
|_public 静态资源目录(用于开发,测试阶段)
|_css
|_sea.js 依赖seajs

服务初始化的过程:

请求调用过程

开发步骤

准备:

  • 安装必要的开发工具,当然先得安装node.js,具体参考这里
  • 安装compass,先安装ruby,参考这里
  • 安装spm,运行npm install spm@3.4.3 -g(spm3.4.3之后使用了webpack,所以暂时用3.4.3)。
  • 安装gulp,运行npm install gulp -g(如果安装了cnpm,可以替代npm)。
  • 安装slush,运行npm install slush -g(用于创建空项目)。
  • 安装slush-yo,运行npm install slush-yo -g(yo项目模板)。

新建一个空项目

1
2
3
4
5
6
7
mkdir todos
slush yo
cd client
spm install
npm install
cd server
npm install

在mac或者ubuntu上需要用sudo

运行空项目:

1
2
3
redis-server -port 7777
node app

可以看到如图:
运行成功

编写服务接口配置

需要存储todo list的服务,这个例子数据和状态主要保存服务端,

  • 添加一个todo;
  • 编辑一个todo;
  • 获取todo列表;
  • 切换todo的状态;
  • 删除todo;
  • 清除已完成的todo;

代码如下:

server/stub/router.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
var list = [];
var toggleAll = false;
module.exports = function(app) {
//添加
app.post('/todo', function(req, res) {
if (req.body.todo) {
list.push({
id: uuid(),
todo: req.body.todo,
state: 0
});
res.send(ret(true));
} else {
res.send(ret(false));
}
});
//编辑保存
app.put('/todo/:id', function(req, res) {
var saved = false,
state;
if (req.params.id) {
for (var i = 0; i < list.length; i++) {
if (req.params.id === list[i].id) {
setval(req.body.todo, req.body.state, list[i]);
saved = true;
}
}
}
res.send(ret(saved));
});
//首页
app.get('/todos', function(req, res) {
var data = ret(true, list);
res.send(data);
});
//删除
app.delete('/todo/:id', function(req, res) {
var isDel = false;
if (req.params.id) {
for (var i = 0; i < list.length; i++) {
if (req.params.id === list[i].id) {
list.splice(i, 1);
isDel = true;
break;
}
}
}
res.send(ret(isDel));
});
//切换状态
app.put('/todos/toggleall', function(req, res) {
for (var i = 0; i < list.length; i++) {
if (!toggleAll) {
list[i].state = 1;
} else {
list[i].state = 0;
}
}
toggleAll = !toggleAll;
res.send(ret(true));
});
//清除完成项
app.delete('/todos/completed', function(req, res) {
var unCompleted = [];
for (var i = 0; i < list.length; i++) {
if (list[i].state === 0) {
unCompleted.push(list[i]);
}
}
list = unCompleted;
return res.send(ret(true, list));
});
}
...

可以用postman类似的http客户端测试下服务:

postman

编写前端view对应后端服务的接口配置和接口适配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
exports.domain = 'http://localhost:3000';
exports.res =
[{
route: '/',
method: 'GET',
view: 'pages/index',
url: '/todos/',
params: []
}, {
route: '/:state',
method: 'GET',
view: 'pages/index',
url: '/todos/',
adapter: 'index',
params: []
}];

目前的todo的view只有一个,为了使不同状态的匹配加了”/:state”的路由。
后端映射到同一个接口。

后端服务也许无法满足你前端展示的要求,所以,会在适配层,加一些返回数据结构的处理。
适配层的业务注入规约:会找到interface的路由作为注入的原则(路由名称+请求方法),或者指定路由的适配的业务模块。
代码如下(server/adapters/index.js)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
exports.get = function(data, req, res) {
var states = ['active', 'completed'];
var curState = 'all';
var curStateVal = 3;
data.completedTodos = false; //完成的todos
data.activeTodoWord = 'items';//todo单位的单复数
data.activeTodoCount = 0;//当前未完成todo
data.allCount = data.data.length;//所有的todo数量
data.module = 'todos';//js的入口模块
var curData = [];
for (var j = 0; j < states.length; j++) { //判断过滤条件
if (states[j] === req.proxyParams.params.state) {
curState = states[j];
curStateVal = j;
break;
} else {
curState = 'all';
curStateVal = 3;
}
}
data[curState] = true; //设置当前的过滤条件
for (var i = 0; i < data.data.length; i++) {
if (data.data[i].state === 1) { //设置是否有完成
data.completedTodos = true;
data.data[i].completed = true;
} else {
data.activeTodoCount++; //设置todo的数据
}
if (data.data[i].state === curStateVal) {
curData.push(data.data[i]); //过滤数据
}
}
if (curStateVal !== 3) { //设置过滤后的数据
data.data = curData;
}
if (data.activeTodoCount === 1) { //设置展示单复数
data.activeTodoWord = 'item';
}
return data;
}

编写模板

拆解页面结构:

1
2
3
4
5
6
7
8
layout
\_header
\_todo
\_header
\_section
\_footer
\_bottom
\_footer

其中,header,todo/header,todo/section,todo/footer,todo/bottom,footer都是partials
采用handlebars模板编写。
当前后端返回数据时,在前端进行数据绑定生成HTML。
没有样式的视图

编写css样式

编写样式采用compass的方式,用compass watch,实时编译成css。

到client目录,直接compass watch

可以按照partials,拆分样式。
配置生成文件,在config.rb,或者使用gulp进行的构建。
添加样式后

编写JS前端逻辑

前端需要操作服务端的todo的内容,并且展现,主要是增删查改这些操作,同时绑定相关的事件。这个例子里面使用jquery和pjax等,所以需要在client下的package.json,添加依赖包:

1
2
3
4
5
6
7
8
9
"spm": {
"main": "index.js",
"dependencies": {
"jquery": "1.8.3",
"jquery-pjax-183": "1.0.0",
"nprogress-183": "0.1.6",
"import-style": "1.0.0"
},
...

需要执行spm install

前端的主要逻辑在js/todos.js里面,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
/**
* todos
*
* todos的前端代码
* 只提交todo的操作,服务端维护todo状态
*/
var $ = require('jquery');
var NProgress = require('nprogress-183');
require('jquery-pjax-183');
/**
* 初始化Pjax
*
* @return void
*/
function initPjax() {
$(document).pjax('a', '#pjax-container');
$(document).on('pjax:start', function() {
NProgress.start();
});
$(document).on('pjax:end', function() {
NProgress.done();
});
}
var ENTER_KEY = 13; //回车键
var ESCAPE_KEY = 27; //esc键
var $newTodoInput = $("#new-todo"), //新建一个todo
$listLi = $("#todo-list li"), //列表单元
$listToggle = $("#todo-list .toggle"), //切换todo状态
$listLiEdit = $("#todo-list .edit"), //编辑输入
$toggleall = $("#toggle-all"), //切换所有的todo状态
$listDestroy = $("#todo-list .destroy"), //删除todo
$clearCompleted = $("#clear-completed"); //清除完成的
/**
* 界面操作对象
* @type {Object}
*/
var actions = {
toggleAll: {
url: '/todos/toggleall',
method: 'PUT',
eventHandle: [{
event: 'click',
elem: $toggleall
}]
},
add: {
url: '/todo',
method: 'POST',
eventHandle: [{
event: 'keyup',
elem: $newTodoInput,
handle: function(e) {
if (e.which === ENTER_KEY) {
actions.add.data = {
todo: $(e.target).val() + ''
};
$(e.target).val('');
return true;
}
}
}]
},
edit: {
url: '/todo/',
method: 'PUT',
before: function(elem) {
this.params = elem.parents('li').attr('data-id');
var state = elem.parents('li').find('.toggle').attr('checked') ? 1 : 0;
this.data = {
todo: elem.parents('li').find('label').text(),
state: state
};
},
eventHandle: [{
event: 'click',
elem: $listToggle
}, {
event: 'dblclick',
elem: $listLi,
handle: function(e) {
var $input = $(e.target).closest('li').addClass('editing').find('.edit');
$input.val($input.val()).focus();
return false;
}
}, {
event: 'keyup',
elem: $listLiEdit,
handle: function(e) {
var val = e.target.value;
if (e.which === ENTER_KEY) {
$(e.target).blur();
$(e.target).parents('li').find('label').text(val);
return true;
}
if (e.which === ESCAPE_KEY) {
$(e.target).blur();
}
return false;
}
}, {
event: 'blur',
elem: $listLiEdit,
handle: function(e) {
$(e.target).parents('li').removeClass('editing');
}
}]
},
remove: {
url: '/todo/',
method: 'DELETE',
before: function(elem) {
this.params = elem.parents('li').attr('data-id');
},
eventHandle: [{
event: 'click',
elem: $listDestroy,
handle: function() {
return true;
}
}]
},
clearCompleted: {
url: '/todos/completed',
method: 'DELETE',
eventHandle: [{
event: 'click',
elem: $clearCompleted
}]
}
}
/**
* 事件绑定操作
* @return {void}
*/
function bind() {
$.each(actions, function(key, value) {
$.each(value.eventHandle, function(index, hb) {
hb.elem.live(hb.event, function(e) {
var isSend = false;
if (hb.handle) {
isSend = hb.handle(e);
} else {
isSend = true;
}
if (isSend) {
if (value.before) {
value.before($(e.target));
}
send(value);
}
});
});
});
initPjax();
}
/**
* 发送操作信息
* @param {Object} hb
* @return {void}
*/
function send(hb) {
var url = hb.url + (hb.params || '');
$.ajax({
url: url,
type: hb.method,
data: hb.data ? hb.data : null,
dataType: "json"
}).done(function(data) {
if (data.opts) {
$.pjax.reload('#pjax-container');
} else {
alert('something wrong!');
}
}).fail(function() {
alert('something wrong!');
});
}
bind();

整理联调

代码编写完成后,可以通过gulp start,进行代码联调。
看看有没有问题,修复开发过程中疏漏的点。在yo中,如果是开发环境,会自动启用smp的服务,对于拆散js的组合调用,可以用chrome dev tool进行前端调试,后端调试,可以使用npm install -g node-inspector,用node-debug app.js进行服务端的代码调试。
chrome dev tool

服务端的express传参,和变量数据,可以通过express-debug查看
express-debug

构建项目

到此为止,已经把todos的功能已全部实现但是,工作流程,才走了一半,后面将会执行构建,将现有的项目,部署到测试环境,当测试环境测试没有问题,讲发布到生产。

而做这些事情,可以由gulp来完成,gulp可以编写管道式的构建过程,高效的将需要的自动化过程执行。
目前前端的gulp涉及如下的任务:

  • 运行server
  • compass的实时预处理
  • js的合并和库文件的合并
  • 样式的合并
  • 部署到CDN

代码清单详见:client/gulpfile.js。

测试代码打包

1
2
cd client
gulp

测试环境运行

1
2
cd ..
NODE_ENV=test node server/app

生产环境发布和运行

把代码部署到CDN

1
2
cd client
gulp dist

运行生产环境,需要保证服务不会挂掉,所以需要使用MP2来进行进程守护,以及服务监控和治理。

1
2
cd ..
NODE_ENV=production pm2 start server/app

后续补充工作流

  • 单元测试
  • 持续集成
  • 代码检查

后续待做事项

  • 组件化
  • 前后端分离(接口规范)
  • docker化

服务端或客户端生成HTML的设计思想

如果说web前端主要是由:HTML,CSS,JavaScript这些技术的组合,那么HTML的生成,可以由服务端或者客户端去渲染,取决于数据和HTML的绑定在哪个端去执行。

当是SPA(Single Page Application)形态的应用时:天然的使HTML的生成或则说DOM生成可以由前端完成,大量的路由逻辑,界面逻辑都在浏览器端完成。主要是服务端提供“服务接口+数据响应”,浏览器端调用“服务接口”,数据绑定到视图渲染(可以是DOM生成或者HTML生成并修改DOM),以及路由视图的跳转。

SPA有一个问题需要解决,那就是如何做SEO,主要问题是两个:一个是URL友好,一个响应HTML结构语义化。

解决办法:

  1. url友好

url友好,在SPA的大部分前端界面跳转都相对映射的URL(当然如果使用一些前端框架也实现了URL变化,如Angular,React-Router,vue-router),除非把url的变化考虑进去,使用Hash,或者HMTL5的history的push state接口,来改变url。

现有的大部分SPA的前端框架都有前端url路由功能,所以实现这个,难度不大。

  1. 响应HTML结构语义化

如果数据在客户端绑定并渲染视图,在对于大多数搜索引擎的爬虫无法理解在客户端渲染的DOM,所以最好在服务端生成HTML。

针对SPA,如比较目前比较通用的有类似Prerender.iobromboneseojs的服务,原理是:

  • 当一个搜索引擎的爬虫访问你的应用程序并且看到<meta name="fragment" content="!">时,它会在你的URL中添加一个?_escaped_fragment_=tag
  • 你的服务器将会拦截这个请求,并把它发送给一个用来处理这个特殊的爬虫请求的中间件。在这篇文章中,我们选用Prerender.io,因此,下一步是针对Prerender.io的。
  • Prerender.io 将会检查请求的页面是否有一个现存的快照(或者缓存的页面),如果有,它会将这个页面响应给爬虫,如果没有的话,他会调用PhantomJS(一个服务端渲染DOM的中间键)来渲染这个完整页面,并将它响应给爬虫。

优点:这个的做法的好处是和语言无关,比较解耦,可以和不同的后端语言结合使用。可以单独部署一组SEO的服务器来应对爬虫的访问,对于用户访问影响不大。

缺点:PhantomJS在生成界面的时候,比较消耗服务器资源,同时时间较长,需要做生成界面的缓存,以及页面缓存的更新。

SPA大量在现有互联网的使用,即将到来,只是现在囿于低版本IE浏览器的制约,约束在浏览器的发展。尽管现有前端框架都有优雅降级的做法,但是在大量低版本IE的用户面前,目前是无法大规模使用的。

主要原因:

  1. 低版本IE(6,7,8)对于ES5的支持(Object.defineProperty),甚至ES6的支持。
  2. 低版本IE(6,7,8)对CSS,DOM不是按照标准去理解的。
  3. 低版本IE(6,7,8)JS引擎性能较差,对于重度依赖Javascript的SPA会有性能瓶颈或编码不慎会导致无故内存泄露。

但是还是比较看好SPA在浏览器的发展,毕竟新版的IE,以及其他的现代浏览器已经不存在以上的问题了,只是时间问题了。

现在升级到Node V4的七个理由

原地址:http://www.cli-nerd.com/2015/09/09/7-reasons-to-upgrade-to-node-v4-now.html

今天Node v4发布了。这是第一个在io.js合并后的
第一个稳定版本,给我们带来了一堆亮点,如新的ES6语法的添加。虽然有已经增加了很好的ES6的描述,但是这篇文章展示了如何使用它们!

特别是,我要去以下ES6补充的地址看看:

Template Strings
Classes
Arrow Functions
Object Literals
Promises
String Methods
let and const

1. Template Strings(模板字符串)

如果你要用Javascript创建多行字符串,你或许会像下面的做法:

1
2
3
4
5
var message = [
'The quick brown fox',
'jumps over',
'the lazy dog'
].join('\n');

虽然这适用于少量的文字,但会多个语句变得混乱。因此,聪明的开发者想出了一个叫hack called multiline的模块:

1
2
3
4
5
6
var multiline = require('multiline');
var message = multiline(function () {/*
The quick brown fox
jumps over
the lazy dog
*/});

幸运的是,ES6给我们带来了模板字符串:

1
2
3
4
5
var message = `
The quick brown fox
jumps over
the lazy dog
`;

在这个特性里,还可以进行字符串的赋值操作

1
2
3
4
5
6
7
8
9
var name = 'Schroedinger';
// stop doing this ...
var message = 'Hello ' + name + ', how is your cat?';
var message = ['Hello ', name, ', how is your cat?'].join('');
var message = require('util').format('Hello %s, how is your cat?', name);
// and instead do that ...
var message = `Hello ${name}, how is your cat?`;

查看更多template strings的详情在MDN.

2. Classes(类)

定义类ES5看起来有些奇怪,还需要一定的时间去习惯:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var Pet = function (name) {
this._name = name;
};
Pet.prototype.sayHello = function () {
console.log('*scratch*');
};
Object.defineProperty(Pet.prototype, 'name', {
get: function () {
return this._name;
}
});
var Cat = function (name) {
Pet.call(this, name);
};
require('util').inherits(Cat, Pet);
Cat.prototype.sayHello = function () {
Pet.prototype.sayHello.call(this);
console.log('miaaaauw');
};

幸运的是,在Node中我们可以使用新的ES6语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Pet {
constructor(name) {
this._name = name;
}
sayHello() {
console.log('*scratch*');
}
get name() {
return this._name;
}
}
class Cat extends Pet {
constructor(name) {
super(name);
}
sayHello() {
super.sayHello();
console.log('miaaaauw');
}
}

一个extends关键字,constructors,调用父类和属性。多好?更多在MDN有完整的指南

3. Arrow Functions(箭头函数)

动态的绑定函数调用的结合总是会引起一些混乱,人们在多个方面围绕它的工作:

1
2
3
4
5
6
7
8
9
10
11
Cat.prototype.notifyListeners = function () {
var self = this;
this._listeners.forEach(function (listener) {
self.notifyListener(listener);
});
};
Cat.prototype.notifyListeners = function () {
this._listeners.forEach(function (listener) {
this.notifyListener(listener);
}.bind(this));
};

现在只需要使用箭头函数:

1
2
3
4
5
Cat.prototype.notifyListeners = function () {
this._listeners.forEach((listener) => {
this.notifyListener(listener);
});
};

查看arrow functions更多详情.

4. Object Literals(对象直接量)

当使用对象直接量,你可以使用很不错的快捷方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var age = 10, name = 'Petsy', size = 32;
// instead of this ...
var cat = {
age: age,
name: name,
size: size
};
// ... do this ...
var cat = {
age,
name,
size
};

此外,您现在可以轻松的将函数添加到您的对象直接量上

5. Promises

可以替换掉用第三方的像bluebird和Q的依赖,直接使用原生的promises。就像如下的api:

1
2
3
4
5
6
7
8
var p1 = new Promise(function (resolve, reject) {});
var p2 = Promise.resolve(20);
var p3 = Promise.reject(new Error());
var p4 = Promise.all(p1, p2);
var p5 = Promise.race(p1, p2);
// and obviously
p1.then(() => {}).catch(() => {});

6. String Methods

我们还可以使用一些新的字符串工具方法:

1
2
3
4
5
6
7
// replace `indexOf()` in a number of cases
name.startsWith('a')
name.endsWith('c');
name.includes('b');
// repeat the string three times
name.repeat(3);

去告诉那些写ruby的孩子!字符串现在还更好的支持unicode的处理。

7. let and const

猜下这个在如下函数调用后返回的值:

1
2
3
4
5
6
7
var x = 20;
(function () {
if (x === 20) {
var x = 30;
}
return x;
}()); // -> undefined

是的,undefined。替换掉var,使用let你可以保证预期的结果:

1
2
3
4
5
6
7
let x = 20;
(function () {
if (x === 20) {
let x = 30;
}
return x;
}()); // -> 20

理由是:var是函数作用域级的,然而let是块作用域级别的(这是人们所希望)。因为这个,说let是安全的var。你可以在MDN上获取更多关于let的信息

新增:Node现在支持const关键字,从而阻止您重新赋给一个不同的值相同的引用:

1
2
3
4
var MY_CONST = 42; // no, no
const MY_CONST = 42; // yes, yes
MY_CONST = 10 // with const, this is no longer possible

综述所述

Node v4带来了更多的ES6的特性,同时我也希望这些7个例子已经使你信服升级和使用最新版本了。

这些更多的语言特性(像 maps/sets, symbols , generators,等)。请确保你已经查看过ES6补充的NodeV4概述。快乐升级!

40个我们不可或缺的NPM包[译]

原地址:https://medium.com/startup-study-group/40-npm-modules-we-can-t-live-without-36e29e352e3a

npm picture

Croissant按照字母顺序分享了他们用到的40个不可或缺的NPM模块:

agenda

这是一个类似于“cron”的作业,如每隔几分钟做一些事情。

agenda-ui

可视化的界面,用于管理agenda的作业,就像麦当劳的订单界面,让员工知道订单的状态。

async

用于对多个异步一次处理,主要用于异步编程,尽管我们也开始迁移到”q”,但是我们任然在一些地方使用,像“waterfall”和“each”对于一行数据在多数据相互依赖操作,会用到。

aws-sdk

我们用于上传文件到AWS S3上。

bcryptjs

担心你的密码的安全性?不用了!该算法是行业标准。即使我们遭受过黑客攻击,您的密码将仍然只是字母和符号混合的字符串。只要你的密码不是一个简单字典中的单词,虽然,因为如果有人没有得到这个数据,他们可以尝试在字典中的所有单词进行暴力破解。

body-parser

我们使用ExpressJS,所以我们需要序列化那些JSON的响应。

braintree

用于支付的一个智能(支付网关)依赖包。(国内用到不多)

chai

我们使用这个用于集成测试。(一个断言库)

compression

另一个像Express一样,你需要的包。用于压缩服务的响应,让数据在网络上传输更快。好东西。

express

这是个大魔法师。这是一个ExpressJS和NodeJS的web服务的核心模块,让我们可以不用写了大量的样板HTTP服务器代码。带来了一些很酷的特性,如指定一个静态目录作为一个简单的文件服务器,当然,以从它的充满活力的社区,创建的许多插件,可以很容易使用。

forever

如果一个node进程或者express服务崩溃了怎么办?这个小家伙会在后台为你重新启动。这实在是太聪明 - 你可以指定运行多久失败需要重新启动。这可以防止重启一遍又一遍自动失败进程。

fs-extra

当“fs”模块不够用时,“fs-extra”给你想要的更多功能。我们真的只是用它来满足使一个嵌套的文件夹如果在父文件夹不存在创建的能力。出于某种原因,对于“fs”来说,这是过分的要求。

gm

这个模块是一种法术。GraphicsMagick是一个包装已经由来已久的被用于许多图像操纵的很经典ImageMagick的C库。我们使用对上传图片的尺寸缩放和切图操作。你知道的那些放在AWS美丽场馆我们,我们会对它们用这个处理。

gulp

Gulp总是在我们编码背后运行着。它会自动的压缩我们的javascript和css文件。我们把它用于测试运行。它也有很多的包(如果你要找的话)!有人说它像“grunt”,其实是流运行的。

gulp-angular-templatecache

之前我们提到用Angular吗?我们是用Angular的。这个gulp插件允许我们在所有的HTML模板为一个文件以便更快的加载网站。Angular在加载页面时可以使用这些模板。

gulp-concat

记得我们前面谈到的呗?这需要找下。用于合并文件。

gulp-csso

做css最小化的。

gulp-istanbul

一个运行istanbul的单元测试覆盖率的gulp插件。

gulp-livereload

前端开发者的必备,可以连接你的浏览器当每一次文件修改它会刷新页面。做得好,gulp。

gulp-mocha

用于测试,这个插件封装了mocha,我们可以智能的进行测试,就像保证接口端可以正常运行。

gulp-plumber

由于我们所有事情用gulp的流和管道,我们需要一个小小的库,万一像gulp-csso运行失败,可以保证其他运行平滑。

gulp-sass

你以为我们使用CSS? 再想一想。你看到的一切曾经是一个轻量的,顶尖的,SCSS(又名Sass)文件。Gulp 监听和编译每当我们作出改变时编译我们出主要的CSS的输出..超级快!

gulp-uglify

最后但并非最不重要的,这一插件确实不错的压缩前端JavaScript代码,所以我们可以超快速和超安全传输给你。

jwt-simple

JWT(JSON Web Token) 编码和解码的node包.

lodash

有用的Javascript的工具集,pick, omit, contains对于接口服务器响应时间大大缩短。

mime-types

用于跟踪文件扩展名的库,一个jpg图片文件得到的结果如“image/jpeg”。

mocha

mocha是一个测试平台,可以使用BDD的方式测试,使用“describe”和“it”,没有比这更简单的Javascript的测试了。

moment

人性化的时间格式。

moment-timezone

moment的时区转化

mongoose

我们提到我们用MongoDB的?我们使用MongoDB的。Mongoose一直我们的团队所熟知,让我们总能看到一眼我们的数据结构是什么。没有它,你可能最终与各种数据库中的对象,造成谁知道什么样的混乱的后端和前端的。

morgan

express的http的访问日志中间件。

newrelic

New Relic是一家给你的应用提供信息的很酷的公司。你可以很简单的加一个包到你服务的第一行,然后去他们的网站观察神奇的事情发生。我们使用它来获取在产品化的服务上发生的错误。

nodemailer

如果你需要发邮件,不需要你自个发-使用一个邮件传输服务,就像SendGird。这个有一些很好的特性,就像给邮件封装一个漂亮的模板。Nodemailer就是一个让我们发生简单信息到SendGird的包。

prerender-node

还记得我们是用Angular吗?这个是用于因为搜索引擎无法解析Angular才用的。带来了陈显的援助,使用这个包,网络爬虫可以更容易的看到一个不是Angular版本的网站因为prerenderer让它发生变化。关闭JavaScript在Web浏览器您再次浏览Croissant,你就会明白我们的意思。

protractor

protractor是一个端对端的测试框架,可以在浏览器里面进行自动化的执行任务和测试

q

async是不错的瀑布楼和映射操作同时,“q”是不错的使用promises的库,通过它,我们意味着使用普通回调函数以及封装成“q”的promise语法。让我们像使用普通回调方式一样使用链式的异步函数,解决“callback hell”。

request

每当一旦我们需要使用http请求来扩展服务时,我们总是使用这个包即时在NodeJS初期,在内部使用扩展服务。

slug

这个是一个转化“Hello There” 到“hello-there”的库,完美的动态的转化标题成友好的链接。

stripe

值得时间考验。Stripe是我们的信用卡支付流程。这个足够好来存储用户的信用卡信息以PCI-compliant方式。我们使用它们的SDK存储卡到文件和支付。

supertest-as-promised

这个是我们核心的智能测试。使用promises让我么会能很容易导出某种响应状态码,同时使用响应数据在下一次测试中,我们在chai里使用。

这样啦,你有了我们的好东西的清单。我们总是会开放的问题,意见和建议。请友善些:)

Dave
Co-founder & CEO, Croissant

Generator库:co的用法

什么是Generator?

Generator是ES6里面新增的语法特性,是异步编程的一个语法级的解决方案。Generator在内部封装的多个状态,在执行Generator函数的时候,会返回一个遍历状态的迭代器对象。表现形式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function* helloWorldGenerator() { //*是Generator一个语法表示
yield 'hello';//yield 是返回状态的值
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
hw.next(); //在每次执行迭代器对象时,会返回当前获取的状态信息。
// { value: 'hello', done: false }
hw.next();
// { value: 'world', done: false }
hw.next();
// { value: 'ending', done: true }
hw.next();
// { value: undefined, done: true }

Generator与协程
协程(coroutine)是一种程序运行的方式,可以理解成“协作的线程”或“协作的函数”。协程既可以用单线程实现,也可以用多线程实现。前者是一种特殊的子例程,后者是一种特殊的线程。

(1)协程与子例程的差异

传统的“子例程”(subroutine)采用堆栈式“后进先出”的执行方式,只有当调用的子函数完全执行完毕,才会结束执行父函数。协程与其不同,多个线程(单线程情况下,即多个函数)可以并行执行,但是只有一个线程(或函数)处于正在运行的状态,其他线程(或函数)都处于暂停态(suspended),线程(或函数)之间可以交换执行权。也就是说,一个线程(或函数)执行到一半,可以暂停执行,将执行权交给另一个线程(或函数),等到稍后收回执行权的时候,再恢复执行。这种可以并行执行、交换执行权的线程(或函数),就称为协程。

从实现上看,在内存中,子例程只使用一个栈(stack),而协程是同时存在多个栈,但只有一个栈是在运行状态,也就是说,协程是以多占用内存为代价,实现多任务的并行。

(2)协程与普通线程的差异

不难看出,协程适合用于多任务运行的环境。在这个意义上,它与普通的线程很相似,都有自己的执行上下文、可以分享全局变量。它们的不同之处在于,同一时间可以有多个线程处于运行状态,但是运行的协程只能有一个,其他协程都处于暂停状态。此外,普通的线程是抢先式的,到底哪个线程优先得到资源,必须由运行环境决定,但是协程是合作式的,执行权由协程自己分配。

由于ECMAScript是单线程语言,只能保持一个调用栈。引入协程以后,每个任务可以保持自己的调用栈。这样做的最大好处,就是抛出错误的时候,可以找到原始的调用栈。不至于像异步操作的回调函数那样,一旦出错,原始的调用栈早就结束。

Generator函数是ECMAScript 6对协程的实现,但属于不完全实现。Generator函数被称为“半协程”(semi-coroutine),意思是只有Generator函数的调用者,才能将程序的执行权还给Generator函数。如果是完全执行的协程,任何函数都可以让暂停的协程继续执行。

如果将Generator函数当作协程,完全可以将多个需要互相协作的任务写成Generator函数,它们之间使用yield语句交换控制权。

co是什么?

co是tj写的一个用于 Generator 函数的自动执行的函数库。最新是4.0版本,支持promise等一些新特性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
var co = require('co');
co(function *(){
// yield any promise
var result = yield Promise.resolve(true);
}).catch(onerror);
co(function *(){
// resolve multiple promises in parallel
var a = Promise.resolve(1);
var b = Promise.resolve(2);
var c = Promise.resolve(3);
var res = yield [a, b, c];
console.log(res);
// => [1, 2, 3]
}).catch(onerror);
// errors can be try/catched
co(function *(){
try {
yield Promise.reject(new Error('boom'));
} catch (err) {
console.error(err.message); // "boom"
}
}).catch(onerror);
function onerror(err) {
// log any uncaught errors
// co will not throw any errors you do not handle!!!
// HANDLE ALL YOUR ERRORS!!!
console.error(err.stack);
}

数组方式调用

1
2
3
4
5
6
7
8
co(function* () {
var res = yield [
Promise.resolve(1),
Promise.resolve(2),
Promise.resolve(3),
];
console.log(res); // => [1, 2, 3]
}).catch(onerror);

对象方式调用

1
2
3
4
5
6
7
co(function* () {
var res = yield {
1: Promise.resolve(1),
2: Promise.resolve(2),
};
console.log(res); // => { 1: 1, 2: 2 }
}).catch(onerror);

co(fn*).then( val => )

1
2
3
4
5
6
7
co(function* () {
return yield Promise.resolve(true);
}).then(function (val) {
console.log(val);
}, function (err) {
console.error(err.stack);
});

var fn = co.wrap(fn*)

1
2
3
4
5
6
7
var fn = co.wrap(function* (val) {
return yield Promise.resolve(val);
});
fn(true).then(function (val) {
});

Bluebird中Promise模式使用

bluebird logo
Bluebird是一个高性能全功能的Promise库,主要有以下特性:

  • 实现了Promises A+规范
  • 同步检查(promise状态,返回值,失败原因)
  • 并发调用(调用多个异步处理)
  • 异步模型转化到Promise模式
  • 使用通过并行使用Python/C#进行资源管理的取消和超时
  • 并行的C#异步和等待
  • 试用的工具方法,如:
    .bind()
    .call()
    Promise.join()…
  • 调试解决方案和合理的默认值
  • 优越的性能表现

bluebird支持node.js和浏览器端

node.js

1
npm install bluebird

然后

1
var Promise = require("bluebird");

浏览器端支持(ES5)

浏览器支持

可见对于IE必须是IE9和以上

用法:

下载:bluebird.jsbluebird.min.js

以解析一个json文件为例

1
2
3
4
5
6
7
8
9
fs.readFileAsync("file.json").then(JSON.parse).then(function(val) {
console.log(val.success);
})
.catch(SyntaxError, function(e) {
console.error("invalid json in file");
})
.catch(function(e) {
console.error("unable to read file");
});

具体需要看详细的可以查看官方github

JQuery中Deferred/Promise的使用

同样Deferred也是为了解决异步嵌套回调而存在的。早在JQuery 1.5就已经支持了。

使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var dtd = $.Deferred(); // 新建一个Deferred对象
var wait = function(dtd){
var tasks = function(){
      alert("执行完毕!");
      dtd.resolve(); // 改变Deferred对象的执行状态
    };
    setTimeout(tasks,5000);
    return dtd.promise(); // 返回promise对象
  };
var d = wait(dtd); // 新建一个d对象,改为对这个对象进行操作
$.when(d)
.done(function(){ alert("哈哈,成功了!"); })
.fail(function(){ alert("出错啦!"); });

我们看下怎么样用,下面列举了几个场景:

  1. 单个Ajax异步交互
1
2
3
4
5
$.ajax('abc.php').done(function(result) {
alert('success');
}).fail(function (jqXHR, textStatus, errorThrown) {
console.log('error');
});
  1. 多个Ajax异步交互
1
2
3
4
5
6
$.when($.ajax('abc.php'), $.ajax('abc2.php'), $.ajax('abc3.php'))
.done(function(result1, result2, result3) {
alert('success');
}).fail(function (jqXHR, textStatus, errorThrown) {
console.log('error');
});
  1. 动画链式调用
1
2
3
4
5
6
7
8
9
$.when(preloadImage())
.then(animation01)
.then(animation02)
.then(animation03)
.then(transition)
.then(merge)
.then(zoom)
.then(showContent)
.then(flicker);

和Promise/A+的规范有些出入,可能因为实现比较早,但是其意义和原理是差不多的。