nodejs学习笔记
Hello Fuck如下:
console.log('Hello, Fuck you, NodeJs'); # node Helloworld.js Hello, Fuck you, NodeJs
事件:
Node.js所有的异步i/o操作在完成时都会发送一个事件到事件队列,事件由EventEmitter对象来提供,前面提到的fs.readFile和http.createServer的回调函数都是通过EventEmitter来实现的。
//event.js var EventEmitter = require('events').EventEmitter; var event = new EventEmitter(); event.on('some_event', function() { console.log('some_event occurred.'); setTimeout(function(){ event.emit('some_event'); },1000); }); setTimeout(function() { event.emit('some_event'); },1000); console.log('end?'); # node event.js end? some_event occurred. some_event occurred. some_event occurred. ^C
nodejs不停监测是否有活动的事件监听器比如i/o, timer等,一旦发现没有活动的事件监听器,nodejs进程将退出。
模块
模块是Node.js的基本组成部分,文件和模块是一一对应的,换言之,一个Node.js文件就是一个模块,这个文件可能是JavaScript代码,JSON或者编译过的c/c++扩展。
前面章节的例子中,我们曾经用到了类似于var http=require('http'),其中http就是一个核心模块,其内部是用c++来实现的,外部使用javascript来进行封装。通过require函数获取了这个模块之后,然后才能使用其中的对象。
创建,加载模块
在Node.js中,创建一个模块非常单简单,因为一个文件就是一个模块,我们关注的问题仅仅在于如何在其它文件中获取这个模块,Node.js提供了exports和require两个对象,其中exports是模块公开的接口,而require用于从外部获取一个模块的接口,也就是获取返回的exports对象。
// module.js var name; exports.setName=function(theName) { name=theName; } exports.sayHello=function() { console.log('Fuck you ' + name); } // getModule.js var myModule=require('./module') //注意这里需要./前缀,因为是相对当前工作目录的。 myModule.setName('Mosmith'); myModule.sayHello(); # node getModule.js Hello Mosmith
单次加载,这个有点类似于创建一个对象,但实际上和对象又有本质的区别,因为require不会重复加载模块,也就是说无论调用多少次require,获得的模块都是同一个。
覆盖exports,有时候我们只是想将一个对象封闭到一个模块中,例如:
// module.js function Hello() { var name; this.setName=function(_name) { this.name=_name; } this.sayHello=function() { console.log("Hello " + this.name); } } exports.Hello=Hello; // override exports object //module.exports=Hello;
// getModule.js
var Hello = require('./module.js').Hello;
var hello=new Hello();
hello.setName('Mosmith');
hello.sayHello();
需要注意的是,不可以通过对 exports 直接赋值代替对 module.exports 赋值。exports 实际上只是一个和 module.exports 指向同一个对象的变量,它本身会在模块执行结束后释放,但 module 不会,因此只能通过指定module.exports 来改变访问接口。
包是在模块的基础上更深一步的抽像,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,并符合一些规范即可。但最好是符合规范。Node.js在调用某个包是,先会去检查包中的package.json中的main字段,将其作为包的接口模块,如果package.json或者main字段不存在,那么会尝试将寻找index.js或者index.node作为包的接口。
package.json是CommonJS规定用于描述包的文件,完整的包应该含有以下字段:
name:包的名称,必须是唯一的由小写字母,数字,和下划线组成,不能有空格。
description:包的简要说明。
version:符合语义化版本识别规范的字符串
maintainer:维护者数组,第个元素要包含name, email(可选), web(可选)
contributors:贡献者数组,格式与maintainer相同,包的作者应该是都数组的第一个元素。
bugs提交bug的地址。
licenses许可证数组,每个元素要包含type的url
repositories仓库托管地址数组,每个元素要包含type(仓库的类型比如git),url(仓库的地址)和path(相对于仓库的路径,可选)字段。
dependencies:包的依赖,一个关联数组,由包的名称和版本号组成。
下面是一下符合CommonJS规范的package.json示例:
{ "name": "mypackage", "description": "Sample package for CommonJS. This package demonstrates the required elements of a CommonJS package.", "version": "0.7.0", "keywords": [ "package", "example" ], "maintainers": [ { "name": "Bill Smith", "email": "bills@example.com", } ], "contributors": [ { "name": "BYVoid", "web": "http://www.byvoid.com/" } ], "bugs": { "mail": "dev@example.com", "web": "http://www.example.com/bugs" }, "licenses": [ { "type": "GPLv2", "url": "http://www.example.org/licenses/gpl.html" } ], "repositories": [ { "type": "git", "url": "http://github.com/BYVoid/mypackage.git" } ], "dependencies": { "webkit": "1.2", "ssl": { "gnutls": ["1.0", "2.0"], "openssl": "0.9.8" } } }
npm是Node.js的包管理工具,它已经成为Node.js包的标准发布平台。用于Node.js包的发布,传播,依赖控制。
npm install/i package_name比如要安装express
npm install express或者npm i express
在项目目录下运行npm install 将下载并安装package.json中的依赖。
同时npm还会自动解析其依赖,并获取express依赖的mime,mkdirp,qs的connect等。
注意:express4.x中将命令工具分离出来了,所有需要先装express-generator
在新建一个nodejs工程的时候我们可以使用npm init命令来进行初始化package.json,它的功能类似于maven archetype:generate
本地模块与全局模式
默认情况下npm会从http://npmjs.org搜索并下载包,并将包安装到当前目前的node_modules子目录下面。也就是本地模式。
另外npm可以以全局模式安装(使用-g参数),使用方法为:
npm install/i -g packageName
但需要注意的是,全局模式下可能会造成冲突,因为别的nodejs程序可能需要另外版本的包。
本地模式下npm不会注册环境变量,而全局模式下会注意环境变量,并将包安装到系统目录比如/usr/local/lib/node_modules,同时package.json文件中的bin字段包含的文件会被链接到/usr/local/bin。/usr/local/bin是在PATH环境变量中默认定义的,因此就可以直接在命令中运行像supervisor的模块了。但全局模式安装的package不能通过require来使用,因为require不会去搜索/usr/local/lib/node_modules目录。
创建全局链接
npm提供了一个npm link命令,它的功能是在本地包和全局包之间创建符号链接,我们说过使用全局模式安装的包不能通过require来使用,但通过npm link命令可以绕过这个限制。比如:
npm link express
这里可以在node_modules子目录发现一个安装到全局的包的符号链接。但这个命令不能在windows下来使用。
调试。
node debug debug.js
node --debug[=port] script.js 然后在另一个终端node debug localhost:debug_port或者使用IDE来进行远程调试。
全局对象
JavaScript中有一个特殊的对象,称为全局对象,它所有的属性可以在程序的任何地方访问,也就是说全局变量,在浏览器的JavaScript中,通常window是全局对象,而Node.js中的全局对象是global,所有的全局变量(除了global本身以外),都是global对象的属性。我们在Node.js中能够直接访问到对象通过都是global的属性比如console, process等。
全局对象与全局变量
global最根本的作用是作为全局变量的宿主,按照ECMAScript的定义,满足以下条件的是全局变量。
- 最外层定义的变量。
- 全局对象的属性。
- 隐式定义的变量(未定义直接同赋值的变量)
当你定义一个全局变量时,这个变量同时也会成为全局对象的属性,反之亦然。需要注意的是,在 Node.js 中你不可能在最外层定义变量,因为所有用户代码都是属于当前模块的,而模块本身不是最外层上下文,提倡永远使用 var 定义变量以避免引入全局变量,因为全局变量会污染命名空间,提高代码的耦合风险。
process
process是一个全局变量,即global对象的一个属性,它用于描述当前Node.js进程的状态,提供了一个与操作系统的简单接口。写一些本地命令行的程序的时候经常需要和它打交道的。
process.argv是命令行参数数组,第一个元素是node,第二个元素是脚本文件名,从第三个元素开始每个元素是一个运行参数。
process.stdout是标准输出流,通常我们使用的console.log向标准输出打印字符,而process.stdout.write()函数则提供更加底层的接口。
process.stdin是标准输入流,初始时它是被暂停的,想要从标准输入读取数据你必须恢复流,并手动编写流的事件响应函数。比如下面:
process.nextTick(callback)的功能是为事件循环设置一项任务,Node.js会在下一次事件循环时调用callback。Node.js 适合 I/O 密集型的应用,而不是计算密集型的应用,因为一个 Node.js 进程只有一个线程,因此在任何时刻都只有一个事件在执行。如果这个事件占用大量的 CPU 时间,执行事件循环中的下一个事件就需要等待很久,因此 Node.js 的一个编程原则就是尽量缩短每个事件的执行时间。 process.nextTick() 提供了一个这样的工具,可以把复杂的工作拆散,变成一个个较小的事件。
process.stdin.on('data', function(data) { process.nextTick(function() { process.stdout.write('do something very time-consuming'); process.stdout.write(data); }); });
需要setTimeout也可以达到类似的作用,但setTimeout效率很低,回调不能被及时执行。
除了上面几个比较常用的成员,除些之后有process.platform,process.pid,process.execPath,process.memoryUsage(),以及POSIX进程信号响应机制。
console
console用于提供控制台标准/错误输出:
console.log()向标准输出流打印字符并以换行符结束。console.log接受若干个参数,如果只有一个参数,则输出这个参数的字符串形式,如果有多个参数,则类似于c语言的printf命令的格式化输出。
console.log("Helloworld"); => Helloworld
console.log("Helloworld%s"); => Helloworld%s console.log("Helloworld %s", Mosmith); => Helloworld Mosmith
console.error()与console.log相同,只不过console.error()向标准错误流输出。
console.trace()用于向标准错误流输出当前的调用栈。
常用工具util
util是Node.js的核心模块,提供常用函数的集合,用于弥补核心JavaScript的功能过于精简的不足。
util.inherits(constructor, superConstructor)是一个实现对象间原型继承的函数,JavaScript的面向对象我是基于原型的,与常见的基于类不同,JavaScript并没有提供对象继承的语言级别特性,而是通过原型复制来实现的,具体细节我们在附录A中说明,这里我们只介绍util.inerits的用法,示例如下:
var util=require('util') function Base() { this.name='base'; this.base=1991; this.sayHello = function() { console.log('Hello ' + this.name); } } Base.prototype.showName=function() { console.log(this.name); } function Sub() { this.name='sub'; } util.inherits(Sub,Base); var objBase=new Base(); objBase.showName(); objBase.sayHello(); console.log(objBase); var objSub=new Sub(); objSub.showName(); // This is undefined in Sub // objSub.sayHello(); console.log(objSub);
util.inspect
util.inspect(object, [showHidden],[depth],[colors])是一个将任意对象转换为字符串的方法,通常用于调试和错误输出,它至少一个参数object,即要转换的对象,showHidden是一个可选的参数,如果值为true,将会输出更多的参数,depth表示最大的递归层数,如果对象很复杂,你可以指定层数以控制输出信息的多少,默认情况下递归两层,为null的情况下则不限层数。
事件驱动events
events是Node.js的最重要的模块,Node.js本身就是依赖于event实现事件驱动的,而它提供了唯一的接口。events模块不仅仅用于用户代码与Node.js下层事件循环的交互,还几乎被所有的模块依赖。
事件发射器
events模块只提供了一个对象,events.EventEmitter, EventEmitter的核心就是事件发射与事件监听器功能的封装。EventEmitter的每一个事件由一个事件和若干个参数组成,事件名是一个字符串,通常表达一定的语义,对于每个事件,EventEmitter支持若干个事件监听器,当事件发射时,注册到这个事件的事件监听器将被依次调用,事件参数作为回调函数传递。
// eventEmitterTest.js
var events=require('events'); var emitter=new events.EventEmitter(); emitter.on('someEvent',function(arg1,arg2){ console.log('listener1 invoked',arg1,arg2); }); emitter.on('someEvent', function(arg1,arg2) { console.log('listener2 invoked',arg1,arg2); }); emitter.emit('someEvent','argument1',2017); $ node eventEmitterTest.js listener1 invoked argument1 2017 listener2 invoked argument1 2017
- EventEmitter.on(event, listener) 为指定事件注册一个监听器,接受一个字符串 event 和一个回调函数 listener 。
- EventEmitter.emit(event, [arg1], [arg2], [...]) 发射event事件,传递若干可选参数到事件监听器的参数表。
- EventEmitter.once(event, listener) 为指定事件注册一个单次监听器,即监听器最多只会触发一次,触发后立刻解除该监听器。
- EventEmitter.removeListener(event, listener) 移除指定事件的某个监听器, listener 必须是该事件已经注册过的监听器。
- EventEmitter.removeAllListeners([event]) 移除所有事件的所有监听器,如果指定 event ,则移除指定事件的所有监听器
error事件
EventEmitter定义了一个特殊的事件error,它包含了’错误‘的语义,我们在遇到异常的时候通常会发射error事件。当error被发射时,EventEmitter规定如果没有响应的监听器,那么Node.js会将它当作异常,退出程序并打印调用栈。因此我们一般要为会发射error事件的对象设置监听器,避免遇到错误后整个程序崩溃。比如:
var events = require('events'); var emitter = new events.EventEmitter(); emitter.emit('error'); 运行时会显示以下错误: node.js:201 throw e; // process.nextTick error, or 'error' event on first tick ^ Error: Uncaught, unspecified 'error' event. at EventEmitter.emit (events.js:50:15) at Object.<anonymous> (/home/byvoid/error.js:5:9) at Module._compile (module.js:441:26) at Object..js (module.js:459:10) at Module.load (module.js:348:31) at Function._load (module.js:308:12) at Array.0 (module.js:479:10) at EventEmitter._tickCallback (node.js:192:40)
继承EventEmitter
大多数时候我们不会直接使用 EventEmitter ,而是在对象中继承它。包括 fs 、 net 、http 在内的,只要是支持事件响应的核心模块都是 EventEmitter 的子类。为什么要这样做呢?原因有两点。首先,具有某个实体功能的对象实现事件符合语义,事件的监听和发射应该是一个对象的方法。其次 JavaScript 的对象机制是基于原型的,支持部分多重继承,继承 EventEmitter 不会打乱对象原有的继承关系。
文件系统fs
fs模块是文件操作的封闭,它提供了文件的读取,写入,更名,删除,遍历目录,链接等POSIX文件操作,与其它模块不同的是,fs模块所有的操作都有同步和异步两个版本,例如读取文件内容的函数有异步的fs.readFile()也有同步的fs.readFileSync
fs.readFile(filename, [encoding], [callback(err,data)])是最简单的读取文件的函数,它接受一个必选的参数filename,如果不提供encoding,那么将以二进制方式打开,如果指定了encoding,data是一个解析后的字符串。
fs.readFIleSync是同步版本,它没有callback参数,data通过返回值获取,而错误则需要通过try catch来进行捕捉处理。
fs.open(path, flag, [mode], [callback(err,fd])]是POSIX open函数的封装,与c和fopen类似,它两个必选参数,path为文件路径,flag是表示以什么方式打开文件,mode参数用于创建文件时给文件指定权限,默认是0666,回调函数将传递一个错误参数,以及一个文件描述符fd
fs.read(fd,buffer, offset, length, position, callback(err, byteRead, buffer)]是POSIX read函数的封闭,相比于fs.readFile, 它提供了更加底层的接口,fs.read功能是从指定的文件描述符fd中读取数据并写入buffer指定的缓冲区对象,offset是buffer的写入偏移量,length是要从文件中读取的字节数,position是文件读取的起始位置,如果position的值为null,则会从当前文件指针的位置读取,回调函数传递byteRead和buffer,分别表示读取的字节数和缓冲区对象。
其中有两个全局变量:
__dirname:全局变量,存储的是文件所在的文件目录
__filename:全局变量,存储的是文件名
http模块
Node.js标准库里面提供了http模块,其中Node.js 标准库提供了 http 模块,其中封装了一个高效的 HTTP 服务器和一个简易的HTTP 客户端。 http.Server 是一个基于事件的 HTTP 服务器,它的核心由 Node.js 下层 C++部分实现,而接口由 JavaScript 封装,兼顾了高性能与简易性。 http.request 则是一个HTTP 客户端工具,用于向 HTTP 服务器发起请求,例如实现 Pingback 1 或者内容抓取
http.Server
http.Server是http模块中的HTTP服务器对象,用Node.js做的所有基于HTTP协议的系统如网站,社交应用,甚于代理服务器,都是基于http.Server来实现的,它提供了一套封装级别很低的API,仅仅是流控制和简单的消息解析,所有的高层功能都要通过它的接口来实现。