1.简介
-
是一个可以让 JavaScript 运行在服务器端的平台。它可以让 JavaScript 脱离浏览器的束缚运行在一般的服务器环境下,它摒弃了传统平台依靠多线 程来实现高并发的设计思路,而采用了单线程、异步式I/O、事件驱动式的程序设计模型
-
Node.js 内建了 HTTP 服务器支持,也就是说你可以轻而易举地实现一个网站和服务器的组合。这个服务器不仅可以用来调试代码,而且它本身就可以部署到产品环境,它 的性能足以满足要求
-
Node.js 还可以部署到非网络应用的环境下,比如一个命令行工具。Node.js 还可以调用 C/C++ 的代码,这样可以充分利用已有的诸多函数库,也可以将对性能要求非常高的部分用 C/C++ 来实现。
-
异步I/O 事件驱动
Node.js 进程在同一时 刻只会处理一个事件,完成后立即进入事件循环检查并处理后面的事件。这样做的好处是, CPU 和内存在同一时间集中处理一件事,同时尽可能让耗时的 I/O 操作并行执行。 -
Node.js 的架构
Node.js 用异步式 I/O 和事件驱动代替多线程,带来了可观的性能提升。Node.js 除了使 用 V8 作为JavaScript引擎以外,还使用了高效的 libev 和 libeio 库支持事件驱动和异步式 I/O。 图是 Node.js 架构的示意图。 Node.js 的开发者在 libev 和 libeio 的基础上还抽象出了层 libuv。对于 POSIX1操作系统, libuv 通过封装 libev 和 libeio 来利用 epoll 或 kqueue。而在 Windows 下,libuv 使用了 Windows的 IOCP(Input/Output Completion Port,输入输出完成端口)机制,以在不同平台下实现同样的高性能 -
CommonJs规范
CommonJS 规范包括了模块(modules)、包(packages)、系统(system)、二进制(binary)、 控制台(console)、编码(encodings)、文件系统(filesystems)、套接字(sockets)、单元测 试(unit testing)等部分,juejin.cn/post/684490…
2.创建HTTP服务
var http = require('http')
http.createServer(function( req, res ) {
res.writeHead( 200, {'Content-Type': 'text/html'});
res.write('<div>Node.js</div>')
res.end('<div>Hello Word</div>')
}).listen(9988)
这时候我们发现如果修改了代码页面不会热更新,supervisor可以实现这个功能,可以解决开发过程中调试问题
npm install -g supervisor
还有另外一种方法是 nodemon
npm install -g nodemon
安装好后直接运行命令,就可以监听到文件变化自动重启了
nodemon app.js
3.异步I/O与事件式编程
I/O操作:程在执行中如果遇到磁盘读写或网络通信
I/O操作通常要耗费较长的时间,这个时候操作系统会暂停这个线程,将资源让给其他线程,这种调度方式成为阻塞,当I/O操作完成时候此线程继续执行,这种操作方式为同步I/O,也是阻塞式I/O
异步I/O则是将I/O请求发送给操作系统 ,继续执行下一条语句,当I/O执行完毕,会以事件的形式通知执行I/O操作的线程,线程会在特定时间处理这个事件,所以线程必须要有事件循环,不断检测是否有未处理的事件,依次处理
var fs = require('fs')
fs.readFile('filename', function(err, data) {
// do something
})
console.log('end')
上面的例子,fs.readFile调用时候将异步I/O请求发送给操作系统,然后执行‘end’,执行完以后进入时间循环监听事件,当fs 收到I/O请求完成事件时候,事件循环会主动调用回调函数完成后续工作
4.事件
1.EventEmitter
Node.js 所有的异步 I/O 操作在完成时都会发送一个事件到事件队列。在开发者看来,事 件由 EventEmitter 对象提供。前面提到的 fs.readFile 和 http.createServer 的回 调函数都是通过 EventEmitter 来实现的。下面我们用一个简单的例子说明 EventEmitter 的用法
var event = require('events')
var EventEmitter = event.EventEmitter
var eventBus = new EventEmitter()
eventBus.on('eventName', function() {
console.log('finish event')
})
setTimeout( function() {
eventBus.emit('eventName')
}, 2000)
上面代码两秒后输出 finish event, event对象注册了事件 eventName的一个监听器,然后2秒后向event对象发送事件 eventName 此时会调用 监听器,
2.Node.js 的事件循环机制
Node.js 在什么时候会进入事件循环呢?
答案是 Node.js 程序由事件循环开始,到事件循环结束,所有的逻辑都是事件的回调函数,所以 Node.js 始终在事件循环中,程序入口就是 事件循环第一个事件的回调函数。事件的回调函数在执行的过程中,可能会发出 I/O 请求或 直接发射(emit)事件,执行完毕后再返回事件循环,事件循环会检查事件队列中有没有未 处理的事件,直到程序结束。Node.js的事件循环对开发者不可见 ,由 l i b e v 库 实 现 。l i b e v 支持多种类型的事件,如 ev_io、ev_timer、ev_signal、ev_idle 等,在 Node.js 中均被 EventEmitter 封装。libev 事件循环的每一次迭代,在 Node.js 中就是一次 Tick,libev 不 断检查是否有活动的、可供检测的事件监听器,直到检测不到时才退出事件循环,进程结束。
5. 模块和包
模块(Module)和包(Package)是 Node.js 最重要的支柱, Node.js提供了 require 函数来调用其他模块,而且模块都是基于文件的,机制十分简单,参考了CommonJs标准
1.什么是模块
模块是Node.js 应用程序的基本组成部分,文件和模块一一对应,一个node文件就是一个模块,这个文件可能是js 代码,JSON或者编译过的C/C++扩展。上面用到过的 http 就是一个模块,内部是用C++实现的,外部用js包装通过require获取到这个模块
2.创建和加载模块
在 Node.js 中,创建一个模块非常简单,因为一个文件就是一个模块,我们要关注的问 题仅仅在于如何在其他文件中获取这个模块。Node.js 提供了 exports 和 require 两个对 象,其中 exports 是模块公开的接口,require 用于从外部获取一个模块的接口,即所获 取模块的 exports 对象
//创建模块 moduleA.js 模块
var name
exports.setName = function( newName ) {
name = newName
}
exports.getName = function() {
console.log('name=' + name)
}
//引用模块
var moduleA = require('./moduleA.js')
moduleA.setName('outName')
moduleA.getName()
上面例子中 moduleA 中通过exports 把setName和getName作为模块的访问接口,
引入文件通过 require 引入模块
// 还可以用module.exports
var name
function setName (newName) {
name = newName
}
function getName() {
console.log( name )
}
module.exports = {
setName,
getName
}
不可以通过对 exports 直接赋值代替对 module.exports 赋值。 exports 实际上只是一个和 module.exports 指向同一个对象的变量, 它本身会在模块执行结束后释放,但 module 不会,因此只能通过指定 module.exports 来改变访问接口。
3.创建包
包是在模块基础上更深一步的抽象,Node.js 的包类似于 C/C++ 的函数库或者 Java/.Net 的类库。它将某个独立的功能封装起来,用于发布、更新、依赖管理和版本控制。Node.js 根 据 CommonJS 规范实现了包机制,开发了 npm来解决包的发布和获取需求
Node.js 的包是一个目录,其中包含一个 JSON 格式的包说明文件 package.json。严格符 合 CommonJS 规范的包应该具备以下特征:
-
package.json 必须在包的顶层目录下;
-
二进制文件应该在 bin 目录下;
-
JavaScript 代码应该在 lib 目录下;
-
文档应该在 doc 目录下;
-
单元测试应该在 test 目录下
// 创建一个package 文件夹 并创建一个lib文件夹并新建interface.js 文件
// interface.js exports.hello = function() { console.log('hello') }
package根目录下 创建 package.json
// package.json { "main": "./lib/interface.js" }
Node.js 在调用某个包时,会首先检查包中 package.json 文件的 main 字段,将其作为包的接口模块,如果 package.json 或 main 字段不存在,会尝试寻找 index.js 或 index.node 作 为包的接口。
package.json 是 CommonJS 规定的用来描述包的文件,完全符合规范的 package.json 文 件应该含有以下字段。
-
name: 包的名称,必须是唯一的,由小写英文字母、数字和下划线组成,不能包含 空格。description: 包的简要说明。
-
version: 符合语义化版本识别规范的版本字符串
-
keywords: 关键字数组,通常用于搜索
-
maintainers: 维护者数组,每个元素要包含 name、email (可选)、web (可选)字段。contributors: 贡献者数组,格式与maintainers相同。包的作者应该是贡献者数组的第一个元素。
bugs: 提交bug的地址,可以是网址或者电子邮件地址
licenses: 许可证数组,每个元素要包含 type(许可证名称) 和 url (链接到许可证文本的地址)
repositories: 仓库托管地址数组,每个元素包含 type(仓库类型如git),url(仓库地址)和path(相对于仓库的路径) -
bugs: 提交bug的地址,可以是网址或者电子邮件地址
-
licenses: 许可证数组,每个元素要包含 type(许可证名称) 和 url (链接到许可证文本的地址)
-
repositories: 仓库托管地址数组,每个元素包含 type(仓库类型如git),url(仓库地址)和path(相对于仓库的路径)
-
dependencies:包的依赖,一个关联数组,由包名称和版本号组成。
4.创建全局链接
npm 提供了一个有趣的命令 npm link,它的功能是在本地包和全局包之间创建符号链 接。我们说过使用全局模式安装的包不能直接通过 require 使用,但通过 npm link命令 可以打破这一限制
npm link express ./node_modules/express -> /usr/local/lib/node_modules/express
我们可以在 node_modules 子目录中发现一个指向安装到全局的包的符号链接。通过这 种方法,我们就可以把全局包当本地包来使用了
除了将全局的包链接到本地以外,使用 npm link命令还可以将本地的包链接到全局。 使用方法是在包目录( package.json 所在目录)中运行 npm link 命令。如果我们要开发 一个包,利用这种方法可以非常方便地在不同的工程间进行测试
5.包的发布
npm 可以非常方便的发布一个包,首先 需要让我们的包符合npm的规范,npm有一套以CommonJS为基础包规范,但与CommonJS 并不完全一致,其主要差别在于必填字段的不同
-
通过使用 npm init 可以根据交互式问答 产生一个符合标准的 package.json
-
在发布前,我们还需要获得一个账号用于今后维护自己的包,使用 npm adduser 根据 ,提示输入用户名、密码、邮箱,等待账号创建完成
-
完成后可以使用 npm whoami 测验是 否已经取得了账号。
-
接下来,在 package.json 所在目录下运行 npm publish,
-
如果你的包将来有更新,只需要在 package.json 文件中修改 version 字段,然后重新 使用 npm publish 命令就行了。
-
如果你对已发布的包不满意(比如我们发布的这个毫无意 义的包),可以使用 npm unpublish 命令来取消发布。
6.调试
1.命令行调试
在命令行下执行 node debug debug.js,将会启动调试工具:
2.使用 node-inspector 调试 Node.js
大部分基于 Node.js 的应用都是运行在浏览器中的,例如强大的调试工具 node-inspector。 node-inspector 是一个完全基于 Node.js 的开源在线调试工具,提供了强大的调试功能和友好 的用户界面,它的使用方法十分简便
首先,使用 npm install -g node-inspector 命令安装 node-inspector,然后在终 端中通过 node --debug-brk=5858 debug.js 命令连接你要除错的脚本的调试服务器, 启动 node-inspector:
node-inspector
在浏览器中打开 http://127.0.0.1:8080/debug?port=5858,即可显示出优雅的 Web 调试工 具
6.Node.js 核心模块
1.全局对象
-
global
-
process 是一个全局变量,即 global对象的属性,它是用于描述当前Node.js进程状态的对象,提供了一个与操作系统的简单接口,通常在写本地命令行程序的时候用到的比较多
process.argv是命令行的参数数组,第一个元素是node执行路径,第二个元素是脚本文件名,之后是传入的参数//如命令行输入 node app.js name=node age=20 sex=male //打印出来 process.argv 为 ['node执行文件路径', '执行文件的路径', 'name=node', 'age=20', 'sex=male']
2.常用工具 Util
-
util.inherits, 是一个实现对象原型继承的函数
//我们先创建一个Person类var util = require('util') function Person( name ) { this.name = name || 'person' this.age = '1991' this.sayHello = function() { console.log( 'Hello', this.name ) } } Person.prototype.showName = function() { console.log(this.name); } //然后创建一个sub类 function Sub() { this.name = 'sub' } util.inherits(Sub, Person) var person = new Sub() 这个时候打印person,发现Sub仅仅继承了定义在原型链上的函数,构造函数内部的方法和属性不会被继承 -
util.inspect ,可以将任意对象转化为字符串的方法,通常用于调试和错误分析
第一个参数是object
第二个参数是showHidden, true 或 false 是否输出更多隐藏信息
第三个参数是depth 表示最大递归层数,默认是2,null时候会完整递归遍历对象
第四个参数是color true 输出颜色会更漂亮 -
util.isArray()
-
util.isRegExp()
-
util.isDate()
3.事件驱动 events
7.HTTP服务端与客户端
1.HTTP服务端
http.Server是http模块中的HTTP服务器对象
-
request事件:当用户客户端请求来到,该事件被触发,提供两个参数 req 和 res,分别是http.ServerRequest 和 http.ServerResponse的实例,表示请求和响应信息
-
connection: 当TCP连接建立时候,改事件被触发,提供一个参数socket,为net.Socket实例
-
close: 当服务器关闭时候,该事件被触发
// 这是显式的写法,createServer是 封装之后的 request var http = require('http') var server = new http.Server() server.on('request', function( req, res ) { res.write('node') res.end('hello word') }) server.listen(3000)
http.ServerRequest 是HTTP请求的信息,**一般分为两部分请求头(Request Header )和请求体(Request Body),**请求头一般可以立即读取比较短,请求体相对较长,需要一定的时间传输,因此提供了3个事件用于控制请求体传输
-
data: 当请求体数据来到时,该事件被触发,该事件提供一个参数chunk,表示接收到的数据
-
end: 当请求体数据传输完成时,该事件被触发,之后就不会有数据再来了
-
close: 用户当前请求结束时候,该事件触发
-
httpVersion: HTTP协议版本
-
method: HTTP请求方法 GET.POST.PUT等
-
url: 原始的请求路径
-
headers: HTTP请求头
-
connection: 当前HTTP连接套接子
var http = require('http') http.createServer( function(req,res) { var post = '' req.on('data', function(chunk) { post += chunk }) req.on('end', function() { }) })
获取GET方法请求内容
Node.js 的url模块提供了parse 函数,用来解析get请求的路径
var http = require('http')
var url = require('url')
http.createServer(function(req, res ){
console.log( url.parse(req.url) )
res.end('finish')
})
获取POST请求内容
post请求内容全部在请求体中, node 提供了 querystring.parse(),可以格式化post过来的参数
var http = require('http')
var querystring = require('querystring')
http.createServer( function(req,res) {
var post = ''
req.on('data', function(chunk) {
post += chunk
})
req.on('end', function() {
post = querystring.parse(post)
res.end('finish')
})
}).listen(3000)
http.serverResponse是用来返回给客户端信息,也是由http.Server的request 事件发送的
一般有三个函数,用于返回响应头,响应内容,结束请求
- response.writeHead(statusCode, [headers]),向请求的客户端发送响应头,statusCode是HTTP状态码,200, 404等;headers表示响应头的每个属性
- response.write(data, [encoding]), 向请求的客户发送响应内容, data 是一个字符串或者Buffer,如果data是字符串要指定encoding来说明编码方式,默认 utf-8,可以被多次调用
- response.end(data, [encoding]): 结束响应,告知客户端所有发送已经完成,当所有要返回的内容发送完毕,必须被调用一次,如果不调用该函数,客户端将永远出于pedding状态
2.HTTP客户端
1.http模块提供了两个函数http.request 和 http.get, 作为客户端向http服务发起请求
-
http.request( options, callback ) 发起HTTP请求,options 请求参数,callback是请求的回调函数,options常用参数如下
1.host:请求网站的域名或者ip 2.port: 请求网站的端口,默认80 3.method: 请求方式,默认 GET 4.path: 请求相对于跟的路径,默认是 / 5.headers: 一个对象,为请求头内容
callback: 传递一个参数,为http.ClientResponse的实例,http.request返回一个http.ClientRequest的实例
//下面是POST请求 聚合数据的ip查询接口
var http = require('http')
var querystring = require('querystring')
var params = querystring.stringify({
IP: '你想查的IP'
})
var options = {
host: 'aps.juhe.cn',
path: '/ip/Example/query.php',
method: 'POST',
headers: {
'Content-Type': 'application/x-www.form-urlencoded;charset=UTF-8'
}
}
var req = http.request( options, functioin( res ) {
res.setEncoding('utf8')
res.on('data', function(data) {
})
})
req.write( params )
req.end()
2. http.get( options, callback ) http模块还提供了快捷GET请求
var http = require('http')
let options = {
host:'',
path: '',
headers: {},
qs: {} //get请求参数
}
http.get( options, function( res ) {
res.setEncoding('utf8')
res.on('data', function( data ) {
})
})
8.模块加载机制
Node.js模块可以分为两大类,核心模块,文件模块
-
核心模块就是自带的API模块,如fs, http等,能通过require直接获取,如果文件模块与核心模块命名冲突,总会加载核心模块
-
文件模块是存储的单独的文件,可能是js代码,JSON或者C/C++,
加载方式有两种,一个是按路径加载,一种是查找node_modules 文件夹如果 require 以 / 开头,就会以绝对路径的方式查找模块,
如require('/home/module'),
这个时候按照优先级依次尝试加载
/home/module.js
/home/module.json
/homt/module.node如果require 以 ./ 或者 ../ 开头,
那么就要以相对路径方式来查找模块,通常是自己扩展的模块**通过查找node_modules目录加载模块
**如果不以相对路径和绝对路径开头,而模块又不是核心模块,那么就要通过node_modules加载模块了,我们使用npm获取的包通常都是以这种方式加载的
模块加载顺序总结如下
- 如果module是一个核心模块,直接加载,结束
- 如果是以 / ./ ../ 开头,按路径加载模块,结束
- 假设当前目录为dir,按路径加载 dir/node_modules/module
如果加载成功,结束
如果加载失败,令dir为其父目录
重复这个过程,直到根目录