事出有因
为何选择event loop?
Event Loop是一种推进无阻塞I/O(网络、文件或跨进程通讯)的软件模式。传统的阻塞编程也是用一样的方式,通过function来调用I/O。但进程会在该I/O操作结束前卡住不动,下面的这段伪代码可以演示阻塞I/O的情况:
var post = db.query('SELECT * FROM posts where id = 1'); // 这行后面的指令都无法执行,除非等到这行指令执行完毕 doSomethingWithPost(post); doSomethingElse();
这里都发生了什么呢?当数据库查询被执行时,整个进程/线程都闲置下来,等待查询操作的响应,我们称之为阻塞。数据查询的响应可能要花费上千个CPU周期,这段时间内,整个进程都处于不可用状态。进程在数据库操作完成前就白白等待下去,浪费了让它来着手处理其它客户端请求的机会。
这种编程方式将不允许你来并行处理I/O(例如同时执行另一个数据库查询或与远程web服务进行通讯),导致调用栈被冻结了,傻傻等候数据库服务的回应。
这里有两个可实现的解决方案能让进程在这种等候期间保存忙碌——创建更多的调用栈,或者使用事件回调。
Why?
Why the event loop?
The Event Loop is a software pattern that facilitates non-blocking I/O (network, file or inter-process
communication). Traditional blocking programming does I/O in the same fashion as regular function
calls; processing may not continue until the operation is complete. Here is some pseudo-code that
demonstrates blocking I/O:
1 var post = db.query('SELECT * FROM posts where id = 1');
2 // processing from this line onward cannot execute until the line above completes
3 doSomethingWithPost(post);
4 doSomethingElse();
What is happening here? While the database query is being executed, the whole process/thread
idles, waiting for the response. This is called blocking. The response to this query may take many
thousands of CPU cycles, rendering the entire process unusable during this time. The process could
have been servicing other client requests instead of just waiting for the database operation to
complete.
Programming in this way does not allow you to parallelize I/O (such as performing another database
query or communicating with a remote web service). The call stack becomes frozen, waiting for the
database server to reply.
This leaves you with two possible solutions to keep the process busy while it’s waiting: create more
call stacks or use event callbacks.
方案1: 创建更多调用栈
为了让你的进程能操作更多并发I/O,你需要拥有更多并发的调用栈。因此你可以使用线程或者一些协作式多任务的方案,例如协同程序(coroutines)、fibers、延续机制(continuations)等等。对多线程并发模型存在难以配置、理解和编译的问题,主要是因为同步访问和修改共享状态的复杂性。你无法知道一个你运行的线程何时才从执行中结束,从而导致奇怪的、难以重现的各种bug。
另外,当你使用的栈超过一个时,使用协作式多任务是场闹剧,每一个执行的“线程”都会被明确要求删除自己,来给其它并行的“线程”让路,这样虽能舒缓同步需求,但也让程序员失去了对线程调度的控制,从而让事情变的复杂并有滋生错误的风险。
Solution 1: Create more call stacks
In order for your process to handle more concurrent I/O, you have to have more concurrent call
stacks. For this, you can use threads or some kind of cooperative multi-threading scheme like coroutines,
fibers, continuations, etc.
The multi-threaded concurrency model can be very difficult to configure, understand and debug,
mainly because of the complexity of synchronization when accessing and modifying shared state;
you never know when the thread you are running is going to be taken out of execution, which can
lead to bugs that are strange and difficult to reproduce.
On the other hand, cooperative multi-threading is a “trick” where you have more than one stack,
and each “thread” of execution explicitly de-schedules itself to give time to another parallel “thread”
of execution. This can relax the synchronization requirements but can become complex and errorprone,
since the thread scheduling is left in the hands of the programmer.
方案2:使用事件驱动I/O
事件驱动I/O方案指的是由你注册回调函数,当与之相关的I/O事件发生时,触发该回调。
一个事件回调是一个函数,可以在某些有意义的事件(比如“从数据库查询得到的结果是有效的”)发生的时候该函数便会被触发。
在之前的示例中,我们可以做一番修改,让其使用事件回调:
callback = function(post) { doSomethingWithPost(post); // 只有当db.query函数返回响应的时候才会执行 }; db.query('SELECT * FROM posts where id = 1', callback); doSomethingElse(); // 该指令会独立执行,无关db.query函数调用返回的状态
这里我们定义了一个函数,它在数据库操作完毕的时候会被触发。接着把该函数作为回调参数放到数据库查询操作中,这样一来,“数据查询结果是有效的就执行回调”就成了数据库操作的一个责任。你可以用一个内联的匿名函数来写的更紧凑些:
db.query('SELECT * FROM posts where id = 1', function(post) { doSomethingWithPost(post); // 只有当db.query函数返回响应的时候才会执行 } ); doSomethingElse(); // 该指令会独立执行,无关db.query函数调用返回的状态
当db.query()执行的时候,进程可以自由地继续运行doSomethingElse()、继续为新的客户端请求提供服务。
Solution 2: Use event-driven I/O
Event-driven I/O is a scheme where you register callbacks to be invoked when an interesting I/O
event happens.
An event callback is a function that gets invoked when something significant happens (e.g. the result
of a database query is available.)
To use event callbacks in the previous example, you could change it to:
1 callback = function(post) {
2 doSomethingWithPost(post); // this will only execute when the db.query function
3 returns.
4 };
5 db.query('SELECT * FROM posts where id = 1', callback);
6 doSomethingElse(); // this will execute independent of the returned status of the
7 db.query call.
Here you are defining a function to be invoked when the database operation is complete, then passing
this function as a callback argument to the db query operation. The db operation becomes responsible
for executing the callback when the result is available.
You can use an inline anonymous function to express this in a more compact fashion:
1 db.query('SELECT * FROM posts where id = 1',
2 function(post) {
3 doSomethingWithPost(post); // this will only execute when the db.query functi
4 on returns.
5 }
6 );
7 doSomethingElse(); // this will execute independent of the returned status of the
8 db.query call.
While db.query() is executing,the process is free to continue running doSomethingElse(), and even
service new client requests.
在很长一段时间,在C系统编程社区“hacker”里都知悉事件驱动编程是扩展服务来操作多个同步连接的最好方法——内存使用更高效(更少的上下文环境需保存),等待时间更少(更少的上下文转换)。
该知识点逐渐渗透到其它平台和社区——像Ruby的 EventMachine, Perl的AnyEvent和Python的Twisted等,都是非常知名的event loop操作机制。
贴士:关于更多事件驱动服务机制的信息,可以查阅http://en.wikipedia.org/wiki/Reactor_pattern
执行一个使用事件驱动框架的应用,需要了解框架特效以及框架特性库。打个比方,当你使用了Event Machine,你必须避免使用Ruby-land上所有可用的同步库(也就是说大部分的库)。
为了获取非阻塞的好处,你会被限制使用那些Event Machine的特性库。如果你使用了那些阻塞的库,你的程序将无法最佳地进行扩展,因为event loop不断地被阻塞了,可能导致I/O事件执行过程的延时,进而让你的程序变慢,违背了你使用事件驱动I/O的初衷。
Node从一开始就被创造为一种无阻塞I/O服务平台,这意味着你可以期许一切建立在Node上的东西都能是无阻塞的(包括一些具体、明确的异常处理)。由于JS本身小巧也不不强制要求任何I/O操作(他不像C、Ruby或者Python那样有标准的I/O库),Node才可以从零开始建立起来。
For quite some time, the C systems-programming “hacker” community has known that event-driven
programming is the best way to scale a server to handle many concurrent connections. It has been
known to be more efficient regarding memory: less context to store, and time: less context-switching.
This knowledge has been infiltrating other platforms and communities: some of the most wellknown
event loop implementations are Ruby’s Event Machine, Perl’s AnyEvent and Python’s
Twisted, and some others.
Tip: For more info about event-driven server implementations, see http://en.wikipedia.org/wiki/Reactor_-
pattern⁴.
Implementing an application using one of these event-driven frameworks requires frameworkspecific
knowledge and framework-specific libraries. For example: when using Event Machine, you
must avoid using all the synchronous libraries available in Ruby-land (i.e. most libraries). To gain
the benefit of not blocking, you are limited to using libraries that are specific for Event Machine. If
you use blocking libraries, your program will not be able to scale optimally because the event loop is
constantly being blocked, which may delay the processing of I/O events and makes your application
slow, defeating the original purpose of using event-driven I/O.
Node has been devised as a non-blocking I/O server platform from day one, which means that you
should expect everything built on top of it to be non-blocking (with some specific and very explicit
exceptions). Since JavaScript itself is very minimal and does not impose any way of doing I/O (it
does not have a standard I/O library like C, Ruby or Python), Node has a clean slate to build upon.
为何采用JavaScript?
Ryan Dahl 开始执行该项目时是采用C语言来搭建平台的,但他很快意识到,若要维持好回调之间的上下文关系会非常复杂,并会导致代码结构不合理。于是他转向了Lua,Lua有着一些阻塞I/O的库,这样混合着阻塞和非阻塞的方式会让大部分开发者摸不着头脑,从而阻碍他们来搭建可扩展的应用,因此Lua也不是干这事的理想的语言。
于是Dahl开始考虑JS了,JS有闭包也有经典的函数,让它实在地、出色地匹配了事件型I/O编程的需求。
闭包指的是继承了外部封闭环境的变量的函数,当一个函数回调执行它的时候,它能魔幻般地记住它之前声明过的上下文环境,包括该上下文环境和其它父级上下文环境中的变量。这种强大的特性是使得Node在各种编程社区中取得成功的重要原因。让我们来看一些它出色的运行方式:
在web浏览器中,如果你想监听某个事件,比如点击一个按钮,你可以类似这么做:
var clickCount = 0; document.getElementById('mybutton').onclick = function() { clickCount ++; alert('Clicked ' + clickCount + ' times.'); };
或者使用jQuery:
var clickCount = 0; $('button#mybutton').click(function() { clickCount ++; alert('Clicked ' + clickCount + ' times.'); });
在这两个示例中,我们都把回调函数作为一个参数来指派,它会在相关事件(如示例是按钮被点击)发生后被执行。该点击事件的处理函数可以访问其声明处作用域内的每一个变量。
也就是说,该点击事件处理函数可以访问在其父级闭包作用域内声明的变量clickCount。
在这里我们使用了一个全局变量“clickCount”,用来保存用户在不同时刻点击按钮的数量。我们也可以避免使用全局变量——把它放到一个闭包内即可,让变量clickCount只能在我们创建的闭包内被访问:
(function() { var clickCount = 0; $('button#mybutton').click(function() { clickCount ++; alert('Clicked ' + clickCount + ' times.'); }); })();
在第七行我们让函数在其定义后便立即执行,如果你对这种方式感到陌生,倒是无须担心,后续我们会介绍到这种模式。
在这里你无需操心同步的问题了——你完全可以保证你的回调函数在其结束前不会被中断。
Why JavaScript?
Ryan Dahl began this pet project of his by building a platform that was programmable in C, but he
soon realized that maintaining the context between callbacks was too complicated and could lead
to poorly structured code. He then turned to Lua.
Lua already has several blocking I/O libraries and the mix of blocking and non-blocking could
confuse average developers and prevent many of them from building scalable applications, so Lua
wasn’t ideal either. Dahl then thought to JavaScript.
JavaScript has closures and first-class functions, making it indeed a powerful match with evented
I/O programming.
Closures are functions that inherit the variables from their enclosing environment. When a function
callback executes it will magically remember the context in which it was declared, along with all
the variables available in that context and any parent contexts. This powerful feature is at the heart
of Node’s success among programming communities. Let’s see a little bit of this goodness in action:
In the web browser, if you want to listen for an event, a button click for instance, you may do
something like:
1 var clickCount = 0;
2 document.getElementById('mybutton').onclick = function() {
3 clickCount ++;
4 alert('Clicked ' + clickCount + ' times.');
5 };
or, using jQuery:
⁴http://en.wikipedia.org/wiki/Reactor_pattern
Why? 7
1 var clickCount = 0;
2 $('button#mybutton').click(function() {
3 clickCount ++;
4 alert('Clicked ' + clickCount + ' times.');
5 });
In both examples we assign or pass a function as an argument. This function will then be executed
later once the relevant event (button clicking in this case) happens. The click handling function has
access to every variable in scope at the point where the function is declared, i.e. practically speaking,
the click handler has access to the clickCount variable, declared in the parent closure.
Here we are using a global variable, “clickCount”, where we store the number of times the user has
clicked a button. We can also avoid having a global variable accessible to the rest of the system,
by wrapping it inside another closure, making the clickCount variable only accessible within the
closure we created:
1 (function() {
2 var clickCount = 0;
3 $('button#mybutton').click(function() {
4 clickCount ++;
5 alert('Clicked ' + clickCount + ' times.');
6 });
7 })();
In line 7 we are invoking a function immediately after defining it. If this is strange to you, don’t
worry! We will cover this pattern later.
Here you don’t have to worry about synchronization: your callback function will not be interrupted
until it returns - you have that guarantee.
我要如何摆脱对JS的恐惧并试着爱上它?
JavaScript有其优势也有其劣势,它是由Netscape公司的Brendan Eich在1995年创建的,当初为了尽快用到新版的Netscape浏览器上,JS被设计的很匆忙。因此JS虽然有很多很好甚至很赞的地方,也存在某些不好的地方。 本书内容不会涉及比较JS的“好”和“坏”,(当然我们只会使用那些“好”的部分来书写示例。)如果你想了解更多此话题信息,可以阅读由O’Reilly出版的Douglas Crockford的《JavaScript, The Good Parts》一书。
尽管JS有其缺点,但也超乎预料地迅速流行起来,成为了浏览器语言的无冕之王。在那时候,JS主要是用来检测和操作HTML文档、创建动态的客户端web应用。在1998年年末,万维网路联盟(W3C)标准化了文档对象模型(DOM),以及在用户端检测、操作HTML文档的API。由于JS相较DOM API存在怪异模式和抗衡,也因为它在不同浏览器渲染引擎上存在兼容问题(甚至在同渲染引擎上的不同产品有时也会存在兼容问题),导致JS很快得到了坏名声。
尽管在一些开发者社区对JS还是口诛笔伐,JS还是日渐被广泛采纳。无论好坏,JS在当下已成为了世界上最多被开发的编程语言,而且开发数量还在增长中。 如果你获悉过一门语言的出色特性,比如原型继承、函数闭包等等,而且你懂得如何避免或绕过一门语言中的瑕疵处,那么JS会是让你用起来很舒心的一门语言。
函数声明风格
在JS中,函数可以通过多种方式来进行声明,最简单的方式就是这样匿名地声明它:
function() { console.log('hello'); }
这里我们声明了一个函数,但这样做没啥用处,因为它没被调用到。而且因为它没有名字,所以我们也没办法调用它。
不过我们可以这样调用一个匿名函数:
(function() { console.log('hello'); })();
此处我们让函数在声明后便立即执行起来。注意我们在整个函数声明体的外部包裹了一对括号。
我们也可以这样给函数命名:
function myFunction () { console.log('hello'); }
我们给这个命名函数起了个名字叫“myFunction”,myFunction在其声明的作用域内部是可以被调用的:
myFunction();
在内部作用域中也可以:
function myFunction () { console.log('hello'); } (function() { myFunction(); })();
由于JS把函数作为一级对象来处理,因此我们可以把一个函数指派给一个变量:
var myFunc = function() { console.log('hello'); }
现在这个函数等值于这个myFunc变量,我们可以指派这个函数给其它变量:
var myFunc2 = myFunc;
并像其它函数那样调用它们:
myFunc();
myFunc2();
我们可以结合全部技巧, 把一个命名函数存储在一个变量中:
var myFunc2 = function myFunc() { console.log('hello'); } myFunc2();
(注意我们在myFunc本身所处的作用域外是不能访问到myFunc的)
我们可以使用一个变量或函数的名称来作为其它函数的参数:
var myFunc = function() { console.log('hello'); } console.log(myFunc);
如果在其它地方我们用不上它,可以直接以内联的形式声明:
console.log(function() { console.log('hello'); });
How I Learned to Stop Fearing and Love JavaScript
JavaScript has good and bad parts. It was created in 1995 by Netscape’s Brendan Eich, in a rush to
ship the latest version of the Netscape web browser. Due to this rush some good, even wonderful,
parts got into JavaScript, but also some bad parts.
This book will not cover the distinction between JavaScript good and bad parts. (For all we know, we
will only provide examples using the good parts.) For more on this topic you should read Douglas
Crockford book named “JavaScript, The Good Parts”, edited by O’Reilly.
In spite of its drawbacks, JavaScript quickly - and somewhat unpredictably - became the de-facto
language for web browsers. Back then, JavaScript was used primarily to inspect and manipulate
HTML documents, allowing the creation the first dynamic, client-side web applications.
In late 1998, the World Wide Web Consortium (W3C), standardized the Document Object Model
(DOM), an API devised to inspect and manipulate HTML documents on the client side. In response
to JavaScript’s quirks and the initial hatred towards the DOM API, JavaScript quickly gained a
bad reputation, also due to some incompatibilities between browser vendors (and sometimes even
between products from the same vendor!).
Despite mild to full-blown hate in some developer communities, JavaScript became widely adopted.
For better or for worse, today JavaScript is the most widely deployed programming language on
planet Earth – and growing.
If you learn the good features of the language - such as prototypical inheritance, function closures,
etc. - and learn to avoid or circumvent the bad parts, JavaScript can be a very pleasant language to
work in.
Function Declaration Styles
A function can be declared in many ways in JavaScript. The simplest is declaring it anonymously:
1 function() {
2 console.log('hello');
3 }
Here we declare a function, but it’s not of much use, because we do not invoke it. What’s more, we
have no way to invoke it as it has no name.
We can invoke an anonymous function in-place:
1 (function() {
2 console.log('hello');
3 })();
Here we are executing the function immediately after declaring it. Notice we wrap the entire
function declaration in parenthesis.
We can also name functions like this:
1 function myFunction () {
2 console.log('hello');
3 }
Here we are declaring a named function with the name: “myFunction”. myFunction will be available
inside the scope in which it’s declared
1 myFunction();
and also within inner scopes:
1 function myFunction () {
2 console.log('hello');
3 }
4
5 (function() {
6 myFunction();
7 })();
A result of JavaScript treating functions as first-class objects means we can assign a function to a
variable:
1 var myFunc = function() {
2 console.log('hello');
3 }
This function is now available as the value of the myFunc variable.
We can assign that function to another variable:
1 var myFunc2 = myFunc;
And invoke them just like any other function:
1 myFunc();
2 myFunc2();
We can mix both techniques, having a named function stored in a variable:
1 var myFunc2 = function myFunc() {
2 console.log('hello');
3 }
4 myFunc2();
(Note though, we cannot access myFunc from outside the scope of myFunc itself!)
We can then use a variable or a function name to pass variables into functions like this:
1 var myFunc = function() {
2 console.log('hello');
3 }
4
5 console.log(myFunc);
or simply declare it inline if we don’t need it for anything else:
1 console.log(function() {
2 console.log('hello');
3 });
函数是一级对象
事实上,在JS里其实不存在“二级对象”。JS是彻头彻尾的面向对象语言,几乎JS上所有的东西都是一个对象。就此而言,一个函数就是一个对象,一个你可以设置其属性,可以把它放到参数中,或者返回它们的对象
示例:
1 var scheduler = function(timeout, callbackfunction) { 2 return function() { 3 setTimeout(callbackfunction, timeout) 4 } 5 }; 6 7 (function() { 8 var timeout = 1000; // 1 second 9 var count = 0; 10 var schedule = scheduler(timeout, function doStuff() { 11 console.log(++ count); 12 schedule(); 13 }); 14 schedule() 15 })();
在这个小例子中我们创建了一个函数并保存到一个叫做scheduler的变量中(第一行),该函数返回了一个启动了定时器的函数,定时器会在某个数量的毫秒(第三行)过后执行所给的函数。这个定时器会在定时变量所指定的1秒的延迟后执行回调函数。
在第九行我们声明了一个会在第十五行立即执行的函数,这是一个JS中很常用的创建新作用域的办法。在该作用域内,我们创建了两个变量:timeout(第八行)和 count(第九行)。注意这些变量无法在作用域外部被访问到。
接着看第十行,我们调用了scheduler函数,并把timeout的值和名为doStuff的函数分别作为第一个和第二个参数传入。我们把它返回的函数保存到局部变量schedule中,我们会在后面调用它(第十四行),来触发setTimeout定时器。当超时发生时,函数会增加变量count的值并记录它,接着再一次调用schedule。
所以在这个小示例中我可以看到:函数可作为参数来传递;函数可以用来创建作用域;函数可以异步地执行回调并返回函数。我们也在这里提出了封装(向外部作用域隐藏局部变量)和递归(在函数的结尾调用它自己)的概念。
在JS中你甚至可以设置和访问一个函数的属性,例如这样:
var myFunction = function() { // do something crazy }; myFunction.someProperty = 'abc'; console.log(myFunction.someProperty); // #=> "abc"
JS毋庸置疑是一门强大的语言,如果你还没准备好,那么你应该开始学习并拥抱它那些出色的地方。
Functions are first-class objects
In fact, there are no second-class objects in JavaScript. JavaScript is the ultimate object-oriented
language, where (almost) everything is indeed, an object. As that, a function is an object where you
can set properties, pass it around inside arguments and return them.
Example:
1 var scheduler = function(timeout, callbackfunction) {
2 return function() {
3 setTimeout(callbackfunction, timeout)
4 }
5 };
6
7 (function() {
8 var timeout = 1000; // 1 second
9 var count = 0;
10 var schedule = scheduler(timeout, function doStuff() {
11 console.log(++ count);
12 schedule();
13 });
14 schedule()
15 })();
16
17 // "timeout" and "count" variables
18 // do not exist in this scope.
In this little example we create a function and store it in a variable called “scheduler” (starting on
line 1). This function returns a function that sets up a timer that will execute a given function within
a certain number of miliseconds (line 3). This timeout will schedule a callback function to be called
after a time delay of 1 second, as specified by the timeout variable.
Why? 11
In line 9 we declare a function that will immediately be executed in line 15. This is a normal way
to create new scopes in JavaScript. Inside this scope we create 2 variables: “timeout” (line 8) and
“count” (line 9). Note that these variables will not be accessible to the outer scope.
Then, on line 10, we invoke the scheduler function, passing in the timeout value as first argument
and a function called doStuff as second argument. This returns a function that we store in the local
schedule variable, which we later invoke (line 14), provoking the setTimeout to be called. When the
timeout occurs, this function will increment the variable count and log it, and also call the schedule
all over again.
So in this small example we have: functions passed as argument, functions to create scope, functions
to serve as asynchronous callbacks and returning functions. We also here present the notions of
encapsulation (by hiding local variables form the outside scope) and recursion (the function is calling
itself at the end).
In JavaScript you can even set and access attributes in a function, something like this:
1 var myFunction = function() {
2 // do something crazy
3 };
4 myFunction.someProperty = 'abc';
5 console.log(myFunction.someProperty);
6 // #=> "abc"
JavaScript is indeed a powerful language, and if you don’t already, you should learn it and embrace
its good parts.
JSHint
本书不会涉及该部分内容,但JS的确存在某些我们应当全力避免的缺陷部分。就我而言,JSHint便是非常好用一款工具,它能分析你的JS文件并输出系列错误和警告信息,从而告知你一些已知的脚本错误,比如直接使用了全局域变量(没有var的变量),比如冻结了在递归的回调中使用的变量。还有其它一些有用的功能。
JHLint可以通过如下命令进行安装:
$ npm install -g jshint
如果你没有事先安装过Node,可查阅本书的NPM章节。
然后我们可以通过如下命令运行JHLint:
$ jshint myfile.js
你也可以在你的home文件夹中定制一个.jshintrc 文件来定义该工具应服从的规则。更多关于JSHint的信息你可以到 http://www.jshint.com/docs/ 查阅官方文档。
JSHint
It’s not to be covered here, but JavaScript indeed has some bad parts, and they should be avoided at
all costs.
One tool that’s proven invaluable to me is JSHint. JSHint analyzes your JavaScript file and outputs a
series of errors and warnings, including some known misuses of JavaScript, such as using globallyscoped
variables (like when you forget the “var” keyword), and freezing values inside iteration that
have callbacks that use them, and many others that are useful.
JHLint can be installed using
1 $ npm install -g jshint
If you don’t have Node installed see section about NPM⁵.
and can be run from the command line like this:
⁵index.html#npm
Why? 12
1 $ jshint myfile.js
You can also define what rules this tool should obey by defining and customizing a .jshintrc inside
your home directory. For more information on JSHint plrease refer to the official documentation at
http://www.jshint.com/docs/⁶.
JavaScript版本
JavaScript是其本身名字“ECMAScript”的一个标准,它已经历了好几次迭代。 Node支持V8脚本引擎所支持的一切——ECMA3和部分ECMA5。
在github wiki页面上有友好地列出那些被支持的ECMA5部分,你可以到这里查阅: https://github.com/joyent/node/wiki/5-Mozilla-Features-Implemented-in-V8
JavaScript versions
JavaScript is a standard with its own name - ECMAScript - and it has gone through various iterations.
Currently Node natively supports everything the V8 JavaScript engine supports ECMA 3rd edition
and parts of the new ECMA 5th edition.
These parts of ECMA 5 are nicely documented on the following github wiki page: https://github.com/joyent/node/wiki/5-Mozilla-Features-Implemented-in-V8⁷