发布于 2016-10-25 05:44:56 | 199 次阅读 | 评论: 0 | 来源: 网友投递
Node.js 服务器端的JavaScript
Node.js 是一个基于Chrome JavaScript 运行时建立的一个平台, 用来方便地搭建快速的 易于扩展的网络应用· Node.js 借助事件驱动, 非阻塞I/O 模型变得轻量和高效, 非常适合 运行在分布式设备 的 数据密集型 的实时应用
Node.js是一个让JavaScript运行在服务端的开发平台,它让JavaScript成为脚本语言世界的一等公民,在服务端堪与PHP、Python、Perl、Ruby平起平坐。
Node.js可以作为服务器向用户提供服务,与PHP、Python、RubyonRails相比,它跳过了Apache、Nginx等HTTP服务器,直接面向前端开发。
Node.js还可以调用C/C++的代码,这样可以充分利用已有的诸多函数库,也可以将对性能要求非常高的部分用C/C++来实现。
Node.js最大的特点就是采用异步式I/O与事件驱动的架构设计。对于高并发的解决方案,传统的架构是多线程模型,也就是为每个业务逻辑提供一个系统线程,通过系统线程切换来弥补同步式I/O调用时的时间开销。Node.js使用的是单线程模型,对于所有I/O都采用异步式的请求方式,避免了频繁的上下文切换。Node.js在执行的过程中会维护一个事件队列,程序在执行时进入事件循环等待下一个事件到来,每个异步式I/O请求完成后会被推送到事件队列,等待程序进程进行处理。
【图】Node.js的架构
Node.js开发环境搭建(Windows、Linux&Mac)
Node.js多版本管理器
$node-e"console.log(‘HelloWorld‘);"
HelloWorld
我们可以把要执行的语句作为node-e的参数直接执行。
REPL(Read-eval-printloop),即输入—求值—输出循环。运行无参数的node将会启动一个JavaScript的交互式shell。如果你执行了一个函数,那么REPL还会在下面显示这个函数的返回值。如果你输入了一个错误的指令,REPL则会立即显示错误并输出调用栈。在任何时候,连续按两次Ctrl+C即可推出Node.js的REPL模式。
【图】Node.js与PHP的架构
Node.js只有在第一次引用到某部份时才会去解析脚本文件,以后都会直接访问内存,避免重复载入。supervisor可以帮助你实现这个功能,它会监视你对代码的改动,并自动重启Node.js。使用方法很简单,首先使用npm安装supervisor:
$npminstall-gsupervisor
使用supervisor命令启动app.js:
$supervisorapp.js
【图】多线程同步式I/O
【图】单线程异步式I/O
单线程事件驱动的异步式I/O比传统的多线程阻塞式I/O究竟好在哪里呢?简而言之,异步式I/O就是少了多线程的开销。对操作系统来说,创建一个线程的代价是十分昂贵的,需要给它分配内存、列入调度,同时在线程切换的时候还要执行内存换页,CPU的缓存被清空,切换回来的时候还要重新从内存中读取信息,破坏了数据的局部性。
【码】readfile.js
// readfile.js var fs = require('fs'); fs.readFile('file.txt', 'utf-8', function(err, data) { if (err) { console.error(err); } else { console.log(data); } }); console.log('end.');
【码】readfilesync.js
// readfilesync.js var fs = require('fs'); var data = fs.readFileSync('file.txt', 'utf-8'); console.log(data); console.log('end.');
在Node.js中,异步式I/O是通过回调函数来实现的。
【码】readfilecallback.js
// readfilecallback.js function readFileCallBack(err, data) { if (err) { console.error(err); } else { console.log(data); } } var fs = require('fs'); fs.readFile('file.txt', 'utf-8', readFileCallBack); console.log('end.');
fs.readFile调用时所做的工作只是将异步式I/O请求发送给了操作系统,然后立即返回并执行后面的语句,执行完以后进入事件循环监听事件。当fs接收到I/O请求完成的事件时,事件循环会主动调用回调函数以完成后续工作。
Node.js所有的异步I/O操作在完成时都会发送一个事件到事件队列。在开发者看来,事件由EventEmitter对象提供。前面提到的fs.readFile和http.createServer的回调函数都是通过EventEmitter来实现的。
【码】event.js
//event.js var EventEmitter = require('events').EventEmitter; var event = new EventEmitter(); // 注册事件了事件some_event的一个监听器 event.on('some_event', function() { console.log('some_event occured.'); }); setTimeout(function() { // 发送事件some_event event.emit('some_event'); }, 1000);
Node.js在什么时候会进入事件循环呢?答案是Node.js程序由事件循环开始,到事件循环结束,所有的逻辑都是事件的回调函数,所以Node.js始终在事件循环中,程序入口就是事件循环第一个事件的回调函数。
【图】事件循环
模块是Node.js应用程序的基本组成部分,文件和模块是一一对应的。换言之,一个Node.js文件就是一个模块,这个文件可能是JavaScript代码、JSON或者编译过的C/C++扩展。
在前面的例子中,我们曾经用到了varhttp=require(‘http‘),其中http是Node.js的一个核心模块,其内部是用C++实现的,外部用JavaScript封装。我们通过require函数获取了这个模块,然后才能使用其中的对象。
Node.js提供了exports和require两个对象,其中exports是模块公开的接口,require用于从外部获取一个模块的接口,即所获取模块的exports对象。
require不会重复加载模块,也就是说无论调用多少次require,获得的模块都是同一个。
【码】loadmodule.js
var hello1 = require('./module'); hello1.setName('ichenxiaodoa'); hello1.sayHello(); var hello2 = require('./module'); hello2.setName('ichenxiaodao2'); hello1.sayHello(); hello2.sayHello(); /** * 因为hello1和hello2指向的是同一个实例, * 所以hello2.setName覆盖hello1.setName, * 最终输出 Hello ichenxiaodao2 */
有时候我们只是想把一个对象封装到模块中。
【码】singleobject.js
function Hello() { var name; this.setName = function(thyName) { name = thyName; }; this.sayHello = function() { console.log('Hello ' + name); }; }; exports.Hello = Hello;
此时我们在其他文件中需要通过require(‘./singleobject‘).Hello来获取Hello对象,这略显冗余,可以用下面方法稍微简化:
【码】hello.js
function Hello() { var name; this.setName = function(thyName) { name = thyName; }; this.sayHello = function() { console.log('Hello ' + name); }; }; module.exports = Hello;
这样就可以直接获得这个对象了:
【码】gethello.js
var Hello = require('./hello'); var hello = new Hello(); hello.sayHello(); hello.setName('ichenxiaodao'); hello.sayHello();
注意,模块接口的唯一变化是使用module.exports=Hello代替了exports.Hello=Hello。在外部引用该模块时,其接口对象就是要输出的Hello对象本身,而不是原先的exports。
事实上,exports本身仅仅是一个普通的空对象,即{},它专门用来声明接口,本质上是通过它为模块闭包的内部建立了一个有限的访问接口。因为它没有任何特殊的地方,所以可以用其他东西来代替,譬如我们上面例子中的Hello对象。
包是在模块基础上更深一步的抽象,Node.js的包类似于C/C++的函数库或者Java/.Net的类库。它将某个独立的功能封装起来,用于发布、更新、依赖管理和版本控制。Node.js根据CommonJS规范实现了包机制,开发了npm来解决包的发布和获取需求。
Node.js的包是一个目录,其中包含一个JSON格式的包说明文件package.json。严格符合CommonJS规范的包应该具备以下特征:
? package.json必须在包的顶层目录下;
? 二进制文件应该在bin目录下;
? JavaScript代码应该在lib目录下;
? 文档应该在doc目录下;
? 单元测试应该在test目录下。
Node.js对包的要求并没有这么严格,只要顶层目录下有package.json,并符合一些规范即可。当然为了提高兼容性,我们还是建议你在制作包的时候,严格遵守CommonJS规范。
【码】somepackage/index.js
exports.hello=function(){ console.log('Hello'); }
【码】getpackage.js
varsomepackage=require('./somepackage'); somepackage.hello();
在前面例子中的somepackage文件夹下,我们创建一个叫做package.json的文件,内容如下所示:
{ "main":"./lib/interface.js" }
然后将index.js重命名为interface.js并放入lib子文件夹下。以同样的方式再次调用这个包,依然可以正常使用。
Node.js在调用某个包时,会首先检查包中package.json文件的main字段,将其作为包的接口模块,如果package.json或main字段不存在,会尝试寻找index.js或index.node作为包的接口。
package.json是CommonJS规定的用来描述包的文件。详细的设置看这里。
本地安装:npm[install/i][package_name]
全局安装:npm[install/i]-g[package_name]
当我们要把某个包作为工程运行时的一部分时,通过本地模式获取,如果要在命令行下使用,则使用全局模式安装。
把全局包链接到本地:
在工程目录下运行:npmlink[package_name]
把本地包链接到全局:
在包目录中运行:npmlink
1、通过npminit交互式回答产生一个符合标准的package.json。
2、创建一个index.js作为包的借口。
3、使用npmadduser根据提示输入用户名、密码、邮箱,等待账号创建完成。
4、完成后可以使用npmwhoami测验是否已经取得了账号。
5、在package.json所在目录下运行npmpublish,稍等片刻就可以完成发布了。
如果该包日后有更新,只需要在package.json文件中修改version字段,然后重新使用npmpublish命令就行了。
如果你对已发布的包不满意,可以使用npmunpublish命令来取消发布。
命令行调试
nodedebug[script.js]
远程调试
node--debug[=port]script.js
node--debug-brk[=port]script.js
使用Eclipse调试Node.js
安装插件:ChromeDeveloper
使用node-inspector调试
1、安装node-inspector:npminstall-gnode-inspector
2、启动node-inspector
3、启动脚本:node--debug-brk=5858debug.js
4、在浏览器中打开http://127.0.0.1:8080/debug?port=5858
node-inspector使用了WebKitWebInspector,因此只能在Chrome、Safari等WebKit内核的浏览器中使用,而不支持Firefox或InternetExplorer。