Node.js适用场景
Node.js 擅长处理 I/O,不善于计算(单线程的缺点),因此 Node.js 适用于:当应用程序需要处理大量并发的 I/O,而在向客户端发出响应之前,应用程序内部并不需要进行非常复杂的处理的时候,Node.js 也非常适合与 web socket 配合,开发长连接的实时交互应用程序。
比如:聊天室,博客系统,考试系统等。
全局对象
在 JavaScript 中全局对象通常是 window,而在 Node.js 中全局对象是 globa,所有全局变量(除了 global 本身以外)都是 global 对象的属性,我们可以直接访问到 global 的属性
全局变量
按照 ECMAScript 的定义,满足以下条 件的变量是全局变量:
-
在最外层定义的变量
-
全局对象的属性
-
隐式定义的变量(未定义直接赋值的变量)
当你定义一个全局变量的时候,这个变量同时也会成为全局对象的属性,反之亦然。在 Node.js 中你不可能在最外层定义变量,因为所有用户代码都是属于当前模块的,而模块本身不是最外层上下文。定义变量一定要使用 var 关键字,因为全局变量会污染命名空间。
常见全局变量和全局函数
- __filename 表示当前正在执行的脚本的文件名。它将输出文件所在位置的绝对路径,且和命令行参数所指定的文件名不一定相同。如果在模块中,返回的值是模块文件的路径。
- __dirname,表示当前执行脚本所在的目录。
- setTimeout(cb, ms) 全局函数在指定的毫秒(ms)数后执行指定函数(cb),只执行一次函数。
require方法–创建hello world
Node中的JavaScript可以具备文件操作能力,得益于丰富的API。
引入核心模块fs
,调用相应的API即可实现
1 | var fs = require('fs')//用require方法创建实例 |
1 | var http = require('http'); |
http.createServer([requestListener])
其中requestListener 请求函数
是一个自动添加到 ‘request’ 事件的函数。函数传递有两个参数:request 请求对象 和 response 响应对象。
response 对象常用的方法有:
-
response.writeHead(statusCode[, statusMessage][, headers])。表示向请求发送响应头。
-
response.write() 发送一块响应主体,也就是说用来给客户端发送响应数据。可以直接写文本信息,也可以写我们的 html 代码,注意要设置 Content-Type 的值 。write 可以使用多次,但是最后一定要使用 end 来结束响应,否则客户端会一直等待。
-
response.end() 此方法向服务器发出信号,表示已发送所有响应头和主体,该服务器应该视为此消息完成。必须在每个响应上调用方法 response.end()。
request对象的常用方法:
-
request.url 获取请求路径,获取到的是端口号之后的那一部分路径,也就是说所有的 url 都是以 / 开头的,判断路径处理响应。
-
request.socket.localAddress 获取 ip 地址。
-
request.socket.remotePort 获取源端口。
Node.js包
用于管理多个模块及其依赖关系,可以对多个模块进行封装,包的根目录必须包含 package.json 文件,package.json 文件是 CommonJS 规范用于描述包的文件,符合 CommonJS 规范的 package.json 文件一般包含以下字段:
- name:包名。包名是唯一的,只能包含小写字母、数字和下划线。
- version:包版本号。
- description:包说明。
- keywords:关键字数组,用于搜索。
- homepage:项目主页。
- bugs:提交 bug 的地址。
- license:许可证。
- maintainers:维护者数组。
- contributors:贡献者数组。
- repositories:项目仓库托管地址数组。
- dependencies:包依赖。这个属性十分重要,NPM会通过这个属性,帮你自动加载依赖的包
packae.json包示例
1 | { |
注:
package.json 文件可以自己手动编辑,还可以通过 npm init 命令进行生成。你可以自己尝试在终端中输入 npm init 命令来生成一个包含 package.json 文件的包。直接输入 npm init --yes 跳过回答问题步骤,直接生成默认值的 package.json 文件。
安装包
npm install express
更新包
npm update express
删除包
npm uninstall express
Node.js模块
在 JavaScript 中,我们通常把 JavaScript 代码分为几个 js 文件,然后在浏览器中将这些 js 文件合并运行,但是在 Node.js 中,是通过以模块为单位来划分所有功能的,每一个模块为一个 js 文件,每一个模块中定义的全局变量和函数的作用范围也被限定在这个模块之内,只有使用 exports 对象才能传递到外部使用。
▶1.js
1 | function foo(){ |
▶2.js
1 | var hello = require('./1.js'); |
require 加载模块,以’/’ 为前缀的模块是文件的绝对路径。’./’ 为前缀的模块是相对于调用 require() 的文件的,上面的例子中 index.js 和 myModule.js 是在同一个目录下(project 目录)。当没有以 ‘/’、’./’ 或 ‘…/’ 开头来表示文件时,这个模块必须是一个核心模块或加载自 node_modules 目录。
module.exports 和 exports 的区别
每次导出接口成员的时候都通过 module.exports.xxx = xxx 的方式很麻烦。所以,Node.js 为了简化你的操作,专门提供了一个变量:exports 等于 module.exports。也就是说在模块中还有这么一句代码:
var exports = module.exports;
让我们看一下它们
1 | a = { |
运行结果:
1 | exports = { |
运行结果:
也就是说给 exports 赋值会断开和 module.exports 之间的引用,同样的给 module.exports 重新赋值也会断开它们之间的引用。但是最终导出的是 module.exports。而如果给其他变量赋值则没有对exports 和 module.exports之间的引用造成改变。即module.exports才是真正的接口,exports只不过是它的一个辅助工具。最终返回给调用的是module.exports而不是exports
总结:
require 得到的是 module.exports 导出的值,导出多个成员可以用 module.exports 和 exports,导出单个成员只能用 module.exports。
Node.js函数
在 JavaScript 中,一个函数可以作为另一个函数的参数。我们可以先定义一个函数,然后把函数作为变量在另一个函数中传递,也可以在传递参数的地方直接定义函数。
1 | function sayHi(value){ |
匿名函数
1 | var fun = function(){ |
箭头函数
1 | (参数1, 参数2, …, 参数N) => { 函数声明 } |
示例:
1 | var fun = function(){ |
Node.js 异步编程
Node.js 异步编程的直接体现就是回调。回调函数在完成任务后就会被调用,Node.js 使用了大量的回调函数,Node.js 所有 API 都支持回调函数。回调函数一般作为函数的最后一个参数出现。
阻塞代码
1 | var fs = require('fs'); |
运行结果:
非阻塞代码
1 | var fs = require('fs'); |
运行结果:
第一个实例在文件读取完后才执行完程序。 第二个实例我们不需要等待文件读取完,这样就可以在读取文件时同时执行接下来的代码,大大提高了程序的性能。
异步打开文件
fs.open(path, flags[, mode], callback)
参数说明:
-
path:文件的路径
-
flags:文件打开的行为。
-
mode:设置文件模式(权限),文件创建默认权限为 0o666(可读写)。mode 设置文件模式(权限和粘滞位),但仅限于创建文件的情况。在 Windows 上,只能操作写权限。
-
callback:回调函数,带有两个参数如:callback(err, fd)。
-
flags 参数可以是以下值:
‘a’ - 打开文件用于追加。如果文件不存在,则创建该文件。
‘ax’ - 与 ‘a’ 相似,但如果路径存在则失败。
‘a+’ - 打开文件用于读取和追加。如果文件不存在,则创建该文件。
‘ax+’ - 与 ‘a+’ 相似,但如果路径存在则失败。
‘as’ - 以同步模式打开文件用于追加。如果文件不存在,则创建该文件。
‘as+’ - 以同步模式打开文件用于读取和追加。如果文件不存在,则创建该文件。
‘r’ - 打开文件用于读取。如果文件不存在,则会发生异常。
‘r+’ - 打开文件用于读取和写入。如果文件不存在,则会发生异常。
‘rs+’ - 以同步模式打开文件用于读取和写入。指示操作系统绕开本地文件系统缓存。这对于在 NFS 挂载上打开文件非常有用,因为它允许跳过可能过时的本地缓存。 它对 I/O 性能有非常实际的影响,因此除非需要,否则不建议使用此标志。这不会将 fs.open() 或 fsPromises.open() 转换为同步的阻塞调用。 如果需要同步操作,则应使用 fs.openSync() 之类的操作。
‘w’ - 打开文件用于写入。创建文件(如果它不存在)或截断文件(如果存在)。
‘wx’ - 与 ‘w’ 相似,但如果路径存在则失败。
‘w+’ - 打开文件用于读取和写入。创建文件(如果它不存在)或截断文件(如果存在)。
‘wx+’ - 与 ‘w+’ 相似,但如果路径存在则失败。
异步关闭文件
fs.close(fd, callback)
参数说明:
-
fd:通过 fs.open() 方法返回的文件描述符。
-
callback:回调函数,除了可能的异常,完成回调没有其他参数。
1 | var fs = require("fs"); |
读写文件
**异步读取文件的语法格式为:
fs.read(fd, buffer, offset, length, position, callback)
参数说明:
-
fd: 通过 fs.open() 方法返回的文件描述符。//不能变成路径!
-
buffer:是数据写入的缓冲区。
通常用var buf= Buffer.alloc(1024)创建1024字节的缓冲区然后传参
-
offset:是缓冲区中开始写入的偏移量。一般它的值我们写为 0。
-
length:是一个整数,指定要读取的字节数。
-
position:指定从文件中开始读取的位置。 如果 position 为 null,则从当前文件位置读取数据,并更新文件位置。
-
callback:回调函数,有三个参数 err, bytesRead, buffer。err 为错误信息, bytesRead 表示读取的字节数,buffer 为缓冲区对象。
示例:
1 | var fs = require("fs"); |
异步写入文件的语法格式为:
fs.write(fd, buffer, offset, length, position, callback)
参数说明:
-
fd:从指定的文件写入数据。
-
buffer:是数据写入的缓冲区。
-
offset:指定要写入的 buffer 部分。
-
length:是一个整数,指定要写入的字节数。
-
position 指定应该写入此数据的文件开头的偏移量。 如果 typeof position !== ‘number’,则从当前位置写入数据。
-
callback:回调有三个参数 (err, bytesWritten, buffer),其中 bytesWritten 指定从 buffer 写入的字节数。
1 | var fs = require('fs'); |
或
fs.write(fd, string[, position[, encoding]], callback)
参数说明:
-
fd:从指定的文件写入数据。
-
string:写入的数据,如果不是字符串,则该值将被强制转换为字符串。
-
position 指定应该写入此数据的文件开头的偏移量。 如果 typeof position !== ‘number’,则从当前位置写入数据。
-
encoding:指定字符串的编码,默认为 ‘utf8’。
-
callback:回调有三个参数 (err, written, string),其中 written 指定字符串中已写入文件的字节数。 写入的字节数与字符串的字符数是不同的。
1 | var fs = require('fs') |
以上方法都是基于fs.open()函数
的,那么有没有简单粗暴点的函数呢?
Yeap!
fs.readFile(path,[options], callback)
闪亮登场
法一:
1 | var fs = require('fs') |
法二:
1 | var fs = require('fs'); |
fs.writeFile(file, data,[options], callback)
参数说明:
- file:文件名或文件描述符。
- data:要写入文件的数据,可以是 String(字符串) 或 Buffer(缓冲) 对象。
- options:该参数是一个对象,包含 {encoding, mode, flag}。encoding 默认值为:‘utf8’, mode 默认值为 0o666 ,flag 默认为 ‘w’。
- callback:回调函数。
示例:
1 | var fs = require('fs'); |
专门的异步追加函数fs.appendFile(path, data[, options], callback)
1 | fs.appendFile('message.txt', '追加的数据', (err) => { |
如果 options 是字符串,则它指定字符编码:
fs.appendFile('message.txt', '追加的数据', 'utf8', callback);
截取文件(将文件只保留前n个字节)
fs.ftruncate(fd[, len], callback)
参数说明:
-
fd:通过 fs.open() 方法返回的文件描述符。
-
len:文件内容截取的长度,默认为 0。
-
callback:除了可能的异常,完成回调没有其他参数。
1 | var fs = require('fs') |
删除文件
fs.unlink(path, callback)
参数说明:
-
path:文件路径。
-
callback: 除了可能的异常,完成回调没有其他参数。
修改文件名
fs.rename(oldPath, newPath, callback)
参数说明:
-
oldPath:原来的文件名字。
-
newPath:新的文件名字。
-
callback:回调函数,除了可能的异常,完成回调没有其他参数。
目录操作
新建目录
fs.mkdir(path[, options], callback)
参数说明:
-
path:文件路径(名)。
-
options:有两个参数。recursive 表示是否以递归的方式创建目录,默认为 false。mode 设置目录权限,Windows 上不支持。默认为 0o777。
-
callback:回调函数,除了可能的异常,完成回调没有其他参数。
读取目录
fs.readdir(path[, options], callback)
参数说明:
-
path:文件路径。
-
options:有两个参数 encoding,withFileTypes。encoding 默认值为 ‘utf8’,withFileTypes 默认值为 false。
-
callback - 回调函数,回调函数带有两个参数 err, files。err 为错误信息,files 为目录下的文件数组列表。
删除目录
fs.rmdir(path, callback)
Node.js事件
大多数 Node.js 核心 API 构建于惯用的异步事件驱动架构,其中某些类型的对象(又称触发器,Emitter)会触发命名事件来调用函数(又称监听器,Listener)。比如:fs.readStream 打开文件时会发出一个事件。可以通过 require(“events”); 获得 event 模块。通常,事件名采用“驼峰式”(即单词首字母大写,其他字母小写)命名方式。
EventEmitter
所有能触发事件的对象都是 EventEmitter 类的实例。这些对象有一个 eventEmitter.on() 函数,用于将一个或多个函数绑定到命名事件上。当 EventEmitter 对象触发一个事件时,所有绑定在该事件上的函数都会被同步地调用。
** EventEmitter类获取**
1 | // 引入 events 模块 |
添加监听器
emitter.on(eventName, listener)
使用 emitter.on(eventName, listener) 方法为指定事件注册一个监听器。添加 listener 函数到名为 eventName 的事件的监听器数组的末尾。
别名 emitter.addListener(eventName, listener)
注意,这种注册监听器的方法将监听器一直保留在内存中
参数说明:
- eventName:事件名称,string 类型。
- listener:回调函数。
示例
1 | //引入 events 模块 |
运行结果:
其原理是emitter
对象给connection
事件注册了监听器,用setTimeout函数1000毫秒后向emitter
对象发送事件connection
,此时调用它的监听器。emit
方法就是发送事件的。
使用emitter.emit(eventName[, ...args])
按照监听器注册的顺序,同步地调用每个注册到名为eventName
的事件的监听器,并传入提供的参数。如果事件有注册监听返回 true,否则返回 false。
参数说明:
- eventName :事件名称
- args:传递的参数,多个,类型为任意。
默认情况下,事件监听器会按照添加的顺序依次调用。emitter.prependListener()
方法可用于将事件监听器添加到监听器数组的开头。
例如在setTimeout
前添加,emitter.prependListener('connection',() =>console.log('hh'));
,将会先打印hh,再打印已连接。
eventEmitter.once(eventName, listener)
此可以注册最多可调用一次的监听器。 当事件被触发时,监听器会被注销,然后再调用。
而emitter.prependOnceListener()
方法可用于将事件监听器添加到监听器数组的开头。
移除监听器
emitter.removeListener(eventName, listener)
参数说明:
- eventName 事件名称
- listener 监听器也就是回调函数名称。
注:removeListener() 最多只会从监听器数组中移除一个监听器。我们可以多次调用 removeListener() 的方式来一个个的移除我们需要移除掉的监听器。
emitter.off(eventName, listener)是emitter.removeListener()的别名
emitter.removeAllListeners([eventName])
使用 emitter.removeAllListeners([eventName]) 移除全部监听器或指定的 eventName 事件的监听器。
设置监听器最大绑定数
emitter.setMaxListeners(n)
默认情况下,如果为特定事件添加了超过 10 个监听器,则 EventEmitter 会打印一个警告,这有助于我们发现内存泄露。显然实际编码中并不是所有的事件都要限制 10 个监听器。 emitter.setMaxListeners() 方法可以为指定的 EventEmitter 实例修改限制。当值设为 Infinity(或 0)表示不限制监听器的数量。
查看事件绑定的监听器个数
emitter.listenerCount(eventName)
error事件
当 EventEmitter 实例出错时,应该触发 ‘error’ 事件。
如果没有为 ‘error’ 事件注册监听器,则当 ‘error’ 事件触发时,会抛出错误、打印堆栈跟踪、并退出 Node.js 进程。
通常我们要为会触发 error 事件的对象设置监听器,避免遇到错误后整个程序崩溃。
1 | var events = require('events'); |
Node.js框架—Express框架
可以通过 Express 可以快速地搭建一个完整功能的网站。使用框架的目的就是让我们更加专注于业务,而不是底层细节。
第一个示例
1 | var express = require('express'); |
路由
app.method(path, handler)
路由用于确定应用程序如何响应客户端请求,包含一个 URI(路径)和一个特定的 HTTP 请求方法(GET、POST 等)。
注:app 是 express 的实例,method 是指 HTTP 请求方法(GET、POST 等),path 是指服务器上的路径,handler 是指路由匹配时执行的函数。
栗子
1 | var express = require('express'); |
静态文件
公开public
目录,然后访问其中的资源
app.use('/public/', express.static('./public/'))
或者
1 | var express = require('express'); |
Express 框架处理POST请求
1 | var express = require('express') |
1 |
|
这里用到了body-parser
这个HTTP请求体解析中间件,用这个模块可以解析JSON、Raw、文本、URL-encoded格式的请求体
- bodyParser.json(options): 解析json数据
- bodyParser.raw(options): 解析二进制格式(Buffer流数据)
- bodyParser.text(options): 解析文本数据
- bodyParser.urlencoded(options): 解析UTF-8的编码的数据。
option可选对象:
- inflate - 设置为true时,deflate压缩数据会被解压缩;设置为true时,deflate压缩数据会被拒绝。默认为true。
- limit - 设置请求的最大数据量。默认为’100kb’
- reviver - 传递给JSON.parse()方法的第二个参数,详见JSON.parse()
- strict - 设置为true时,仅会解析Array和Object两种格式;设置为false会解析所有JSON.parse支持的格式。默认为true
- type - 该选项用于设置为指定MIME类型的数据使用当前解析中间件。这个选项可以是一个函数或是字符串,当是字符串是会使用type-is来查找MIMI类型;当为函数是,中间件会通过fn(req)来获取实际值。默认为application/json。
- verify - 这个选项仅在verify(req, res, buf, encoding)时受支持
- inflate - 设置为true时,deflate压缩数据会被解压缩;设置为true时,deflate压缩数据会被拒绝。默认为true。
- limit - 设置请求的最大数据量。默认为’100kb’
- type - 该选项用于设置为指定MIME类型的数据使用当前解析中间件。这个选项可以是一个函数或是字符串,当是字符串是会使用type-is来查找MIMI类型;当为函数是,中间件会通过fn(req)来获取实际值。默认为application/octet-stream。
- verify - 这个选项仅在verify(req, res, buf, encoding)时受支持
- defaultCharset - 如果Content-Type后没有指定编码时,使用此编码。默认为’utf-8’
- inflate - 设置为true时,deflate压缩数据会被解压缩;设置为true时,deflate压缩数据会被拒绝。默认为true。
- limit - 设置请求的最大数据量。默认为’100kb’
- type - 该选项用于设置为指定MIME类型的数据使用当前解析中间件。这个选项可以是一个函数或是字符串,当是字符串是会使用type-is来查找MIMI类型;当为函数是,中间件会通过fn(req)来获取实际值。默认为application/octet-stream。
- verify - 这个选项仅在verify(req, res, buf, encoding)时受支持
- extended - 当设置为false时,会使用querystring库解析URL编码的数据;当设置为true时,会使用qs库解析URL编码的数据。后没有指定编码时,使用此编码。默认为true
- inflate - 设置为true时,deflate压缩数据会被解压缩;设置为true时,deflate压缩数据会被拒绝。默认为true。
- limit - 设置请求的最大数据量。默认为’100kb’
- parameterLimit - 用于设置URL编码值的最大数据。默认为1000
- type - 该选项用于设置为指定MIME类型的数据使用当前解析中间件。这个选项可以是一个函数或是字符串,当是字符串是会使用type-is来查找MIMI类型;当为函数是,中间件会通过fn(req)来获取实际值。默认为application/octet-stream。
- verify - 这个选项仅在verify(req, res, buf, encoding)时受支持