在 Node.js 中看 Javascript 的引用

本文首发于《程序员》杂志 2017 年第 3 期

早期学习 Node.js 的时候 (2011-2012),有挺多是从 PHP 转过来的,当时有部分人对于 Node.js 编辑完代码需要重启一下表示麻烦(PHP不需要这个过程),于是社区里的朋友就开始提倡使用 node-supervisor 这个模块来启动项目,可以编辑完代码之后自动重启。不过相对于 PHP 而言依旧不够方便,因为 Node.js 在重启以后,之前的上下文都丢失了。

虽然可以通过将 session 数据保存在数据库或者缓存中来减少重启过程中的数据丢失,不过如果是在生产的情况下,更新代码的重启间隙是没法处理请求的(PHP可以,另外那个时候 Node.js 还没有 cluster)。由于这方面的问题,加上本人是从 PHP 转到 Node.js 的,于是从那时开始思考,有没有办法可以在不重启的情况下热更新 Node.js 的代码。

最开始把目光瞄向了 require 这个模块。想法很简单,因为 Node.js 中引入一个模块都是通过 require 这个方法加载的。于是就开始思考 require 能不能在更新代码之后再次 require 一下。尝试如下:

a.js

var express = require('express');
var b = require('./b.js');

var app = express();

app.get('/', function (req, res) {
  b = require('./b.js');
  res.send(b.num);
});

app.listen(3000);

b.js

exports.num = 1024;

两个 JS 文件写好之后,从 a.js 启动,刷新页面会输出 b.js 中的 1024,然后修改 b.js 文件中导出的值,例如修改为 2048。再次刷新页面依旧是原本的 1024。

再次执行一次 require 并没有刷新代码。require 在执行的过程中加载完代码之后会把模块导出的数据放在 require.cache 中。require.cache 是一个 { } 对象,以模块的绝对路径为 key,该模块的详细数据为 value。于是便开始做如下尝试:

a.js

var path = require('path');
var express = require('express');
var b = require('./b.js');

var app = express();

app.get('/', function (req, res) {
  if (true) { // 检查文件是否修改
    flush();
  }
  res.send(b.num);
});

function flush() {
  delete require.cache[path.join(__dirname, './b.js')];
  b = require('./b.js');
}

app.listen(3000);

再次 require 之前,将 require 之上关于该模块的 cache 清理掉后,用之前的方法再次测试。结果发现,可以成功的刷新 b.js 的代码,输出新修改的值。

了解到这个点后,就想通过该原理实现一个无重启热更新版本的 node-supervisor。在封装模块的过程中,出于情怀的原因,考虑提供一个类似 PHP 中 include 的函数来代替 require 去引入一个模块。实际内部依旧是使用 require 去加载。以b.js为例,原本的写法改为 var b = include(‘./b’),在文件 b.js 更新之后 include 内部可以自动刷新,让外面拿到最新的代码。

但是实际的开发过程中,这样很快就碰到了问题。我们希望的代码可能是这样:

web.js

var include = require('./include');
var express = require('express');
var b = include('./b.js');
var app = express();

app.get('/', function (req, res) {
  res.send(b.num);
});

app.listen(3000);

但按照这个目标封装include的时候,我们发现了问题。无论我们在include.js内部中如何实现,都不能像开始那样拿到新的 b.num。

对比开始的代码,我们发现问题出在少了 b = xx。也就是说这样写才可以:

web.js

var include = require('./include');
var express = require('express');
var app = express();

app.get('/', function (req, res) {
  var b = include('./b.js');
  res.send(b.num);
});

app.listen(3000);

修改成这样,就可以保证每次能可以正确的刷新到最新的代码,并且不用重启实例了。读者有兴趣的可以研究这个include是怎么实现的,本文就不深入讨论了,因为这个技巧使用度不高,写起起来不是很优雅[1],反而这其中有一个更重要的问题——JavaScript的引用。

JavaScript 的引用与传统引用的区别

要讨论这个问题,我们首先要了解 JavaScript 的引用于其他语言中的一个区别,在 C++ 中引用可以直接修改外部的值:

#include <iostream>

using namespace std;

void test(int &p) // 引用传递
{
    p = 2048;
}

int main()
{
    int a = 1024;
    int &p = a; // 设置引用p指向a

    test(p); // 调用函数

    cout << "p: " << p << endl; // 2048
    cout << "a: " << a << endl; // 2048
    return 0;
}

而在 JavaScript 中:

var obj = { name: 'Alan' };

function test1(obj) {
  obj = { hello: 'world' }; // 试图修改外部obj
}

test1(obj);
console.log(obj); // { name: 'Alan' } // 并没有修改①

function test2(obj) {
  obj.name = 'world'; // 根据该对象修改其上的属性
}

test2(obj);
console.log(obj); // { name: 'world' } // 修改成功②

我们发现与 C++ 不同,根据上面代码 ① 可知 JavaScript 中并没有传递一个引用,而是拷贝了一个新的变量,即值传递。根据 ② 可知拷贝的这个变量是一个可以访问到对象属性的“引用”(与传统的 C++ 的引用不同,下文中提到的 JavaScript 的引用都是这种特别的引用)。这里需要总结一个绕口的结论:Javascript 中均是值传递,对象在传递的过程中是拷贝了一份新的引用

为了理解这个比较拗口的结论,让我们来看一段代码:

var obj = {
  data: {}
};

// data 指向 obj.data
var data = obj.data;

console.log(data === obj.data); // true-->data所操作的就是obj.data

data.name = 'Alan';
data.test = function () {
  console.log('hi')
};

// 通过data可以直接修改到data的值
console.log(obj) // { data: { name: 'Alan', test: [Function] } }

data = {
  name: 'Bob',
  add: function (a, b) {
    return a + b;
  }
};

// data是一个引用,直接赋值给它,只是让这个变量等于另外一个引用,并不会修改到obj本身
console.log(data); // { name: 'Bob', add: [Function] }
console.log(obj); // { data: { name: 'Alan', test: [Function] } }

obj.data = {
  name: 'Bob',
  add: function (a, b) {
    return a + b;
  }
};

// 而通过obj.data才能真正修改到data本身
console.log(obj); // { data: { name: 'Bob', add: [Function] } }

通过这个例子我们可以看到,data 虽然像一个引用一样指向了 obj.data,并且通过 data 可以访问到 obj.data 上的属性。但是由于 JavaScript 值传递的特性直接修改 data = xxx 并不会使得 obj.data = xxx。

打个比方最初设置 var data = obj.data 的时候,内存中的情况大概是:

|   Addr   |  内容  |
|----------|--------
| obj.data |  内存1 |
|   data   |  内存1 |

所以通过 data.xx 可以修改 obj.data 的内存1。

然后设置 data = xxx,由于 data 是拷贝的一个新的值,只是这个值是一个引用(指向内存1)罢了。让它等于另外一个对象就好比:

|   Addr   |  内容  |
|----------|--------
| obj.data |  内存1 |
|   data   |  内存2 |

让 data 指向了新的一块内存2。

如果是传统的引用(如上文中提到的 C++ 的引用),那么 obj.data 本身会变成新的内存2,但 JavaScript 中均是值传递,对象在传递的过程中拷贝了一份新的引用。所以这个新拷贝的变量被改变并不影响原本的对象。

Node.js 中的 module.exports 与 exports

上述例子中的 obj.data 与 data 的关系,就是 Node.js 中的 module.exports 与 exports 之间的关系。让我们来看看 Node.js 中 require 一个文件时的实际结构:

function require(...) {
  var module = { exports: {} };
  ((module, exports) => { // Node.js 中文件外部其实被包了一层自执行的函数
    // 这中间是你模块内部的代码.
    function some_func() {};
    exports = some_func;
    // 这样赋值,exports便不再指向module.exports
    // 而module.exports依旧是{}

    module.exports = some_func;
    // 这样设置才能修改到原本的exports
  })(module, module.exports);
  return module.exports;
}

所以很自然的:

console.log(module.exports === exports); // true
// 所以 exports 所操作的就是 module.exports

Node.js 中的 exports 就是拷贝的一份 module.exports 的引用。通过 exports 可以修改Node.js 当前文件导出的属性,但是不能修改当前模块本身。通过 module.exports 才可以修改到其本身。表现上来说:

exports = 1; // 无效
module.exports = 1; // 有效

这是二者表现上的区别,其他方面用起来都没有差别。所以你现在应该知道写module.exports.xx = xxx; 的人其实是多写了一个module.。

更复杂的例子

为了再练习一下,我们在来看一个比较复杂的例子:

var a = {n: 1};  
var b = a; 
a.x = a = {n: 2};  
console.log(a.x);
console.log(b.x);

按照开始的结论我们可以一步步的来看这个问题:

var a = {n: 1};   // 引用a指向内存1{n:1}
var b = a;        // 引用b => a => { n:1 }

内部结构:

|   Addr  |     内容     |
|---------|-------------|
|    a    |  内存1 {n:1} |
|    b    |  内存1       |

继续往下看:

a.x = a = {n: 2};  //  (内存1 而不是 a ).x = 引用 a = 内存2 {n:2}

a 虽然是引用,但是 JavaScript 是值传的这个引用,所以被修改不影响原本的地方。

|    Addr   |          内容         |
|-----------|-----------------------|
| 1) a	    |  内存2({n:2})         |
| 2) 内存1.x |  内存2({n:2})         |
| 3) b	    |  内存1({n:1, x:内存2}) |

所以最后的结果

  • a.x 即(内存2).x ==> {n: 2}.x ==> undefined
  • b.x 即(内存1).x ==> 内存2 ==> {n: 2}

总结

JavaScrip t中没有引用传递,只有值传递。对象(引用类型)的传递只是拷贝一个新的引用,这个新的引用可以访问原本对象上的属性,但是这个新的引用本身是放在另外一个格子上的值,直接往这个格子赋新的值,并不会影响原本的对象。本文开头所讨论的 Node.js 热更新时碰到的也是这个问题,区别是对象本身改变了,而原本拷贝出来的引用还指向旧的内存,所以通过旧的引用调用不到新的方法。

Node.js 并没有对 JavaScript 施加黑魔法,其中的引用问题依旧是 JavaScript 的内容。如 module.exports 与 exports 这样隐藏了一些细节容易使人误会,本质还是 JavaScript 的问题。另外推荐一个关于 Node.js 的进阶教程 《Node.js 面试》

注[1]:

  1. 老实说,模块在函数内声明有点谭浩强的感觉。
  2. 把 b = include(xxx) 写在调用内部,还可以通过设置成中间件绑定在公共地方来写。
  3. 除了写在调用内部,也可以导出一个工厂函数,每次使用时 b().num 一下调用也可以。
  4. 还可以通过中间件的形式绑定在框架的公用对象上(如:ctx.b = include(xxx))。
  5. 要实现这样的热更新必须在架构上就要严格避免旧代码被引用的可能性,否则很容易写出内存泄漏的代码。

Jade 模板引擎使用

在 Express 中调用 jade 模板引擎

var express = require('express');
var http = require('http');
var app = express();
app.set('view engine', 'jade'); // 设置模板引擎
app.set('views', __dirname);  // 设置模板相对路径(相对当前目录)

app.get('/', function(req, res) {
	res.render('test'); // 调用当前路径下的 test.jade 模板
})

var server = http.createServer(app);
server.listen(3002);
console.log('server started on http://127.0.0.1:3002/');

test.jade

p hello jade

其上的 jade 模板会被解析成

<p>hello jade</p>

虽然平常我们修改 node.js 代码之后需要重启来查看变化,但是 jade 引擎不在此列,因为是动态加载的,所以我们修改完 jade 文件之后可以直接刷新网页来查看效果的。

如果文本比较长可以使用

p
  | foo bar baz
  | rawr rawr

或者

p.
  foo bar baz
  rawr rawr

两种情况都可以处理为

<p>foo bar baz rawr rawr</p>

jade 变量调用

jade 的变量调用有 3 种方式

  1. #{表达式}
  2. =表达式
  3. !=表达式

注意:- 开头在 jade 种属于服务端执行的代码

- console.log('hello'); // 这段代码在服务端执行
- var s = 'hello world' // 在服务端空间中定义变量
p #{s}
p= s

会被渲染成为

<p>hello world</p>
<p>hello world</p>

以下代码效果相同

- var s = 'world'
p hello #{s}
p= 'hello' + s

方式1可以自由的嵌入各个地方
方式2返回的是表达式的值
= 与 != 雷同,据说前者会编码字符串(如 <stdio.h> 变成 &lt;stdio.h&gt;),后者不会,不过博主没试出来不知道什么情况。

除了直接在 jade 模板中定义变量,更常见的应用是在 express 中调用 res.render 方法的时候将变量一起传递进模板的空间中,例如这样:

res.render(test, {
    s: 'hello world'
});

调用模板的时候,在 jade 模板中也可以如上方那样直接调用 s 这个变量

if 判断

方式1

- var user = { description: '我喜欢猫' }
- if (user.description)
    h2 描述
    p.description= user.description
- else
    h1 描述
    p.description 用户无描述

结果:

<div id="user">
  <h2>描述</h2>
  <p class="description">我喜欢猫</p>
</div>

方式2

上述的方式有种省略写法

- var user = { description: '我喜欢猫' }
#user
  if user.description
    h2 描述
    p.description= user.description
  else
    h1 描述
    p.description 用户无描述

方式3

使用 Unless 类似于 if 后的表达式加上了 ! 取反

- var user = { name: 'Alan', isVip: false }
unless user.isVip
  p 亲爱的 #{user.name} 您并不是 VIP

结果

<p>亲爱的 Alan 您并不是 VIP</p>

注意这个 unless 是 jade 提供的关键字,不是运行的 js 代码

循环

for 循环

- var array = [1,2,3]
ul
  - for (var i = 0; i < array.length; ++i) {
    li hello #{array[i]}
  - }

结果

<ul>
	<li>hello 1</li>
	<li>hello 2</li>
	<li>hello 3</li>
</ul>

each

同样的 jade 对于循环液提供了省略 “-” 减号的写法

ul
  each val, index in ['西瓜', '苹果', '梨子']
    li= index + ': ' + val

结果

<ul>
  <li>0: 西瓜</li>
  <li>1: 苹果</li>
  <li>2: 梨子</li>
</ul>

该方法同样适用于 json 数据

ul
  each val, index in {1:'苹果',2:'梨子',3:'乔布斯'}
    li= index + ': ' + val

结果:

<ul>
  <li>1: 苹果</li>
  <li>2: 梨子</li>
  <li>3: 乔布斯</li>
</ul>

Case

类似 switch 里面的结果,不过这里的 case 不支持case 穿透,如果 case 穿透的话会报错。

- var friends = 10
case friends
  when 0
    p you have no friends
  when 1
    p you have a friend
  default
    p you have #{friends} friends

结果:

<p>you have 10 friends</p>

简略写法:

- var friends = 1
case friends
  when 0: p you have no friends
  when 1: p you have a friend
  default: p you have #{friends} friends

结果:

<p>you have a friend</p>

在模板中调用其他语言

:markdown
  # Markdown 标题
  这里使用的是 MarkDown 格式
script
  :coffee
    console.log '这里是 coffee script'

结果:

<h1>Markdown 标题</h1>
<p>这里使用的是 MarkDown 格式</p>
<script>console.log('这里是 coffee script')</script>

可重用的 jade 块 (Mixins)

//- 申明可重用的块
mixin list
  ul
    li foo
    li bar
    li baz

//- 调用
+list()
+list()

结果:

<ul>
  <li>foo</li>
  <li>bar</li>
  <li>baz</li>
</ul>
<ul>
  <li>foo</li>
  <li>bar</li>
  <li>baz</li>
</ul>

你也可以给这个重用块制定参数

mixin pets(pets)
  ul.pets
    - each pet in pets
      li= pet

+pets(['cat', 'dog', 'pig'])

结果:

<ul class="pets">
  <li>cat</li>
  <li>dog</li>
  <li>pig</li>
</ul>

Mixins 同时也支持在外部传入 jade 块

mixin article(title)
  .article
    .article-wrapper
      h1= title
      //- block 为 jade 关键字代表外部传入的块
      if block
        block
      else
        p 该文章没有内容
        
+article('Hello world')

+article('Hello Jade')
  p 这里是外部传入的块
  p 再写两句

结果:

<div class="article">
  <div class="article-wrapper">
    <h1>Hello world</h1>
    <p>该文章没有内容</p>
  </div>
</div>
<div class="article">
  <div class="article-wrapper">
    <h1>Hello Jade</h1>
    <p>这里是外部传入的块</p>
    <p>再写两句</p>
  </div>
</div>

Mixins 同时也可以从外部获取属性。

mixin link(href, name)
  a(class!=attributes.class, href=href)= name
  
+link('/foo', 'foo')(class="btn")

结果:

<a href="/foo" class="btn">foo</a>

模板包含 (Includes)

你可以使用 Includes 在模板中包含其他模板的内容。就像 PHP 里的 include 一样。

index.jade

doctype html
html
  include includes/head
body
  h1 我的网站
  p 欢迎来到我的网站。
  include includes/foot

includes/head.jade

head
  title 我的网站
  script(src='/javascripts/jquery.js')
  script(src='/javascripts/app.js')

includes/foot.jade

#footer
  p Copyright (c) foobar

调用 index.jade 的结果:

<!doctype html>
<html>
  <head>
    <title>我的网站</title>
    <script src='/javascripts/jquery.js'></script>
    <script src='/javascripts/app.js'></script>
  </head>
  <body>
    <h1>我的网站</h1>
    <p>欢迎来到我的网站。</p>
    <div id="footer">
      <p>Copyright (c) foobar</p>
    </div>
  </body>
</html>

模板引用 (Extends)

就绝

layout.jade

doctype html
html
  head
    title 我的网站
    meta(http-equiv="Content-Type",content="text/html; charset=utf-8")
    link(type="text/css",rel="stylesheet",href="/css/style.css")
    script(src="/js/lib/jquery-1.8.0.min.js",type="text/javascript")
  body
    block content

article.jade

//- extends 拓展调用 layout.jade
extends ../layout
block content
  h1 文章列表
  p 习近平忆贾大山 李克强:将启动新核电项目
  p 朴槿惠:"岁月号"船长等人弃船行为等同于杀人
  p 普京疑换肤:眼袋黑眼圈全无 领导人整容疑云
  p 世界最大兔子重45斤长逾1米 1年吃2万元食物

res.render(‘article’) 的结果:

<html>
  <head>
    <title>我的网站</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head>
    <link type="text/css" rel="stylesheet" href="/css/style.css"></head>
    <script src="/js/lib/jquery-1.8.0.min.js" type="text/javascript"></script>
  </head>
  <body>
    <h1>文章列表</h1>
    <p>习近平忆贾大山 李克强:将启动新核电项目</p>
    <p>朴槿惠:"岁月号"船长等人弃船行为等同于杀人</p>
    <p>普京疑换肤:眼袋黑眼圈全无 领导人整容疑云</p>
    <p>世界最大兔子重45斤长逾1米 1年吃2万元食物</p>
  </body>
</html>

javascript 引用传递

在 Javascript 中,我们使用函数并且传递参数来调用函数。但是 Javascript 到底是如果传递你所传递的参数呢?当你开始面向对象的开发时,你可能会陷入有时可以访问你的对象有时又不能访问困惑。

在传递字符串或数字的这样的基本类型变量时,情况是按值传递的。这意味着,在函数中对该变量所做的任何更改都不会影响到外部的传递竟来的变量。让我们来看看下面的例子:


function myfunction(x) {
	// x 等于 4
	x = 5;
	// x 现在是 5
}

var x = 4;
console.log(x); // x 等于 4
myfunction(x);
console.log(x); // x 依然是 4

然而,在传递对象的时候是通过引用传递的。在这种情况下,该对象任何属性在函数中都是可以被改变的,让我们来看看另一个例子:


function myClass() {
	this.value = 5;
}

var obj = new myClass();
console.log(obj.value); // obj.value = 5

function fun(f) {
	f.value = 6;
}

fun(obj); // 函数中改变对象的值
console.log(obj.value); // obj.value 现在等于 6

所以,当你传递对象的时候会发生什么?大多数人会觉得(或者至少我觉得),它会通过引用传递可以访问它属性的方法。不幸的是,事实并非如此。看看这个例子:


function myClass() {
	this.value = 5;
}

myClass.prototype.add = function() {
	this.value++;
}

var o = new myClass();
console.log(o.value); // o.value = 5

o.add();
console.log(o.value); // o.value = 6

function fun(f) {
	f(); // 运行传递进来的函数
}

fun(o.add); // 调用对象的函数
console.log(o.value); // sorry, 依旧是 6

这里的问题是使用“this” 关键字。这是一个指向当前对象的上下文便捷引用。虽然我们将函数作为参数传递了进去,但是我们并没有把当前对象(当前的“this”)也一同传递进去,所以执行改函数的时候对象上下文丢失。更准确地说,this现在指向新函数调用的上下文而不是我们刚刚传递进来的对象的函数的上下文。对于一个独立的函数二样, this 可能是 window 对象的上下文,对于时间调用的函数二样, this 可能是一个 event object。简单的证明示例:

// node.js 默认全局变量是 global
if (typeof window !== 'undefined') {
	// 客户端 js 默认是 window
	global = window;
}

function fun() {
	console.log('this === global: ' + (this === global)); // true
}

fun();

function myClass() {

}

myClass.prototype.test = function() {
	console.log('this === global: ' + (this === global));
	console.log('this instanceof myClass: ' + (this instanceof myClass));
};

var c = new myClass();
c.test();

结果

this === global: true
this === global: false
this instanceof myClass: true

解决方案:

1.当你知道方法名的时候


function objectchanger(obj) {
	obj.add(); // 将对象传递进来,通过对象调用函数
}
objectchanger(o);
alert(o.value); // 值为 7

2.当你不知道方法名的时候


function objectchanger(fnc, obj) {
	fnc.call(obj); // 通过 call 来调用函数,并指定其 this 指向。
}
objectchanger(o.add, o);
alert(o.value); // 值为 7

英文原文:http://snook.ca/archives/javascript/javascript_pass

很久以前看的文章存在草稿箱今天顺便拿出来翻译了。其实有挺多想总结的但是一下子憋不出来,就不多引申了。

为什么我要使用 Node.js? 案例逐一介绍

介绍

JavaScript’s rising popularity has brought with it a lot of changes, and the face of web development today is dramatically different. The things that we can do on the web nowadays with JavaScript running on the server, as well as in the browser, were hard to imagine just several years ago, or were encapsulated within sandboxed environments like Flash or Java Applets.

JavaScript 高涨的人气带来了很多的变化,以至于如今使用其进行网络开发的形式也变得截然不同了。就如同在浏览器中一样,现在的我们也可以在服务器上运行 JavaScript ,从前端跨越到后端,这样巨大的反差让人难以想象,因为仅仅在几年前 Javascript 还是如同 Flash 或者 Java applet 那样嵌入网页在沙箱环境中运行的小程序。

Before digging into Node.js, you might want to read up on the benefits of using JavaScript across the stack which unifies the language and data format (JSON), allowing you to optimally reuse developer resources. As this is more a benefit of JavaScript than Node.js specifically, we won’t discuss it much here. But it’s a key advantage to incorporating Node in your stack.

在深入Node.js之前,你可能需要阅读和了解,整个开发流程从客户端到服务端再到数据库的《JavaScript一条龙》,使用 Javascript 可以让您以最佳重的方式复用开发资源。由于这更多的是关于 JavaScript 的特点,这里就不过多讨论它。但它确实是一个让人在开发环节中使用 Node 的关键的优点。

As Wikipedia states: “Node.js is a packaged compilation of Google’s V8 JavaScript engine, the libuv platform abstraction layer, and a core library, which is itself primarily written in JavaScript.” Beyond that, it’s worth noting that Ryan Dahl, the creator of Node.js, was aiming to create real-time websites with push capability, “inspired by applications like Gmail”. In Node.js, he gave developers a tool for working in the non-blocking, event-driven I/O paradigm.

正如维基百科 所说:“Node.js 是谷歌 V8 引擎、libuv 以及使用 Javscript 编写的核心库三者集合的一个包装外壳。” 除此之外,值得注意的是,Node.js 的作者瑞恩·达尔 (Ryan Dahl) 的目标是创建具有实时推送能力的网站。在 Node.js 中,他给了开发者一个使用事件驱动来实现异步开发的优秀解决方案。(注:V8是谷歌开发的,目前公认最快的 Javascript 解析引擎,libuv 是一个开源的、为 Node 定制而生的跨平台的异步 IO 库。)

In one sentence: Node.js shines in real-time web applications employing push technology over websockets. What is so revolutionary about that? Well, after over 20 years of stateless-web based on the stateless request-response paradigm, we finally have web applications with real-time, two-way connections, where both the client and server can initiate communication, allowing them to exchange data freely. This is in stark contrast to the typical web response paradigm, where the client always initiates communication. Additionally, it’s all based on the open web stack (HTML, CSS and JS) running over the standard port 80.

简单的说:Node.js 在实时的 Web应用上采用着优于 WebSocket 的推送技术。这意味着什么样的革命性?Well,经过了20多年的短连接 (stateless request-response) 交互,最终出现了能让我们的客户端和服务器可以互相发起通信,使其能够自由地交换数据的 web 应用。与此形成鲜明对比的是传统的 web响应模式,客户端总是主动链接而服务端被动返回。

One might argue that we’ve had this for years in the form of Flash and Java Applets—but in reality, those were just sandboxed environments using the web as a transport protocol to be delivered to the client. Plus, they were run in isolation and often operated over non-standard ports, which may have required extra permissions and such.

可能有人会说,我们已经使用 Flash 和 Java Applet 的形式很多年了 ———— 但实际上,这些方式只是使用网络将数据传递到客户端上的沙箱环境。他们都是隔离运行的,而且经常操作到需要额外的权限之类的非标准端口。

With all of its advantages, Node.js now plays a critical role in the technology stack of many high-profile companies who depend on its unique benefits.

凭借其独特的优势,Node.js的现在已经在许多著名公司的产品中起到了关键作用。

In this post, I’ll discuss not only how these advantages are accomplished, but also why you might want to use Node.js—and why not—using some of the classic web application models as examples.

在这篇文章中,我们不仅将讨论这些优势是如何实现的,而且也会讨论为什么你使用 Node.js 来替代一些经典的Web应用程序模型。

Node.js 是如何工作的?

The main idea of Node.js: use non-blocking, event-driven I/O to remain lightweight and efficient in the face of data-intensive real-time applications that run across distributed devices.

Node.js 的主要思路是:使用非阻塞的,事件驱动的 I/O 操作来保持在处理跨平台 (across distributed devices) 数据密集型实时应用时的轻巧高效。这听起来有点绕口。

What it really means is that Node.js is not a silver-bullet new platform that will dominate the web development world. Instead, it’s a platform that fills a particular need. And understanding this is absolutely essential. You definitely don’t want to use Node.js for CPU-intensive operations; in fact, using it for heavy computation will annul nearly all of its advantages. Where Node really shines is in building fast, scalable network applications, as it’s capable of handling a huge number of simultaneous connections with high throughput, which equates to high scalability.

它的真正含义是,Node.js 不是一个即将主导Web开发的世界的平台。相反,它是一个满足特别需求的平台。你肯定不会希望使用 Node.js 去做 CPU密集型操作。事实上,使用它进行繁重的计算等于摒弃 Node 几乎所有的优点。Node 真正的亮点在于建设速度快,扩展性高————因为它能够处理庞大的并且高吞吐量的并发连接。

How it works under-the-hood is pretty interesting. Compared to traditional web-serving techniques where each connection (request) spawns a new thread, taking up system RAM and eventually maxing-out at the amount of RAM available, Node.js operates on a single-thread, using non-blocking I/O calls, allowing it to support support tens of thousands of concurrent connections (held in the event loop).

它的工作原理是相当有趣的。传统的网络服务技术,是每个新增一个连接(请求)便生成一个新的线程,这个新的现成会占用系统内存,直到内存满了无法处理。而 Node.js 仅仅只运行在一个单线程中,使用非阻塞的异步 I/O 调用,所有连接都由该线程处理,在 libuv 的加分下,可以允许其支持数万并发连接(全部挂在该线程的事件循环中)。

A quick calculation: assuming that each thread potentially has an accompanying 2 MB of memory with it, running on a system with 8 GB of RAM puts us at a theoretical maximum of 4000 concurrent connections, plus the cost of context-switching between threads. That’s the scenario you typically deal with in traditional web-serving techniques. By avoiding all that, Node.js achieves scalability levels of over 1M concurrent connections (as a proof-of-concept).

做一个简单的计算: 假设是普通的Web程序,新接一个连接占用 2M 的内存,在有 8GB RAM的系统上运行时, 算上线程之间上下文切换的成本,并发连接的最大理论值则为 4000 个。这是在传统 Web服务端技术下的处理情况。而 Node.js 则达到了约 1M 一个并发连接的拓展级别 (相关证明).

There is, of course, the question of sharing a single thread between all clients requests, and it is a potential pitfall of writing Node.js applications. Firstly, heavy computation could choke up Node’s single thread and cause problems for all clients (more on this later) as incoming requests would be blocked until said computation was completed. Secondly, developers need to be really careful not to allow an exception bubbling up to the core (topmost) Node.js event loop, which will cause the Node.js instance to terminate (effectively crashing the program).

当然, 在所有客户端的请求共享单一线程也会有问题, 这也是一个编写 Node.js 应用的潜在缺陷. 首先, 大量的计算可能会使得 Node 的单线程暂时失去反应, 并导致所有的其他客户端的请求一直阻塞, 直到计算结束才恢复正常。 其次,开发人员需要非常小心,不要让一个 Exception 阻塞核心的事件循环,因为这将导致 Node.js 实例的终止(实际上就是程序崩溃)。( 笔者注:如 PHP 中某个页面挂掉是不会影响网站运行的,但是 Nodejs 是一个线程一个线程来处理所有的链接,所以不论是计算卡了或者是被异常阻塞了都可能会影响到其他所有的链接。解决方案在稍后讨论。)

The technique used to avoid exceptions bubbling up to the surface is passing errors back to the caller as callback parameters (instead of throwing them, like in other environments). Even if some unhandled exception manages to bubble up, there are mutiple paradigms and tools available to monitor the Node process and perform the necessary recovery of a crashed instance (although you won’t be able to recover users’ sessions), the most common being the Forever module, or a different approach with external system tools upstart and monit.

用来避免异常抛出时阻塞进程的方法是将异常使用回调传递出去(而不是抛出他们,就像在其他环境中一样)。即使一些未处理的异常阻塞了程序,依旧有多种应对的解决方案,而且也有很多可用于监视 Node 进程来执行一个崩溃的还原的工具(虽然你将无法恢复用户的 Session ),最常见的是使用 Forever 模块,或者采用其他的系统工具如 “upstart and monit”。

NPM: The Node Package Manager

When discussing Node.js, one thing that definitely should not be omitted is built-in support for package management using the NPM tool that comes by default with every Node.js installation. The idea of NPM modules is quite similar to that of Ruby Gems: a set of publicly available, reusable components, available through easy installation via an online repository, with version and dependency management.

当我们讨论 Node.js 的时候,一个绝对不应该忽略地方就是默认内置的模块管理工具 ———— NPM。 其灵感来源与 Ruby Gems(具有版本和依赖管理功能,可以通过在线资料库便捷安装可重用的组件的管理工具)。

A full list of packaged modules can be found on the NPM website https://npmjs.org/ , or accessed using the NPM CLI tool that automatically gets installed with Node.js. The module ecosystem is open to all, and anyone can publish their own module that will be listed in the NPM repository. A brief introduction to NPM (a bit old, but still valid) can be found at http://howtonode.org/introduction-to-npm.

一个完整的公用模块列表可以在 NPM 的网站上找到(https:://npmjs.org/),或者通过使用与 Node.js 一同安装的 NPM CLI 工具也同样可以找到。该模块的生态系统向所有人开放,任何人都可以发布自己的模块,所有的模块都可以在 NPM 资料库中找到。你可以在 http://howtonode.org/introduction-to-npm 页面找到 NPM 的一个简要介绍(有点旧,但依旧能看)。

目前非常流行的一些 NPM 模块有:

  • express – Express.js,是一个简洁而灵活的 node.js Web应用框架, 并且已经是现在大多数 Node.js 应用的标准框架,你已经可以在很多 Node.js 的书籍中看到它了。
  • connect – Connect 是一个 Node.js 的 HTTP 服务拓展框架,提供一个高性能的“插件”集合,以中间件闻名,是 Express 的基础部分之一。
  • socket.iosockjs – 目前服务端最流行的两个 websocket 组件。
  • Jade – 流行的模板引擎之一,并且是 Express.js 的默认模板引擎。其灵感来源于 HAML。
  • mongomongojs – 封装了 MongoDB 的的各种 API,不过笔者平常工作用的是 mongoose 也很推荐。
  • redis – Redis 的客户端函数库.
  • coffee-script – CoffeeScript 编译器,允许开发者使用 Coffee 来编写他们的 Node.js 程序。
  • underscore (lodash, lazy) – 最流行的 JavaScript 工具库 , 用于 Node.js 的封装包,以及两个采取略有不同的实现方法来获得更好性能的同行。
  • forever – 可能是用来确保 node 脚本持续运行的最流行的工具。

The list goes on. There are tons of really useful packages out there, available to all (no offense to those that I’ve omitted here).
还有很多好的模块,这里就不一一列举了(希望没有冒犯到我没列举的)。

Node.js 应该用在什么地方

聊天

Chat is the most typical real-time, multi-user application. From IRC (back in the day), through many proprietary and open protocols running on non-standard ports, to the ability to implement everything today in Node.js with websockets running over the standard port 80.

聊天是最典型的多用户实时交互的应用。从 IRC 开始,有许多开源或者不开源的协议都运行在非标准端口上,而现在,使用 Node.js 则可以解决这些问题 ———— 在标准的80端口运行 WebSockets。

The chat application is really the sweet-spot example for Node.js: it’s a lightweight, high traffic, data-intensive (but low processing/computation) application that runs across distributed devices. It’s also a great use-case for learning too, as it’s simple, yet it covers most of the paradigms you’ll ever use in a typical Node.js application.

聊天应用程序是最能体现 Node.js 优点的例子:轻量级、高流量并且能良好的应对跨平台设备上运行密集型数据(虽然计算能力低)。同时,聊天也是一个非常值得学习的用例,因为它很简单,并且涵盖了目前为止一个典型的 Node.js 会用到的大部分解决方案。

Let’s try to depict how it works.

让我们试着来描绘它如何工作。

In the simplest scenario, we have a single chatroom on our website where people come and can exchange messages in one-to-many (actually all) fashion. For instance, say we have three people on the website all connected to our message board.

在最简单的情况下,我们布置了一个聊天室在我们的网站上,用户可以在上面发消息,当然是一对多的形式。例如,假设总共有三个人连接到我们的网站上。

On the server-side, we have a simple Express.js application which implements two things: 1) a GET ‘/’ request handler which serves the webpage containing both a message board and a ‘Send’ button to initialize new message input, and 2) a websockets server that listens for new messages emitted by websocket clients.

在服务端这边, 我们有一个使用 Express.js 搭建的简单站点,该站点实现了两件事 1) 处理路径为 ‘/’ 的GET请求时,下发包括一个留言板以及一个发送信息的 ‘发送’ 按钮的页面 2) 一个监听客户端发送新消息的 websockets 服务。

On the client-side, we have an HTML page with a couple of handlers set up, one for the ‘Send’ button click event, which picks up the input message and sends it down the websocket, and another that listens for new incoming messages on the websockets client (i.e., messages sent by other users, which the server now wants the client to display).

在客户端这边,我们有一个 HTML 页面,上面有个两个 js 方法,一个是用于触发事件的 “发送” 按钮,这会把把输入的消息通过 webscoket 发送,另一个方法是用 webscoket 在客户端上监听服务端来的推送(例如,其他用户发送的消息)。

When one of the clients posts a message, here’s what happens:

当有一个客户端发送消息的时候,发生的事情是:

1.Browser catches the ‘Send’ button click through a JavaScript handler, picks up the value from the input field (i.e., the message text), and emits a websocket message using the websocket client connected to our server (initialized on web page initialization).

浏览器上,点击发送按钮触发了 js 函数,将输入框中的文字通过 websocket 消息发送到服务器的 websocket 客户端(页面初始化加载的时候连接的)。

2.Server-side component of the websocket connection receives the message and forwards it to all other connected clients using the broadcast method.

服务端的 websocket 组件收到 消息,然后通过广播方法转发到其他所有连接的客户端。

3.All clients receive the new message as a push message via a websockets client-side component running within the web page. They then pick up the message content and update the web page in-place by appending the new message to the board.

通过页面上运行的 websocket 客户端组件,所有的客户端都能收到这条推送的新消息。接着 js 处理函数可以把这个消息添加到文字框内。

This is the simplest example. For a more robust solution, you might use a simple cache based on the Redis store. Or in an even more advanced solution, a message queue to handle the routing of messages to clients and a more robust delivery mechanism which may cover for temporary connection losses or storing messages for registered clients while they’re offline. But regardless of the improvements that you make, Node.js will still be operating under the same basic principles: reacting to events, handling many concurrent connections, and maintaining fluidity in the user experience.

这是一个最简单的例子。如果要更好的解决方案,你可以使用 Redis 数据库做一个简单的缓存。在一个高级的解决方案中,你可能需要一个消息路由来专门处理消息队列,并且需要一个更好的发送机制,比如发送的时候覆盖上暂时离线的用户或者为离线的注册用户存储尚未接收的消息等等。但是不论你坐了怎么样的改进,Node.js 都将遵循一个基本原则:响应事件,处理多个并发连接,并保持流动性的用户体验。

API ON TOP OF AN OBJECT DB

Although Node.js really shines with real-time applications, it’s quite a natural fit for exposing the data from object DBs (e.g. MongoDB). JSON stored data allow Node.js to function without the impedance mismatch and data conversion.

尽管,Node.js 确实非常擅长实时交互的应用,同事它也十分适合通过对象数据库(object DB)来查询数据(如 MongoDB)。以 Json 格式存储的数据允许 Node.js 直接处理,不需要纠结数据转换和匹配的问题。

For instance, if you’re using Rails, you would convert from JSON to binary models, then expose them back as JSON over the HTTP when the data is consumed by Backbone.js, Angular.js, etc., or even plain jQuery AJAX calls. With Node.js, you can simply expose your JSON objects with a REST API for the client to consume. Additionally, you don’t need to worry about converting between JSON and whatever else when reading or writing from your database (if you’re using MongoDB). In sum, you can avoid the need for multiple conversions by using a uniform data serialization format across the client, server, and database.

举个例子,如果你正在使用 Rails,你会将 JSON 数据转成 二进制的 model,当数据再被 Backbone.js, Angular.js 或者 jQuery AJAX 之类的调用又要转回 JSON。如果是 Nodejs 的话,你可以通过一个 REST API 简单的导出 JSON 对象以供客户端使用。另外,从数据库读写时候如果使用的是 MongoDB 的话,你也不用担心的 JSON 与任何数据之间的格式问题。总之,你可以避免多元的数据转换问题,不论是在客户端、服务端还是数据库。

QUEUED INPUTS

队列输入

If you’re receiving a high amount of concurrent data, your database can become a bottleneck. As depicted above, Node.js can easily handle the concurrent connections themselves. But because database access is a blocking operation (in this case), we run into trouble. The solution is to acknowledge the client’s behavior before the data is truly written to the database.

如果你正在接收一个高量并发的数据,你的数据库可能会成为你处理的瓶颈。正如上面的描述,Node.js 可以轻松的处理并发连接。 但是,由于数据库操作是一个阻塞的操作(在这种情况下),这就是麻烦的地方。Node.js的解决方案是,在数据真正的写入之前就承认客户端的数据是真实的。

With that approach, the system maintains its responsiveness under a heavy load, which is particularly useful when the client doesn’t need firm confirmation of a the successful data write. Typical examples include: the logging or writing of user-tracking data, processed in batches and not used until a later time; as well as operations that don’t need to be reflected instantly (like updating a ‘Likes’ count on Facebook) where eventual consistency (so often used in NoSQL world) is acceptable.

用这种方法,在高负载的时候系统继续维持它的响应,这在当客户端不需要严格确认一个数据是否成功的被写入时特别有用。典型的例子包括:用户跟踪数据(user-tracking data)的日志与写入,这会被分批处理并且在稍后才使用;同时也包括最终一致性(so, 常用于 NoSQL)可以接受的时候,不需要立即反应的操作(例如 Facebook 上更新点赞的数目)。

Data gets queued through some kind of caching or message queuing infrastructure (e.g., RabbitMQ, ZeroMQ) and digested by a separate database batch-write process, or computation intensive processing backend services, written in a better performing platform for such tasks. Similar behavior can be implemented with other languages/frameworks, but not on the same hardware, with the same high, maintained throughput.

数据通过某些缓存或者消息队列的基础设置(例如 RabbitMQ, ZeroMQ)进入队列,并且通过一个单独数据库的批量写入进程来一一消化,或者通过一个更好的后端服务来进行密集型计算处理。其他的语言/框架也可以实现相似的操作,但在相同的配置下是达不到 nodejs 的高吞吐量与高并发。

In short: with Node, you can push the database writes off to the side and deal with them later, proceeding as if they succeeded.

简单的说:使用 Node,你可以把数据库操作扔到一边并在稍后处理它们,假设他们成功了一样继续执行下去。(笔者注:在开发中通常的情况通常是,种耗时的操作通过回调函数来异步处理,主线程继续往下执行)

DATA STREAMING

数据流

In more traditional web platforms, HTTP requests and responses are treated like isolated event; in fact, they’re actually streams. This observation can be utilized in Node.js to build some cool features. For example, it’s possible to process files while they’re still being uploaded, as the data comes in through a stream and we can process it in an online fashion. This could be done for real-time audio or video encoding, and proxying between different data sources (see next section).

在较为传统的网络平台上,HTTP 的请求和响应更像是孤立的事件;然后事实上,他们都是数据流。这一观察结果在 Nodejs 上可以用来建立一些很酷的功能。因为数据通以流的形式接收,而我们可以在网站上在线处理正在上传中的文件。这样的话,就可以实现实时的音频和视频编码,以及在不同数据源之间进行代码(代理见下一段)。

(注:Node 有代替如 apache 这样的 webserver 处理数据,所以开发者可以直接收到客户端一份一份上传的数据,并实时处理。上面这段话听起来有点抽象,不过各位可以简单的想象一下不需要开 YY 或者 QQ,打开网页就能进行语音视频的功能。)

PROXY

代理

Node.js is easily employed as a server-side proxy where it can handle a large amount of simultaneous connections in a non-blocking manner. It’s especially useful for proxying different services with different response times, or collecting data from multiple source points.

Node.js 可以通过异步的方式处理大量的同时连接,所以很容易作为服务端的代理来使用。这在与不同响应时间的不同服务之间进行代理,或者是收集来自多个来源的数据时尤其有用。

An example: consider a server-side application communicating with third-party resources, pulling in data from different sources, or storing assets like images and videos to third-party cloud services.

举个例子:考虑一个服务器端的应用程序和第三方资源进行通信以更新自不同来源的数据,或者将服务端上的一些图像和视频资源存储到第三方云服务。

Although dedicated proxy servers do exist, using Node instead might be helpful if your proxying infrastructure is non-existent or if you need a solution for local development. By this, I mean that you could build a client-side app with a Node.js development server for assets and proxying/stubbing API requests, while in production you’d handle such interactions with a dedicated proxy service (nginx, HAProxy, etc.).

虽然专用代理服务器确实存在,但是如果你还没有专用的代理服务器,或者你需要一个本地开发的解决方案,那么使用 Node 来做代理可能是更好的选择。关于这个解决方案,我的意思是指你当你在作业的时候,需要与专用代理服务进行交互,可以通过 Node.js 开进行处理。

BROKERAGE – STOCK TRADER’S DASHBOARD

股票操盘手的仪表盘

Let’s get back to the application level. Another example where desktop software dominates, but could be easily replaced with a real-time web solution is brokers’ trading software, used to track stocks prices, perform calculations/technical analysis, and create graphs/charts.

让我们继续讨论应用程序这块。实时网络的解决方案可以很轻松的实现证券交易软件————用于跟踪股票的价格,执行计算、做技术分析,同时生成报表。(省略了)

Switching to a real-time web-based solution would allow brokers to easily switch workstations or working places. Soon, we might start seeing them on the beach in Florida.. or Ibiza.. or Bali.

使用一个实时的的基于网页的解决方案,将会允许炒股的人轻松的切换工作软件以及工作地点。相信不久,我们就可以看到这个东西出现在 Florida 或者 Ibiza 又或者是 Bali 上面。

APPLICATION MONITORING DASHBOARD

Another common use-case in which Node-with-web-sockets fits perfectly: tracking website visitors and visualizing their interactions in real-time. (If you’re interested, this idea is already being productized by Hummingbird.)

另一种常见的用例中,使用 Node+Web+Socket 非常适合:跟踪网站访问者并且可视化实时它们之间的实时交互。 (如果你有兴趣,可以去看看 Hummingbird

You could be gathering real-time stats from your user, or even moving it to the next level by introducing targeted interactions with your visitors by opening a communication channel when they reach a specific point in your funnel. (If you’re interested, this idea is already being productized by CANDDi.)

你可能需要采集用户的实时状态, 或者甚至当他们到达渠道中某个特定的点时, 打开一个交流频道, 通过有针对性的互动介绍移动到下一个阶段. (如果你感兴趣的话,推荐你看看 CANDDi

Imagine how you could improve your business if you knew what your visitors were doing in real-time—if you could visualize their interactions. With the real-time, two-way sockets of Node.js, now you can.

想象一下,如果你知道你的访客的实时操作,如果你能想象它们之间的相互作用。随着实时的、双向 socket 通信的 Node.js 现在你也可以做到了。

SYSTEM MONITORING DASHBOARD

系统监控仪表

Now, let’s visit the infrastructure side of things. Imagine, for example, an SaaS provider that wants to offer its users a service-monitoring page (e.g., GitHub’s status page). With the Node.js event-loop, we can create a powerful web-based dashboard that checks the services’ statuses in an asynchronous manner and pushes data to clients using websockets.

现在,让我们看看事情的基础设施方面。想象一下,比如,希望为其用户提供服务监控页面(例如,GitHub上的状态页)的 SaaS 运营商 。通过 Node.js 的事件循环,我们可以创建一个基于 Web 的功能强大的仪表板,以异步方式检查服务状态并且使用的 WebSockets 将数据推送到客户端。

Both internal (intra-company) and public services’ statuses can be reported live and in real-time using this technology. Push that idea a little further and try to imagine a Network Operations Center (NOC) monitoring applications in a telecommunications operator, cloud/network/hosting provider, or some financial institution, all run on the open web stack backed by Node.js and websockets instead of Java and/or Java Applets.

内部(公司内部)和公共服务的状态都可以使用该项技术实时的上报。让我们把这一想法延伸的远一点,试着想象一个电信运营商中网络运营中心(NOC)的监控应用。云/网络/服务器运营商,或者一些金融机构,全都运行在这个由 Node.js 和 WebSocket 组成的应用上,而不是 Java 和/或 Java Applet。

Note: Don’t try to build hard real-time systems in Node (i.e., systems requiring consistent response times). Erlang is probably a better choice for that class of application.

注意:不要尝试使用 Node 打造硬实时系统(即,响应时间要求一致的系统)。 Erlang是可能是该类应用程序的更好的选择。

什么地方可以使用 Node.js

服务端 WEB 应用

Node.js with Express.js can also be used to create classic web applications on the server-side. However, while possible, this request-response paradigm in which Node.js would be carrying around rendered HTML is not the most typical use-case. There are arguments to be made for and against this approach. Here are some facts to consider:

通过 Node.js 使用 Express.js 也可以用来创建服务端上的典型的网页应用。然而,虽然有可能,使用 Node.js 来进行请求+响应的形式来呈现 HTML 并不是最典型的用例。有人赞成也有人反对这一做法。这里有一些看法以供参考:

优点:

If your application doesn’t have any CPU intensive computation, you can build it in Javascript top-to-bottom, even down to the database level if you use JSON storage Object DB like MongoDB. This eases development (including hiring) significantly.

如果你不需要进行 CPU密集型计算,你可以从头到尾甚至是数据库(比如 MongoDB)都使用 Javascript 来开发。这显著地减轻了开发工序(包括成本)。

Crawlers receive a fully-rendered HTML response, which is far more SEO-friendly than, say, a Single Page Application or a websockets app run on top of Node.js.

对于一个使用 Node.js 作为服务端的单页应用或者 websocket 应用,爬虫可以收到一个完全 HTML 呈现的响应,这是更为SEO友好的。

缺点:

Any CPU intensive computation will block Node.js responsiveness, so a threaded platform is a better approach. Alternatively, you could try scaling out the computation [*].

任何CPU密集型的计算都将阻碍 Node.js 的反应,所以使用多线程的平台是一个更好的方法。或者,您也可以尝试向外扩展的计算[*]。

Using Node.js with a relational database is still quite a pain (see below for more detail). Do yourself a favour and pick up any other environment like Rails, Django, or ASP.Net MVC if you’re trying to perform relational operations.

Node.js 使用关系型数据库依旧十分痛苦(详细见下方)。拜托了,如果你想执行关系型数据操作,请考虑别的环境:Rails, Django 甚至 ASP.NET MVC 。。。。

[*] An alternative to these CPU intensive computations is to create a highly scalable MQ-backed environment with back-end processing to keep Node as a front-facing ‘clerk’ to handle client requests asynchronously.

【*】另一种解决方案是,为这些CPU密集型的计算建立一个高度可扩展的MQ支持的环境与后端处理,以保持 Node 作为一个前台秘书来异步处理客户端请求。

Node.js 不应该在什么地方使用

使用关系型数据库的服务端 WEB 应用

Comparing Node.js with Express.js against Ruby on Rails, for example, there is a clean decision in favour of the latter when it comes to relational data access.

对比 Node.js 上的 Express.js 和 Ruby on Rails,当你使用关系型数据库的时候请毫不犹豫的选择后者。

Relational DB tools for Node.js are still in their early stages; they’re rather immature and not as pleasant to work with. On the other hand, Rails automagically provides data access setup right out of the box together with DB schema migrations support tools and other Gems (pun intended). Rails and its peer frameworks have mature and proven Active Record or Data Mapper data access layer implementations, which you’ll sorely miss if you try to replicate them in pure JavaScript.[*]

Node.js 的关系数据库工具仍处于早期阶段,目前还不成熟。另一方面来说,这个没用 rails 没太清楚这里说的是哪个点。

Still, if you’re really inclined to remain JS all-the-way (and ready to pull out some of your hair), keep an eye on Sequelize and Node ORM2—both are still immature, but they may eventually catch up.

不过,如果你真的倾向于全部使用 JS(并且做好可能抓狂的准备),那么请继续关注 Sequelize 和 Node ORM2 ,虽然这两者仍然不成熟的,但他们最终会迎头赶上。

[*] It’s possible and not uncommon to use Node solely as a front-end, while keeping your Rails back-end and its easy-access to a relational DB.

[*] 使用 Node 光是作为前端而 Rails 做后端来连接关系型数据库,这是完全有可能也并不少见的。(笔者注:国外有种说法,PHP这一类程序员也可以算作是前端)

HEAVY SERVER-SIDE COMPUTATION/PROCESSING

繁重的服务端的计算和处理

When it comes to heavy computation, Node.js is not the best platform around. No, you definitely don’t want to build a Fibonacci computation server in Node.js. In general, any CPU intensive operation annuls all the throughput benefits Node offers with its event-driven, non-blocking I/O model because any incoming requests will be blocked while the thread is occupied with your number-crunching.

当涉及到大量的计算,Node.js 就不是最佳的解决方案。你肯定不希望使用 Node.js 建立一个斐波那契数的计算服务。一般情况下,任何 CPU密集型操作 会削弱掉 Node通过事件驱动, 异步 I/O 模型等等 带来的吞吐量好处,因为当线程被非异步的高计算量占用时任何传入的请求将被阻塞。

As stated previously, Node.js is single-threaded and uses only a single CPU core. When it comes to adding concurrency on a multi-core server, there is some work being done by the Node core team in the form of a cluster module [ref: http://nodejs.org/api/cluster.html%5D. You can also run several Node.js server instances pretty easily behind a reverse proxy via nginx.

正如前面所说,Node.js 是单线程的,只使用一个单一的CPU核心。至于,涉及到服务器上多核并发处理,Node 的核心团队已经使用 cluster 模块的形式在这一方面做了一些工作 [参考:http://nodejs.org/api/cluster.html]。当然,您也可以很容易的通过 nginx 的反向代理运行多个 Node.js 的服务器实例来避免单一线程阻塞的问题。

With clustering, you should still offload all heavy computation to background processes written in a more appropriate environment for that, and having them communicate via a message queue server like RabbitMQ.

关于集群(clustering) ,你应该将所有繁重的计算转移到更合适的语言写的后台进程来处理,同时让他们通过像 RabbitMQ 那样通过消息队列服务器来进行通信。

Even though your background processing might be run on the same server initially, such an approach has the potential for very high scalability. Those background processing services could be easily distributed out to separate worker servers without the need to configure the loads of front-facing web servers.

即使你的后台处理可能最初运行在同一台服务器上时看不出什么优点,但是这样的做法具有非常高的可扩展性的潜力。这些后台处理服务可以容易地分割出去,作为单独的 worker 服务器,而不需要配置的入口的 web服务器的负载。

Of course, you’d use the same approach on other platforms too, but with Node.js you get that high reqs/sec throughput we’ve talked about, as each request is a small task handled very quickly and efficiently.

当然,你也可以在其他语言平台上用同样的方法,但使用 Node.js 你可以得到很高的吞吐量,每个请求都作为一个小任务非常迅速和高效地处理,这一点我们已经讨论过了。

结论

We’ve discussed Node.js from theory to practice, beginning with its goals and ambitions, and ending with its sweet spots and pitfalls. When people run into problems with Node, it almost always boils down to the fact that blocking operations are the root of all evil—99% of Node misuses come as a direct consequence.

我们已经从理论到实践讨论过 Node.js 了,从它的目标和野心,到他的优点和缺点。在 Node.js 的开发中99%的问题是由误用阻塞操作是造成。

Remember: Node.js was never created to solve the compute scaling problem. It was created to solve the I/O scaling problem, which it does really well.

请记住:Node.js 从来不是用于解决计算结垢问题而创建的。它的出现是为了解决了 I/O 的结垢问题,并且在这一点上做的非常好。

So, give it some thought: if your use case does not contain CPU intensive operations nor access any blocking resources, you can exploit the benefits of Node.js and enjoy fast and scalable network applications. Welcome to the real-time web.

综上,如果你项目需求中不包含CPU密集型操作也不需要访问任何阻塞的资源,那么你就可以利用的 Node.js 的优点,尽情的享受快速、可扩展的网络应用。

原文链接:http://www.toptal.com/nodejs/why-the-hell-would-i-use-node-js

MongoDB EmbeddedDocument 与 JSON 转换

今天倒是碰到个很奇怪的问题, schema 中的字段是一个 json 对象包含三个数组, 然后每个数组里面存储 json 对象, 结果取出来数组 arr[i] 里面 json 对象的值 arr[i].key 不出来, 用中括号 arr[i][key] 也取不出来值感觉有些莫名其妙. 随后用 arr[i].constructor 字段查看了一下结果发现这个取出来的数据类型不是常见的 json 那样的 object 而是:

function EmbeddedDocument() {
    Subdocument.apply(this, arguments);
}

关于 EmbeddedDocument 类型, 仔细 google 了一下发现还是一个挺常见的类型确实算是我孤陋寡闻吧. 尝试用 JSON.parse 来转换失败之后研究了一下发现这个 Document 类型有自带很多方法的. 解决方案也很多 :

// 方法1
var json = arr[i].toJson();

// 方法2
arr[i].getValue(key);

// 方法3
arr[i].get(key);

EmbeddedDocument 的内置属性方法

见名之意, 所以直接列举了

  • __parent
  • __parentArray
  • _doc
  • _events
  • _id
  • _lazySetupHooks
  • _maxListeners
  • _posts
  • _pres
  • addListener
  • emit
  • equals
  • errors
  • get
  • getValue
  • hook
  • init
  • inspect
  • invalidate
  • isDirectModified
  • isInit
  • isModified
  • isNew
  • isSelected
  • listeners
  • markModified
  • modifiedPaths
  • on
  • once
  • ownerDocument
  • parent
  • parentArray
  • populate
  • populated
  • post
  • pre
  • remove
  • removeAllListeners
  • removeListener
  • removePre
  • save
  • schema
  • set
  • setMaxListeners
  • setValue
  • toJSON
  • toObject
  • toString
  • type
  • update
  • validate

nodejs constructor instanceof constructor

今天在做数据缓冲,新旧实体对比的时候,使用 underscore.js 的 isEqual 方法,结果因为一些问题不能工作。于是跟踪到 underscore 内部去看,发现了这段代码:

    // Objects with different constructors are not equivalent, but `Object`s
    // from different frames are.
    var aCtor = a.constructor, bCtor = b.constructor;
    if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) &&
                             _.isFunction(bCtor) && (bCtor instanceof bCtor))) {
      return false;
    }

本来 instanceof 这个东西就是用来看一个对象的实例:

var str = new String("hello");
console.log(str instanceof String); // true

在我的印象中也就是用来做如上这种判断而已,所以当我发现了 underscore 中这种 aCtor instanceof aCtor 的情况时表示十分不能理解。找了好几个地方都没有什么资料,最后只有自己用穷举研究了一边。

constructor instanceof constructor

穷举遍历所有 GLOBAL 下的对象,发现除了 console、process、Infinity、isFinite、undefined 之外,其他所有内置对象的 constructor 都是自己 constructor 的实例。

而所有能 new 出来实例的,除了 Object 和 Function 的实例,其他实例的 constructor 都不是自己 constructor 的实例。

根据上述现象,我没有丝毫技术含量 (Orz) 的得出了这个结论:

aCtor = a.constructor
当 aCtor instanceof aCtor == true 的时候

这个 a 只可能是内置全局对象,或者是 Object 和 Function 的实例。。

(环境 nodejs v0.10.15)

Node.js 入门教程 第二讲 常见的全局对象

全局对象列表

使用util工具模块提供的inspect方法可以遍历出 global 全局变量中所存在的对象与方法

var util = require("util");
console.log(util.inspect(global, true, 0));

输出结果(已排序)

{ 
    // global自身
    root                 : [Circular],
    global               : [Circular],
    GLOBAL               : [Circular],

    // node 內置对象
    console              : [Getter],  // 控制台对象
    process              : [Object],  // 进程对象
    Buffer               : [Object],  // 缓冲对象 (不满纯js的ArrayBuffer?)

    // 基础对象
    [Object]             : [Object],  // Object 对象
    [Function]           : [Object],  // Function 对象
    [Date]               : [Object],  // 日期对象
    [Number]             : [Object],  // 数字对象对象
    [String]             : [Object],  // 字符串对象
    [Array]              : [Object],  // 数组对象
    [Boolean]            : [Object],  // 布尔对象对象
    [Math]               : [Object],  // Math 对象
    [RegExp]             : [Object],  // RegExp 正则对象
    [JSON]               : [Object],  // json 对象
    [eval]               : [Object],  // eval 对象

    // 类型化数组                     //  字节      描述                C语言描述
    Int8Array            : [Object],  //   1   8位有符号整数           signed char
    Uint8Array           : [Object],  //   1   8位无符号整数           unsigned char
    Uint8ClampedArray    : [Object],  //   1   8位无符号整数 (clamped) unsigned char
    Int16Array           : [Object],  //   2   16位有符号整数          short
    Uint16Array          : [Object],  //   2   16位无符号整数          unsigned short
    Int32Array           : [Object],  //   4   32位有符号整数          int
    Uint32Array          : [Object],  //   4   32位无符号整数          unsigned int
    Float32Array         : [Object],  //   4   32位IEEE浮点数          float
    Float64Array         : [Object],  //   8   64位IEEE浮点数          double
    ArrayBuffer          : [Object],  // 二进制数据的原始缓冲区
    DataView             : [Object],  // 可用在 ArrayBuffer 中的任何位置读写入不同类型的数据

    // 錯誤對象
    [Error]              : [Object],  // 错误错误
    [EvalError]          : [Object],  // Eval 错误
    [TypeError]          : [Object],  // Type 错误
    [URIError]           : [Object],  // URI 错误
    [RangeError]         : [Object],  // Range 错误
    [ReferenceError]     : [Object],  // Reference 错误
    [SyntaxError]        : [Object],  // Syntax 错误

    // 类型判断
    [NaN]                : NaN,       // not a number 不是一個数字
    [Infinity]           : Infinity,  // 正无穷大
    [isFinite]           : [Object],  // 判断是否为正无穷大
    [undefined]          : undefined  // 未定义
    [isNaN]              : [Object],  // 判断是否为未定义 

    // 定时延时对象
    setInterval          : [Object],  // 设置定时器
    clearInterval        : [Object],  // 清除定时器
    setTimeout           : [Object],  // 设置延迟执行
    clearTimeout         : [Object],  // 清除延迟执行
    setImmediate         : [Object],  // 设置延迟执行(更快更兼容)
    clearImmediate       : [Object],  // 清除延迟执行

    // 编码
    [decodeURI]          : [Object],  // 解码某个编码的 URI
    [decodeURIComponent] : [Object],  // 解码一个编码的 URI 组件
    [encodeURI]          : [Object],  // 把字符串编码为 URI
    [encodeURIComponent] : [Object],  // 把字符串编码为 URI 组件
    [escape]             : [Object],  // 对字符串进行编码
    [unescape]           : [Object],  // 对由 escape() 编码的字符串进行解码
    [parseInt]           : [Object],  // 解析一个字符串并返回一个整数
    [parseFloat]         : [Object],  // 解析一个字符串并返回一个浮点数

    // node 内置对象(请先无视)
    COUNTER_HTTP_SERVER_REQUEST        : [Object],
    COUNTER_HTTP_SERVER_RESPONSE       : [Object],
    COUNTER_HTTP_CLIENT_REQUEST        : [Object],
    COUNTER_HTTP_CLIENT_RESPONSE       : [Object],
    COUNTER_NET_SERVER_CONNECTION      : [Object],
    COUNTER_NET_SERVER_CONNECTION_CLOSE: [Object],
    DTRACE_HTTP_CLIENT_REQUEST         : [Object],
    DTRACE_HTTP_SERVER_REQUEST         : [Object],
    DTRACE_HTTP_CLIENT_RESPONSE        : [Object],
    DTRACE_HTTP_SERVER_RESPONSE        : [Object],
    DTRACE_NET_SERVER_CONNECTION       : [Object],
    DTRACE_NET_STREAM_END              : [Object],
    DTRACE_NET_SOCKET_WRITE            : [Object],
    DTRACE_NET_SOCKET_READ             : [Object],
}

Circular 可以理解为递归到自己

大家也可以去看w3school 上的 JavaScript 全局对象参考手册 来对比 浏览器上的 js 与 node 上 js 全局对象的一些区别

global 对象

global 为全局命名空间对象,不过需要注意的是,这个全局只是针对当前文件本身。

console 对象

console.log(“hello node.js”)
for(var i in console){
console.log(i+” “+console[i])
}

var log = function () {
process.stdout.write(format.apply(this, arguments) + ‘n’);
}
var info = function () {
process.stdout.write(format.apply(this, arguments) + ‘n’);
}
var warn = function () {
writeError(format.apply(this, arguments) + ‘n’);
}
var error = function () {
writeError(format.apply(this, arguments) + ‘n’);
}
var dir = function (object) {
var util = require(‘util’);
process.stdout.write(util.inspect(object) + ‘n’);
}
var time = function (label) {
times[label] = Date.now();
}
var timeEnd = function (label) {
var duration = Date.now() – times[label];
exports.log(‘undefined: NaNms’, label, duration);
}
var trace = function (label) {
// TODO probably can to do this better with V8’s debug object once that is
// exposed.
var err = new Error;
err.name = ‘Trace’;
err.message = label || ”;
Error.captureStackTrace(err, arguments.callee);
console.error(err.stack);
}
var assert = function (expression) {
if (!expression) {
var arr = Array.prototype.slice.call(arguments, 1);
require(‘assert’).ok(false, format.apply(this, arr));
}
}

process 对象

buffer 对象

module 对象

for(var i in module){
console.log(“var ” + i + ” = “+module[i])
}

var id = .
var exports = [object Object]
var parent = null
var filename = /home/cheng19840218/node/example.js
var loaded = false
var exited = false
var children =
var paths = /home/cheng19840218/node/node_modules,/home/cheng19840218/node_modules,/home/node_modules,/node_modules
var load = function (filename) {
//太长了,省略
}
var _compile = function (content, filename) {
//太长了,省略
}

原来那个著名的exports是在此提供的,__filename大概也是filename的引用。只要遍历一下,你就发现许多有趣的东西。但别以为一下秘密就暴光在你眼皮下,还有许多不可遍历属性。比如上面我遍历global对象,只有尞尞可数几个成员,我们可以使用ecma262v5新增的方法去考察一下:

console.log(Object.getOwnPropertyNames(global))

许多人学node.js就立即看其文档,殊不知node.js本身所依赖的V8引擎就拥有许多要学的东西,这其中包括ecma262v5带来的新方法新对象,还有效仿firefox的一些语法:

__defineGetter__
__defineSetter__
__lookupGetter__
__lookupSetter__
set
get
__proto__

好了,我们来点实质的内容吧。node.js本来就是一个http服务器,它是要与前端交互的,因此少不了两个对象:请求(request)与响应(response)。请求与响应显然一种异步的东西,因为我们 不知道前端什么时候发请求过来,响应也不能立即给前端,还要做日志,读写数据库等操作呢。因此对于javascript来说,这用回调函数来实现最好。那么由誰来接受这个回调呢?一个服务器对象!

var http = require(“http”);
http.createServer(function(request, response) {
response.writeHead(200, {“Content-Type”: “text/plain”});
response.write(“Hello node.js”);
response.end();
}).listen(8888);
node.js有个特殊的require,用于同步加载其他模块的对象,这与其他语言的require, import差不多。能同步就是好,不像前端那样一层套一层。然后利用一个函数去实例化一个服务器对象,然后监听8888端口。这是node.js官网最初的例子,大家都写烂了。但这样的程序在现实中一无是处,我们在地址栏上输入URL,你起码要返回一个完整页面给我吧!

对此,我们首先要进行模块化。模块化是以文件为单位的,把example.js更名为server.js,然后再把里面的内容改为一个模块。对于一个node.js的文件,其实它里面的内容是在一个封闭的环境中执行。要想共享给其他模块使用,就必须绑定在exports对象上。

var http = require(“http”);

exports.start = function(){
http.createServer(function(request, response) {
console.log(“Request received…”);
response.writeHead(200, {“Content-Type”: “text/plain”});
response.write(“Hello node.js”);
response.end();
}).listen(8888);
console.log(“server start…”);
}
然后我们再建一个index.js作为入口(index.js与server.js放在同一目录下)。

var server = require(“./server”);

server.start();
然后建一个index.html页面。

index

这是首页

现在我们就在要请求过来时,把此页的内容读出来,返给用户。这时我们就要用到fs模块的方法了。

var http = require(“http”);
var fs = require(‘fs’);
exports.start = function(){
http.createServer(function(request, response) {
fs.readFile(‘./index.html’, ‘utf-8’,function (err, data) {//读取内容
if (err) throw err;
response.writeHead(200, {“Content-Type”: “text/html”});//注意这里
response.write(data);
response.end();
});
}).listen(8888);
console.log(“server start…”);
}
好了,这时我们重启再次输入地址,就看到一个完整的页面了。

但一个页面除了HTML结构层外,还有javascript与css。那么,我们在当前目录建一个文件夹javascripts, 里面建index.js,内容如下:

window.onload = function(){
var p = document.createElement(“p”);
p.innerHTML = “这是动态添加的”
document.body.appendChild(p);
}
再建一个styles目录,里面建index.css,内容如下:

html,body{
background: #3671A5;
height: 100%
}
然后在index.html引入这两个文件:

index

/javascripts/index.js

这是首页

重新打开,发现没有改变,google,说要处理js与css文件的请求。没有办法,取得request.url属性,再判定后缀名,为它进行文件读取与设置首部。

var http = require(“http”);
var fs = require(‘fs’);
var url = require(‘url’);
exports.start = function(){
http.createServer(function(request, response) {
var pathname = url.parse(request.url).pathname;
var ext = pathname.match(/(.[^.]+|)$/)[0];//取得后缀名
switch(ext){
case “.css”:
case “.js”:
fs.readFile(“.”+request.url, ‘utf-8’,function (err, data) {//读取内容
if (err) throw err;
response.writeHead(200, {
“Content-Type”: {
“.css”:”text/css”,
“.js”:”application/javascript”,
}[ext]
});
response.write(data);
response.end();
});
break;
default:
fs.readFile(‘./index.html’, ‘utf-8’,function (err, data) {//读取内容
if (err) throw err;
response.writeHead(200, {
“Content-Type”: “text/html”
});
response.write(data);
response.end();
});

}

}).listen(8888);
console.log(“server start…”);
}