前阵子,在nodejs版本更新到v0.10.6(stable),isaacs 宣布不赞成再使用require.extensions,这必然严重影响今后CoffeeScript的发展。所以这个版本一发布,大家就进行了激烈的讨论,有兴趣的读者可以看以下的几篇文章,比较典型,也是现在争论的焦点。

针对这个问题,我觉得是仁者见仁智者见智,大家都有点道理。而我写这篇文章的本意,就是重新梳理下原理,好让大家能有自己的判断。

发展历史

根据wiki的介绍,CoffeeScript的发展如下

“2009年12月3日,Jeremy Ashkenas 在 Git 上对 CoffeeScript 做出了第一笔提交“这是这个神秘语言最初的提交。(initial commit of the mystery language.)”当时的编译器由 Ruby 语言所写成。在同年12月24日,Ashkenas 做出了第一个上标签与归档的版本-0.1.0。2010年2月21日,他释出了 0.5 版,此版本将原先由 Ruby 撰写的编译器改写为纯 CoffeeScript 撰写。当时吸引了 GitHub 上的许多贡献者,每日约有 300 人次造访该专案的页面。2010年12月24日,Askenas 释出了 1.0.0 稳定版本,并在 Hacker News 上发表。Hacker News 也是当时此专案第一次发表的网站”

 原理

在CoffeeScript的主页上就已经清楚地告诉了大家:

“CoffeeScript is a little language that compiles into JavaScript.” 

它是种构建在JavaScript上的新型语言,优点就是增强了 JavaScript 的简洁性与可读性,也新增了更复杂的功能; 缺点也很明显,它的代码最终还是会被编译成JavaScript,所有常给人种“多余”,“还不如直接写javascript来得痛快”的感觉。

现在来具体讲讲, CoffeeScript的代码是如何在Nodejs环境中运行的。

在安装好CoffeeScript之后,安装方法可以参考:http://coffeescript.org/#installation

 我写了helloworld.coffee,代码如下

console.log 'hello world jifeng'

 

将该文件转化为.js文件的指令

 coffee -c helloworld.coffee

通过以上指令,就可以生成helloworld.js,代码如下

// Generated by CoffeeScript 1.4.0
(function() {

  console.log('hello world jifeng');

}).call(this);

通过上面的例子,我们可以看到CoffeeScript就是将.coffee 文件如何快速的编译成.js文件的过程。

 

Nodejs调用CoffeeScript代码 

还是用上面的例子,如果我用nodejs代码,想要调用上面的helloworld.coffee的代码,只需要写index.js

require('coffee-script');
var hello = require('./helloworld.coffee');

它所依赖的就是nodejs的require.extensions的功能,具体代码可以看 coffee-script.js,主要代码就是这块

  loadFile = function(module, filename) {
    ....
    return module._compile(compile(stripped, {
      filename: filename,
      literate: helpers.isLiterate(filename)
    }), filename);
  };

  if (require.extensions) {
    _ref = ['.coffee', '.litcoffee', '.coffee.md'];
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
      ext = _ref[_i];
      require.extensions[ext] = loadFile;
    }
  }

CoffeeScript就是的做法,就是通过nodejs自带的require.extensions扩展功能, 在require '.coffee' 文件时,先将改文件编译成JavaScript代码之后再被加载。

这也就是为什么取消require.extensions会对coffeescript产生这么重大影响的原因。

希望对大家有所帮助。 

 

 

 参考文章:http://zh.wikipedia.org/wiki/CoffeeScript#cite_note-changelog-1

posted @ 2013-05-18 18:48 lengyuhong 阅读(726) 评论(0) 编辑

上周再做一个easyproxy的小项目,实现代码反向代理的工作,具体就是在tcp层对各个请求(只要遵循建立在tcp层之上的协议即可)进行解析,然后分发各个具体服务上。

这中间遇到的一个问题就是HTTP中的长连接问题,重新去看了下具体的http协议,发现之前对这块知识还是存在盲点。

这篇文章可以算是自己的学习笔记, 很多内容更是直接使用我看到和觉得讲得不错的资料,希望对大家也有所帮助。

基础知识

 1. 名称

 维基百科中的介绍:

“HTTP persistent connection, also called HTTP keep-alive, or HTTP connection reuse, is the idea of using a single TCP connection to send and receive multiple HTTP requests/responses, as opposed to opening a new connection for every single request/response pair.”

讲这个,是因为我觉得“长连接”这个称呼并不是非常准确,用“持久连接”,而“Long Connection”这种直接翻译词就更不用再用了。但毕竟这个名字已经沿用这么久了,我们也就继续用这个名

2. 原理

在HTTP协议中长连接中同一个TCP连接来发送和接收多个HTTP请求/应答,而不是为每一个新的请求/应答打开新的连接的方法。

下图是网上一张很形象讲解普通连接和长连接区别的图

从图中,我们就能清晰地看到,两者区别就是长连接会“复用”原先建立TCP连接

3. 实现

在 HTTP 1.0 中

没有官方的 keepalive 的操作。通常是在现有协议上添加一个指数。如果浏览器支持 keep-alive,它会在请求的包头中添加:

Connection: Keep-Alive

然后当服务器收到请求,作出回应的时候,它也添加一个头在响应中:

Connection: Keep-Alive

这样做,连接就不会中断,而是保持连接。当客户端发送另一个请求时,它会使用同一个连接。这一直继续到客户端或服务器端认为会话已经结束,其中一方中断连接。

比如我用chrome访问博客园首页

request header:

 

response header:

在 HTTP 1.1 中 

所有的连接默认都是持续连接,除非特殊声明不支持。

所以现在各个浏览器的高版本基本都是支持长连接。

 

4. 优势

  • 较少的CPU和内存的使用(由于同时打开的连接的减少了)
  • 允许请求和应答的HTTP pipelining
  • 降低网络阻塞 (TCP连接减少了)
  • 减少了后续请求的延迟(无需再进行握手)
  • 报告错误无需关闭TCP连接

 其实它一切的优势就在于复用了原先建立的TCP连接,减少重新建立TCP连接的消耗

5. 劣势

  • 空闲的连接需要过段时间后才能被断开,可能影响整体性能(比如那些单次访问次数多的web 服务)

 

6. 浏览器 

 现在的高版本浏览器都是默认支持长连接的,而它们维护长连接的方式就是用“超时管理”。

 在一段时间内没有请求和相应时,就自动将连接断掉。

 

 

参考文章:

维基百科介绍:英文

Persistent HTTP Connections in RFC 2616 "Hypertext Transfer Protocol -- HTTP/1.1

posted @ 2013-05-12 21:42 lengyuhong 阅读(1277) 评论(2) 编辑

Web开发中的缓存》是自己最近在团队中做的一次技术分享,大致梳理下在web中可能用到的相关缓存内容和自己的一些使用心得。

ppt重在整体的介绍,由于篇幅原因,细节没讲太深。

 

ppt地址:http://wenku.baidu.com/view/132abd0476c66137ee0619e1.html

网页版ppt地址(建议用chrome访问):http://jifeng.github.io/ppt/webcache.html 

posted @ 2013-04-11 19:11 lengyuhong 阅读(1633) 评论(7) 编辑

在html页面中,footer是非常有用,我们用它来标识它的版权和页面的相关信息。比如淘宝指数中的版权信息

它的一个特点就是会“固定”在页面的尾部,并不会随这页面主内容少而跑上面去,当页面内容过多,超过一屏幕时,会紧跟在内容尾部,具体效果如图(来自网络):

那怎么实现呢?其实很简单,只用设置css样式即可。

 

第一种方法

HTML代码

<div id="container">
  <div id="header">Header Section</div>
  <div id="body" class="clearfix">
    页面容容部分
  </div>
  <div id="footer">Footer Section</div>
</div>

CSS代码

html,body {
  margin: 0;
  padding:0;
  height: 100%;
}
#container {
  min-height:100%;
  height: auto !important;
  height: 100%; /*IE6不识别min-height*/
  position: relative;
}
#body {
  width: 960px;
  margin: 0 auto;
  padding-bottom: 60px;/*等于footer的高度*/
}
#footer {
  position: absolute;
  bottom: 0;
  width: 100%;
  height: 60px;/*脚部的高度*/
  clear:both;
}

 

优点:
结构简单清晰,无需js和任何hack能实现各浏览器下的兼容,同时在ipad、iphone下也可以正常运行

缺点:

1. 需要给div#footer容器设置一个固定高度
2. 需要将div#page容器的padding-bottom设置大于等于div#footer的高度

参考: http://matthewjamestaylor.com/blog/keeping-footers-at-the-bottom-of-the-page
DEMO: http://matthewjamestaylor.com/blog/bottom-footer-demo.htm

 

第二种方法

利用footer的margin-top负值来实现footer永远固定在页面的底部效果,原理和效果跟第一种方法是一样的

HTML代码

<div id="container">
  <div id="header">Header Section</div>
  <div id="page">Main Content</div>
</div>  
<div id="footer">footer</div>

 

CSS代码

html,body {
  height: 100%;
  margin: 0;
  padding: 0;
}
#container {
  min-height: 100%;
  height: auto !important;
  height: 100%;
}
#footer {
  position: relative;
  margin-top: -60px;/*等于footer的高度*/
  height: 60px;
  clear:both;
}
#page {
  padding-bottom: 60px;/*高度等于footer的高度*/
}

 

  第三种方法 

HTML代码

<div id="container">
    <div id="page"> Content </div>
    <div class="push"> 空标签</div>
  </div>
<div id="footer">Footer</div>

 CSS代码

html,body{
  height: 100%;
  margin:0;
  padding:0;
}
#container {
  min-height: 100%;
  height: auto !important;
  height: 100%;
  margin: 0 auto -60px;/*margin-bottom的负值等于footer高度*/
}
.push, #footer {
  height: 60px;
  clear:both;
}

 

优点:

简单明了,易于理解,兼容所有浏览器。

缺点:

1.较之前面的两种方法,多使用了一个div.push容器

2.同样此方法限制了footer部分高度,无法达到自适应高度效果。

这种方法并不推荐使用

参考:
http://ryanfait.com/resources/footer-stick-to-bottom-of-page/

 

补充:现在html5中已经增加了footer标签,在适当情况可以善用这个标签

声明:这篇文章并非完全原创,而且根据我同事田超强的分享文章整理所得。

posted @ 2013-04-03 16:15 lengyuhong 阅读(927) 评论(0) 编辑

在nodejs中,process.nextTick()可以说是个绕不开的地方,不然你都不好意思跟别人说你已经懂了nodejs的异步非阻塞特性了。

简介

首先开听听nodejs中对nextTick的解释:

On the next loop around the event loop call this callback. This is not a simple alias to setTimeout(fn, 0), it's much more efficient

可以清楚得看到,nextTick()达到的效果跟setTimeout(fn, 0)是一样,但为什么nextTick()会比setTimeout()更高效,不知道大家有没有想多这样的问题。

回答这个问题之前,大家要搞清楚nodejs中Tick的概念:在libev中的event loop 的每次迭代,在nodejs 中就叫做 “Tick”。javascript是单进程运行的,具体到nodejs中,就是如图运行。

 

如果用nextTick()话,它的执行时间是O(1), 而setTimeout(fn, 0)则是O(n),考虑到篇幅,这里不再做具体的详述,有兴趣的朋友可以看《nodejs 异步之 Timer &Tick; 篇》一文,里面做了具体的解释。

 

应用场景

通过之前的介绍,可以看到nextTick()就是将某个任务放到下一个Tick中执行,简单讲就是延迟执行,那具体什么场景下需要用到这个功能呢?

一. 同步emit时间,保证时序

比如以下这段代码:

var EventEmitter = require('events').EventEmitter;

function StreamLibrary(resourceName) { 
    this.emit('start');
    // read from the file, and for every chunk read, do: 
       this.emit('data', chunkRead); 
}
StreamLibrary.prototype.__proto__ = EventEmitter.prototype;

var stream = new StreamLibrary('fooResource');
stream.on('start', function() {
    console.log('Reading has started');
});

stream.on('data', function(chunk) {
    console.log('Received: ' + chunk);
});

从代码本身来看没什么问题,但事实是:我们可能永远也监听不到"start"事件,因为这个事件在调用构造函数时,已经马上抛出了这个事件。

那解决方案就是让'start'延时执行,具体如下:

function StreamLibrary(resourceName) { 
   var self = this;
   process.nextTick(function() {
     self.emit('start');
});

   // read from the file, and for every chunk read, do: 
       this.emit('data', chunkRead); 
}

 

其实早nodejs的源码也存在大量这样的用法,比如net模块

    require('dns').lookup(host, function(err, ip, addressType) {
      // It's possible we were destroyed while looking this up.
      // XXX it would be great if we could cancel the promise returned by
      // the look up.
      if (!self._connecting) return;

      if (err) {
        // net.createConnection() creates a net.Socket object and
        // immediately calls net.Socket.connect() on it (that's us).
        // There are no event listeners registered yet so defer the
        // error event to the next tick.
        process.nextTick(function() {
          self.emit('error', err);
          self._destroy();
        });
      }

这里的err处理就是出于这样的目的。

具体代码连接:https://github.com/joyent/node/blob/master/lib/net.js#L806

 

二. cpu高密集代码段

由于javascript是单进程的,当一个cpu被一个任务任务跑满时,它是不能执行其他任务的,在web服务中,这点甚至是致命,如果它被一个cpu密集的任务占满,那就不能相应其他的请求了,程序相当于“假死”。

这个时候,将这个任务推迟执行倒不失为一个不错的选择。

比如:

var http = require('http');

function compute() {
   // 执行一个cpu密集的任务
   // ...
   process.nextTick(compute);
}

http.createServer(function(req, res) {
   res.writeHead(200, {'Content-Type': 'text/plain'});
   res.end('Hello World');
}).listen(5000, '127.0.0.1');

compute();

 

但问题又来,我们怎么去界定哪些任务是需要推迟推迟执行,而那些则不需要,有什么量化的标准吗?

对不起,我暂时也没有特别好的方法。执行时间是个很好的判断标准,到底是1毫秒,1秒,还是10秒.......,并有很好的量化标准

所以我的感受是:在你懂得nextTick的原理后,根据自己的业务场景来决定。

 

经典文章:
Understanding process.nextTick()
nextTick and setTimeout
test case about nextTick and setTimeout

posted @ 2013-03-31 16:52 lengyuhong 阅读(557) 评论(0) 编辑
摘要: about朱佳墩淘宝花名:继风web开发工程师,现就职于淘宝数据平台与产品部,居住在人间天堂杭州。主要爱好研究研究NoSQL数据库,nodejs和web前端开发等技术。工程师中的文艺青年,代码,阅读,行走,电影,现在差不多是我现在生活的全部。联系方式微博:继风Jordan豆瓣:冷羽鸿github:jifengmail:wade428@163.com“路走对了,就不怕远”阅读全文
posted @ 2013-03-24 16:52 lengyuhong 阅读(57) 评论(0) 编辑
摘要: 前阵子,接一个用户授权服务时,遇到一个关于cookie的诡异问题,折腾了一天才知道问题出在哪儿,虽然时候才知道这是个小白问题。遇到问题是这样子: 比如访问A地址(比如http://localhost/index,http://localhost/test/index)时需要登陆时会跳转到一个登陆页面,登陆成功后,跳转回原页面,这时将用户信息存入到session中,并通过response的Set-Cookie头信息,设置向对应的cookie值。具体如下:Set-Cookie:sid=Wnse42HlgkYBbLtAEOMRkXcWHSwkr9nRUS0WBFFHA4TCZUga; Max-Ag.阅读全文
posted @ 2013-02-26 20:54 lengyuhong 阅读(1404) 评论(3) 编辑
摘要: 作用域和this绝对是javascript中最容易让人栽跟头的地方之一,闲话少说,先问题个问题?var a = { b: 1, c: function() { alert(this.b.toString()); }}a.c();b = a.cb();思考下,再看后面的答案。是不是觉得是:11公布正确答案1function () { console.log(this.b.toString());}再看下面这段程序:var b;var a = { b: 1, c: function() { alert(b); }}a.c();b = a.cb();正确答案:undef...阅读全文
posted @ 2013-02-04 17:56 lengyuhong 阅读(47) 评论(0) 编辑
摘要: 相信大家对siege还是比较了解的,它是一款比较功能比较强大的压力测试软件。可能大家平常也会用,但直到前阵子,发现自己对它的参数理解还不够深刻,分享出来希望对大家有所帮助。如果我用以下这样的参数去进行压力测试,具体是怎样的?siege -r1000 -c20 "127.0.0.1:1823"是不是“20个并发用户,每个用户重复请求1000次,去压本地的1823端口”。好的,那接下来的问题来了,每个用户的每个请求之间会不会有延时?如果有延时的话,会不会对得到的性能指标有所影响呢?这就我们之前很可能忽视的一个参数:-b, --benchmark BENCHMARK: no de阅读全文
posted @ 2012-08-14 17:00 lengyuhong 阅读(113) 评论(0) 编辑
摘要: 最近在操作MongoDB时,遇到一个比较坑爹的问题,就是将“query”当做一条的记录,它在一些条件下就“失效”。 我使用的MongoDB数据库版本是1.8.3。MongoDB shell version: 2.0.3 具体数据:{ "_id" : ObjectId("4fe955021219143f31b9d2b9"), "query" : 1, "test" : [ 1, 2, 3 ] }{ "_id" : ObjectId("4fe9557b1219143f31b9d2ba&qu阅读全文
posted @ 2012-06-28 20:04 lengyuhong 阅读(971) 评论(2) 编辑