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的包应该具备以下特征。

  1. package.json必须在包的顶层目录下
  2. 二进制文件应该在bin目录下
  3. JavaScript代码应该在lib目录下
  4. 文档应该在doc目录下
  5. 单元测试应该在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的定义,满足以下条件的是全局变量。

  1. 最外层定义的变量。
  2. 全局对象的属性。
  3. 隐式定义的变量(未定义直接同赋值的变量)

当你定义一个全局变量时,这个变量同时也会成为全局对象的属性,反之亦然。需要注意的是,在 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,仅仅是流控制和简单的消息解析,所有的高层功能都要通过它的接口来实现。

 

posted on 2017-09-03 08:48  mosmith  阅读(246)  评论(0编辑  收藏  举报