总结:
通用
Node.js安装包:http://nodejs.cn/download/。
API检索网址:API 文档 | Node.js 中文网 (nodejs.cn)
第三方模板引擎:art-template官方文档
npm init 生成 package.json文件。
Express:
- 提供了创建 Web 服务器的最简单但功能最强大的方法之一。 它的极简主义方法,专注于服务器的核心功能,是其成功的关键。
- 基于 Node.js 平台,快速、开放、极简的 Web 开发框架。
koa: 由 Express 背后的同一个团队构建,旨在变得更简单更轻巧。 新项目的诞生是为了满足创建不兼容的更改而又不破坏现有社区。
readyState
- 存有 XMLHttpRequest 的状态。从 0 到 4 发生变化。
- 0: 请求未初始化
- 1: 服务器连接已建立
- 2: 请求已接收
- 3: 请求处理中
- 4: 请求已完成,且响应已就绪
status
- 200, OK,访问正常
- 301, Moved Permanently,永久移动
- 302, Moved temporarily,暂时移动
- 304, Not Modified,未修改
- 307, Temporary Redirect,暂时重定向
- 401, Unauthorized,未授权
- 403, Forbidden,禁止访问
- 404, Not Found,未发现指定网址
- 500, Internal Server Error,服务器发生错误
MIME Type
MIME (Multipurpose Internet Mail Extensions) 是描述消息内容类型的因特网标准,说白了也就是文件的媒体类型。浏览器可以根据它来区分文件,然后决定什么内容用什么形式来显示。
首先,我们要了解浏览器是如何处理内容的。在浏览器中显示的内容有 HTML、有 XML、有 GIF、还有 Flash ……那么,浏览器是如何区分它们,决定什么内容用什么形式来显示呢?答案是 MIME Type,也就是该资源的媒体类型。
媒体类型通常是通过 HTTP 协议,由 Web 服务器告知浏览器的,更准确地说,是通过 Content-Type 来表示的,例如:
Content-Type: text/HTML
- 表示内容是 text/HTML 类型,也就是超文本文件。为什么是“text/HTML”而不是“HTML/text”或者别的什么?MIME Type 不是个人指定的,是经过 ietf 组织协商,以 RFC 的形式作为建议的标准发布在网上的,大多数的 Web 服务器和用户代理都会支持这个规范 (顺便说一句,Email 附件的类型也是通过 MIME Type 指定的)。
CLI
- 命令行界面(英语:command-line interface,缩写:CLI)是在图形用户界面得到普及之前使用最为广泛的用户界面,它通常不支持鼠标,用户通过键盘输入指令,计算机接收到指令后,予以执行。也有人称之为字符用户界面(CUI)。
- 通常认为,命令行界面(CLI)没有图形用户界面(GUI)那么方便用户操作。因为,命令行界面的软件通常需要用户记忆操作的命令,但是,由于其本身的特点,命令行界面要较图形用户界面节约计算机系统的资源。在熟记命令的前提下,使用命令行界面往往要较使用图形用户界面的操作速度要快。所以,图形用户界面的操作系统中,都保留着可选的命令行界面。
buffer
Buffer 是内存区域。可以将 buffer 视为整数数组,每个整数代表一个数据字节。
Buffer 被引入用以帮助开发者处理二进制数据,在此生态系统中传统上只处理字符串而不是二进制数据。
Buffer 与流紧密相连。 当流处理器接收数据的速度快于其消化的速度时,则会将数据放入 buffer 中。
一个简单的场景是:当观看 YouTube 视频时,红线超过了观看点:即下载数据的速度比查看数据的速度快,且浏览器会对数据进行缓冲。
流
流是为 Node.js 应用程序提供动力的基本概念之一。它们是一种以高效的方式处理读/写文件、网络通信、或任何类型的端到端的信息交换。
Node.js 的
stream
模块 提供了构建所有流 API 的基础。 所有的流都是 EventEmitter 的实例。优点
- 内存效率: 无需加载大量的数据到内存中即可进行处理。
- 时间效率: 当获得数据之后即可立即开始处理数据,这样所需的时间更少,而不必等到整个数据有效负载可用才开始。
stream.pipe(res)
它获取来源流,并将其通过管道传输到目标流。
pipe()
方法的返回值是目标流,这是非常方便的事情,它使得可以链接多个pipe()
调用,如下所示:src.pipe(dest1).pipe(dest2)
分类
Readable
: 可以通过管道读取、但不能通过管道写入的流(可以接收数据,但不能向其发送数据)。 当推送数据到可读流中时,会对其进行缓冲,直到使用者开始读取数据为止。Writable
: 可以通过管道写入、但不能通过管道读取的流(可以发送数据,但不能从中接收数据)。Duplex
: 可以通过管道写入和读取的流,基本上相对于是可读流和可写流的组合。Transform
: 类似于双工流、但其输出是其输入的转换的转换流。TypeScript
- 它是 JavaScript 的超集,为语言增加了新的功能。
- 最值得注意的新功能是静态类型定义,这是普通 JavaScript 中所没有的。
- 多亏于类型,我们可以声明期望的参数类型,以及在函数中确切返回的参数,或者所创建对象的确切是什么。
- TypeScript 是一个非常强大的工具,它在 JavaScript 项目中开辟了可能性的新世界。
- 通过在代码交付之前防止大量错误,它使我们的代码更安全,更健壮 - 它会在编写代码时发现问题,并与 Visual Studio Code 这样的代码编辑器完美集成。
fs
模块提供了许多非常实用的函数来访问文件系统并与文件系统进行交互。
无需安装。 作为 Node.js 核心的组成部分,可以通过简单地引用来使用它:
const fs = require('fs')
一旦这样做,就可以访问其所有的方法,包括:
fs.access()
: 检查文件是否存在,以及 Node.js 是否有权限访问。fs.appendFile()
: 追加数据到文件。如果文件不存在,则创建文件。fs.chmod()
: 更改文件(通过传入的文件名指定)的权限。相关方法:fs.lchmod()
、fs.fchmod()
。fs.chown()
: 更改文件(通过传入的文件名指定)的所有者和群组。相关方法:fs.fchown()
、fs.lchown()
。fs.close()
: 关闭文件描述符。fs.copyFile()
: 拷贝文件。fs.createReadStream()
: 创建可读的文件流。fs.createWriteStream()
: 创建可写的文件流。fs.link()
: 新建指向文件的硬链接。fs.mkdir()
: 新建文件夹。fs.mkdtemp()
: 创建临时目录。fs.open()
: 设置文件模式。fs.readdir()
: 读取目录的内容。fs.readFile()
: 读取文件的内容。相关方法:fs.read()
。fs.readlink()
: 读取符号链接的值。fs.realpath()
: 将相对的文件路径指针(.
、..
)解析为完整的路径。fs.rename()
: 重命名文件或文件夹。fs.rmdir()
: 删除文件夹。fs.stat()
: 返回文件(通过传入的文件名指定)的状态。相关方法:fs.fstat()
、fs.lstat()
。fs.symlink()
: 新建文件的符号链接。fs.truncate()
: 将传递的文件名标识的文件截断为指定的长度。相关方法:fs.ftruncate()
。fs.unlink()
: 删除文件或符号链接。fs.unwatchFile()
: 停止监视文件上的更改。fs.utimes()
: 更改文件(通过传入的文件名指定)的时间戳。相关方法:fs.futimes()
。fs.watchFile()
: 开始监视文件上的更改。相关方法:fs.watch()
。fs.writeFile()
: 将数据写入文件。相关方法:fs.write()
。关于 fs
模块的特殊之处是,所有的方法默认情况下都是异步的,但是通过在前面加上 Sync
也可以同步地工作。
例如:
fs.rename()
fs.renameSync()
fs.write()
fs.writeSync()
这在应用程序流程中会产生巨大的差异。
Node.js 10 包含了对基于 promise API 的实验性支持。
例如,试验一下 fs.rename()
方法。 异步的 API 会与回调一起使用:
const fs = require('fs')
fs.rename('before.json', 'after.json', err => {
if (err) {
return console.error(err)
}
//完成
})
同步的 API 则可以这样使用,并使用 try/catch 块来处理错误:
const fs = require('fs')
try {
fs.renameSync('before.json', 'after.json')
//完成
} catch (err) {
console.error(err)
}
此处的主要区别在于,在第二个示例中,脚本的执行会阻塞,直到文件操作成功。
path
模块提供了许多非常实用的函数来访问文件系统并与文件系统进行交互。
无需安装。 作为 Node.js 核心的组成部分,可以通过简单地引用来使用它:
const path = require('path')
该模块提供了 path.sep
(作为路径段分隔符,在 Windows 上是 \
,在 Linux/macOS 上是 /
)和 path.delimiter
(作为路径定界符,在 Windows 上是 ;
,在 Linux/macOS 上是 :
)。
还有这些 path
方法:
path.basename()
返回路径的最后一部分。 第二个参数可以过滤掉文件的扩展名:
require('path').basename('/test/something') //something
require('path').basename('/test/something.txt') //something.txt
require('path').basename('/test/something.txt', '.txt') //something
path.dirname()
返回路径的目录部分:
require('path').dirname('/test/something') // /test
require('path').dirname('/test/something/file.txt') // /test/something
path.extname()
返回路径的扩展名部分。
require('path').extname('/test/something') // ''
require('path').extname('/test/something/file.txt') // '.txt'
path.isAbsolute()
如果是绝对路径,则返回 true。
require('path').isAbsolute('/test/something') // true
require('path').isAbsolute('./test/something') // false
path.join()
连接路径的两个或多个部分:
const name = 'joe'
require('path').join('/', 'users', name, 'notes.txt') //'/users/joe/notes.txt'
path.normalize()
当包含类似 .
、..
或双斜杠等相对的说明符时,则尝试计算实际的路径:
require('path').normalize('/users/joe/..//test.txt') //'/users/test.txt'
path.parse()
解析对象的路径为组成其的片段:
root
: 根路径。dir
: 从根路径开始的文件夹路径。base
: 文件名 + 扩展名name
: 文件名ext
: 文件扩展名例如:
require('path').parse('/users/test.txt')
结果是:
{
root: '/',
dir: '/users',
base: 'test.txt',
ext: '.txt',
name: 'test'
}
path.relative()
接受 2 个路径作为参数。 基于当前工作目录,返回从第一个路径到第二个路径的相对路径。
例如:
require('path').relative('/Users/joe', '/Users/joe/test.txt') //'test.txt'
require('path').relative('/Users/joe', '/Users/joe/something/test.txt') //'something/test.txt'
path.resolve()
可以使用 path.resolve()
获得相对路径的绝对路径计算:
path.resolve('joe.txt') //'/Users/joe/joe.txt' 如果从主文件夹运行
通过指定第二个参数,resolve
会使用第一个参数作为第二个参数的基准:
path.resolve('tmp', 'joe.txt') //'/Users/joe/tmp/joe.txt' 如果从主文件夹运行
如果第一个参数以斜杠开头,则表示它是绝对路径:
path.resolve('/etc', 'joe.txt') //'/etc/joe.txt'
该模块提供了许多函数,可用于从底层的操作系统和程序运行所在的计算机上检索信息并与其进行交互。
const os = require('os')
有一些有用的属性可以告诉我们一些与处理文件有关的关键事项:
os.EOL
可给出行定界符序列。 在 Linux 和 macOS 上为 \n
,在 Windows 上为 \r\n
。
os.constants.signals
可告知所有与处理过程信号相关的常量,例如 SIGHUP、SIGKILL 等。
os.constants.errno
可设置用于错误报告的常量,例如 EADDRINUSE、EOVERFLOW 等。
可以在 http://nodejs.cn/api/os.html#os_signal_constants 上阅读所有的内容。
现在看一下 os
提供的主要方法:
os.arch()
返回标识底层架构的字符串,例如 arm
、x64
、arm64
。
os.cpus()
返回关于系统上可用的 CPU 的信息。
例如:
[
{
model: 'Intel(R) Core(TM)2 Duo CPU P8600 @ 2.40GHz',
speed: 2400,
times: {
user: 281685380,
nice: 0,
sys: 187986530,
idle: 685833750,
irq: 0
}
},
{
model: 'Intel(R) Core(TM)2 Duo CPU P8600 @ 2.40GHz',
speed: 2400,
times: {
user: 282348700,
nice: 0,
sys: 161800480,
idle: 703509470,
irq: 0
}
}
]
os.endianness()
根据是使用大端序或小端序编译 Node.js,返回 BE
或 LE
。
os.freemem()
返回代表系统中可用内存的字节数。
os.homedir()
返回到当前用户的主目录的路径。
例如:
'/Users/joe'
os.hostname()
返回主机名。
os.loadavg()
返回操作系统对平均负载的计算。
这仅在 Linux 和 macOS 上返回有意义的值。
例如:
[3.68798828125, 4.00244140625, 11.1181640625]
os.networkInterfaces()
返回系统上可用的网络接口的详细信息。
例如:
{
lo0:
[ {
address: '127.0.0.1',
netmask: '255.0.0.0',
family: 'IPv4',
mac: 'fe:82:00:00:00:00',
internal: true },
{
address: '::1',
netmask: 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff',
family: 'IPv6',
mac: 'fe:82:00:00:00:00',
scopeid: 0,
internal: true },
{
address: 'fe80::1',
netmask: 'ffff:ffff:ffff:ffff::',
family: 'IPv6',
mac: 'fe:82:00:00:00:00',
scopeid: 1,
internal: true } ],
en1:
[ {
address: 'fe82::9b:8282:d7e6:496e',
netmask: 'ffff:ffff:ffff:ffff::',
family: 'IPv6',
mac: '06:00:00:02:0e:00',
scopeid: 5,
internal: false },
{
address: '192.168.1.38',
netmask: '255.255.255.0',
family: 'IPv4',
mac: '06:00:00:02:0e:00',
internal: false } ],
utun0:
[ {
address: 'fe80::2513:72bc:f405:61d0',
netmask: 'ffff:ffff:ffff:ffff::',
family: 'IPv6',
mac: 'fe:80:00:20:00:00',
scopeid: 8,
internal: false } ] }
os.platform()
返回为 Node.js 编译的平台:
darwin
freebsd
linux
openbsd
win32
os.release()
返回标识操作系统版本号的字符串。
os.tmpdir()
返回指定的临时文件夹的路径。
os.totalmem()
返回表示系统中可用的总内存的字节数。
os.type()
标识操作系统:
Linux
Darwin
Windows_NT
os.uptime()
返回自上次重新启动以来计算机持续运行的秒数。
os.userInfo()
返回包含当前 username
、uid
、gid
、shell
和 homedir
的对象。
events
模块为提供了 EventEmitter 类,这是在 Node.js 中处理事件的关键。
const EventEmitter = require('events')
const door = new EventEmitter()
事件监听器返回及使用以下事件:
newListener
。removeListener
。以下是最常用的方法的详细说明:
emitter.addListener()
emitter.on()
的别名。
emitter.emit()
触发事件。 按照事件被注册的顺序同步地调用每个事件监听器。
door.emit("slam") // 触发 "slam" 事件。
emitter.eventNames()
返回字符串(表示在当前 EventEmitter
对象上注册的事件)数组:
door.eventNames()
emitter.getMaxListeners()
获取可以添加到 EventEmitter
对象的监听器的最大数量(默认为 10,但可以使用 setMaxListeners()
进行增加或减少)。
door.getMaxListeners()
emitter.listenerCount()
获取作为参数传入的事件监听器的计数:
door.listenerCount('open')
emitter.listeners()
获取作为参数传入的事件监听器的数组:
door.listeners('open')
emitter.off()
emitter.removeListener()
的别名,新增于 Node.js 10。
emitter.on()
添加当事件被触发时调用的回调函数。
用法:
door.on('open', () => {
console.log('打开')
})
emitter.once()
添加当事件在注册之后首次被触发时调用的回调函数。 该回调只会被调用一次,不会再被调用。
const EventEmitter = require('events')
const ee = new EventEmitter()
ee.once('my-event', () => {
//只调用一次回调函数。
})
emitter.prependListener()
当使用 on
或 addListener
添加监听器时,监听器会被添加到监听器队列中的最后一个,并且最后一个被调用。 使用 prependListener
则可以在其他监听器之前添加并调用。
emitter.prependOnceListener()
当使用 once
添加监听器时,监听器会被添加到监听器队列中的最后一个,并且最后一个被调用。 使用 prependOnceListener
则可以在其他监听器之前添加并调用。
emitter.removeAllListeners()
移除 EventEmitter
对象的所有监听特定事件的监听器:
door.removeAllListeners('open')
emitter.removeListener()
移除特定的监听器。 可以通过将回调函数保存到变量中(当添加时),以便以后可以引用它:
const doSomething = () => {
}
door.on('open', doSomething)
door.removeListener('open', doSomething)
emitter.setMaxListeners()
设置可以添加到 EventEmitter
对象的监听器的最大数量(默认为 10,但可以增加或减少)。
door.setMaxListeners(50)
HTTP 核心模块是 Node.js 网络的关键模块。
可以使用以下代码引入:
const http = require('http')
该模块提供了一些属性、方法、以及类。
http.METHODS
此属性列出了所有支持的 HTTP 方法:
> require('http').METHODS
[ 'ACL',
'BIND',
'CHECKOUT',
'CONNECT',
'COPY',
'DELETE',
'GET',
'HEAD',
'LINK',
'LOCK',
'M-SEARCH',
'MERGE',
'MKACTIVITY',
'MKCALENDAR',
'MKCOL',
'MOVE',
'NOTIFY',
'OPTIONS',
'PATCH',
'POST',
'PROPFIND',
'PROPPATCH',
'PURGE',
'PUT',
'REBIND',
'REPORT',
'SEARCH',
'SUBSCRIBE',
'TRACE',
'UNBIND',
'UNLINK',
'UNLOCK',
'UNSUBSCRIBE' ]
http.STATUS_CODES
此属性列出了所有的 HTTP 状态代码及其描述:
> require('http').STATUS_CODES
{
'100': 'Continue',
'101': 'Switching Protocols',
'102': 'Processing',
'200': 'OK',
'201': 'Created',
'202': 'Accepted',
'203': 'Non-Authoritative Information',
'204': 'No Content',
'205': 'Reset Content',
'206': 'Partial Content',
'207': 'Multi-Status',
'208': 'Already Reported',
'226': 'IM Used',
'300': 'Multiple Choices',
'301': 'Moved Permanently',
'302': 'Found',
'303': 'See Other',
'304': 'Not Modified',
'305': 'Use Proxy',
'307': 'Temporary Redirect',
'308': 'Permanent Redirect',
'400': 'Bad Request',
'401': 'Unauthorized',
'402': 'Payment Required',
'403': 'Forbidden',
'404': 'Not Found',
'405': 'Method Not Allowed',
'406': 'Not Acceptable',
'407': 'Proxy Authentication Required',
'408': 'Request Timeout',
'409': 'Conflict',
'410': 'Gone',
'411': 'Length Required',
'412': 'Precondition Failed',
'413': 'Payload Too Large',
'414': 'URI Too Long',
'415': 'Unsupported Media Type',
'416': 'Range Not Satisfiable',
'417': 'Expectation Failed',
'418': 'I\'m a teapot',
'421': 'Misdirected Request',
'422': 'Unprocessable Entity',
'423': 'Locked',
'424': 'Failed Dependency',
'425': 'Unordered Collection',
'426': 'Upgrade Required',
'428': 'Precondition Required',
'429': 'Too Many Requests',
'431': 'Request Header Fields Too Large',
'451': 'Unavailable For Legal Reasons',
'500': 'Internal Server Error',
'501': 'Not Implemented',
'502': 'Bad Gateway',
'503': 'Service Unavailable',
'504': 'Gateway Timeout',
'505': 'HTTP Version Not Supported',
'506': 'Variant Also Negotiates',
'507': 'Insufficient Storage',
'508': 'Loop Detected',
'509': 'Bandwidth Limit Exceeded',
'510': 'Not Extended',
'511': 'Network Authentication Required' }
http.globalAgent
指向 Agent 对象的全局实例,该实例是 http.Agent
类的实例。
用于管理 HTTP 客户端连接的持久性和复用,它是 Node.js HTTP 网络的关键组件。
稍后会在 http.Agent
类的说明中提供更多描述。
http.createServer()
返回 http.Server
类的新实例。
用法:
const server = http.createServer((req, res) => {
//使用此回调处理每个单独的请求。
})
http.request()
发送 HTTP 请求到服务器,并创建 http.ClientRequest
类的实例。
http.get()
类似于 http.request()
,但会自动地设置 HTTP 方法为 GET,并自动地调用 req.end()
。
HTTP 模块提供了 5 个类:
http.Agent
http.ClientRequest
http.Server
http.ServerResponse
http.IncomingMessage
http.Agent
Node.js 会创建 http.Agent
类的全局实例,以管理 HTTP 客户端连接的持久性和复用,这是 Node.js HTTP 网络的关键组成部分。
该对象会确保对服务器的每个请求进行排队并且单个 socket 被复用。
它还维护一个 socket 池。 出于性能原因,这是关键。
http.ClientRequest
当 http.request()
或 http.get()
被调用时,会创建 http.ClientRequest
对象。
当响应被接收时,则会使用响应(http.IncomingMessage
实例作为参数)来调用 response
事件。
返回的响应数据可以通过以下两种方式读取:
response.read()
方法。response
事件处理函数中,可以为 data
事件设置事件监听器,以便可以监听流入的数据。http.Server
当使用 http.createServer()
创建新的服务器时,通常会实例化并返回此类。
拥有服务器对象后,就可以访问其方法:
close()
停止服务器不再接受新的连接。listen()
启动 HTTP 服务器并监听连接。http.ServerResponse
由 http.Server
创建,并作为第二个参数传给它触发的 request
事件。
通常在代码中用作 res
:
const server = http.createServer((req, res) => {
//res 是一个 http.ServerResponse 对象。
})
在事件处理函数中总是会调用的方法是 end()
,它会关闭响应,当消息完成时则服务器可以将其发送给客户端。 必须在每个响应上调用它。
以下这些方法用于与 HTTP 消息头进行交互:
getHeaderNames()
获取已设置的 HTTP 消息头名称的列表。getHeaders()
获取已设置的 HTTP 消息头的副本。setHeader('headername', value)
设置 HTTP 消息头的值。getHeader('headername')
获取已设置的 HTTP 消息头。removeHeader('headername')
删除已设置的 HTTP 消息头。hasHeader('headername')
如果响应已设置该消息头,则返回 true。headersSent()
如果消息头已被发送给客户端,则返回 true。在处理消息头之后,可以通过调用 response.writeHead()
(该方法接受 statusCode 作为第一个参数,可选的状态消息和消息头对象)将它们发送给客户端。
若要在响应正文中发送数据给客户端,则使用 write()
。 它会发送缓冲的数据到 HTTP 响应流。
如果消息头还未被发送,则使用 response.writeHead()
会先发送消息头,其中包含在请求中已被设置的状态码和消息,可以通过设置 statusCode
和 statusMessage
属性的值进行编辑:
response.statusCode = 500
response.statusMessage = '内部服务器错误'
http.IncomingMessage
http.IncomingMessage
对象可通过以下方式创建:
http.Server
,当监听 request
事件时。http.ClientRequest
,当监听 response
事件时。它可以用来访问响应:
statusCode
和 statusMessage
方法来访问状态。headers
方法或 rawHeaders
来访问消息头。method
方法来访问 HTTP 方法。httpVersion
方法来访问 HTTP 版本。url
方法来访问 URL。socket
方法来访问底层的 socket。因为 http.IncomingMessage
实现了可读流接口,因此数据可以使用流访问。
Buffer 是内存区域。 JavaScript 开发者可能对这个概念并不熟悉,比每天与内存交互的 C、C++ 或 Go 开发者(或使用系统编程语言的任何程序员)要少得多。
它表示在 V8 JavaScript 引擎外部分配的固定大小的内存块(无法调整大小)。
可以将 buffer 视为整数数组,每个整数代表一个数据字节。
它由 Node.js Buffer 类实现。
Buffer 被引入用以帮助开发者处理二进制数据,在此生态系统中传统上只处理字符串而不是二进制数据。
Buffer 与流紧密相连。 当流处理器接收数据的速度快于其消化的速度时,则会将数据放入 buffer 中。
一个简单的场景是:当观看 YouTube 视频时,红线超过了观看点:即下载数据的速度比查看数据的速度快,且浏览器会对数据进行缓冲。
使用 Buffer.from()
、Buffer.alloc()
和 Buffer.allocUnsafe()
方法可以创建 buffer。
const buf = Buffer.from('Hey!')
Buffer.from(array)
Buffer.from(arrayBuffer[, byteOffset[, length\]])
Buffer.from(buffer)
Buffer.from(string[, encoding\])
也可以只初始化 buffer(传入大小)。 以下会创建一个 1KB 的 buffer:
const buf = Buffer.alloc(1024)
//或
const buf = Buffer.allocUnsafe(1024)
虽然 alloc
和 allocUnsafe
均分配指定大小的 Buffer
(以字节为单位),但是 alloc
创建的 Buffer
会被使用零进行初始化,而 allocUnsafe
创建的 Buffer
不会被初始化。 这意味着,尽管 allocUnsafe
比 alloc
要快得多,但是分配的内存片段可能包含可能敏感的旧数据。
当 Buffer
内存被读取时,如果内存中存在较旧的数据,则可以被访问或泄漏。 这就是真正使 allocUnsafe
不安全的原因,在使用它时必须格外小心。
Buffer(字节数组)可以像数组一样被访问:
const buf = Buffer.from('Hey!')
console.log(buf[0]) //72
console.log(buf[1]) //101
console.log(buf[2]) //121
这些数字是 Unicode 码,用于标识 buffer 位置中的字符(H => 72、e => 101、y => 121)。
可以使用 toString()
方法打印 buffer 的全部内容:
console.log(buf.toString())
注意,如果使用数字(设置其大小)初始化 buffer,则可以访问到包含随机数据的已预初始化的内存(而不是空的 buffer)!
使用 length
属性:
const buf = Buffer.from('Hey!')
console.log(buf.length)
const buf = Buffer.from('Hey!')
for (const item of buf) {
console.log(item) //72 101 121 33
}
可以使用 write()
方法将整个数据字符串写入 buffer:
const buf = Buffer.alloc(4)
buf.write('Hey!')
就像可以使用数组语法访问 buffer 一样,你也可以使用相同的方式设置 buffer 的内容:
const buf = Buffer.from('Hey!')
buf[1] = 111 //o
console.log(buf.toString()) //Hoy!
使用 copy()
方法可以复制 buffer:
const buf = Buffer.from('Hey!')
let bufcopy = Buffer.alloc(4) //分配 4 个字节。
buf.copy(bufcopy)
默认情况下,会复制整个 buffer。 另外的 3 个参数可以定义开始位置、结束位置、以及新的 buffer 长度:
const buf = Buffer.from('Hey!')
let bufcopy = Buffer.alloc(2) //分配 2 个字节。
buf.copy(bufcopy, 0, 0, 2)
bufcopy.toString() //'He'
如果要创建 buffer 的局部视图,则可以创建切片。 切片不是副本:原始 buffer 仍然是真正的来源。 如果那改变了,则切片也会改变。
使用 slice()
方法创建它。 第一个参数是起始位置,可以指定第二个参数作为结束位置:
const buf = Buffer.from('Hey!')
buf.slice(0).toString() //Hey!
const slice = buf.slice(0, 2)
console.log(slice.toString()) //He
buf[1] = 111 //o
console.log(slice.toString()) //Ho
流是为 Node.js 应用程序提供动力的基本概念之一。
它们是一种以高效的方式处理读/写文件、网络通信、或任何类型的端到端的信息交换。
流不是 Node.js 特有的概念。 它们是几十年前在 Unix 操作系统中引入的,程序可以通过管道运算符(|
)对流进行相互交互。
例如,在传统的方式中,当告诉程序读取文件时,这会将文件从头到尾读入内存,然后进行处理。
使用流,则可以逐个片段地读取并处理(而无需全部保存在内存中)。
Node.js 的 stream
模块 提供了构建所有流 API 的基础。 所有的流都是 EventEmitter 的实例。
相对于使用其他的数据处理方法,流基本上提供了两个主要优点:
一个典型的例子是从磁盘读取文件。
使用 Node.js 的 fs
模块,可以读取文件,并在与 HTTP 服务器建立新连接时通过 HTTP 提供文件:
const http = require('http')
const fs = require('fs')
const server = http.createServer(function(req, res) {
fs.readFile(__dirname + '/data.txt', (err, data) => {
res.end(data)
})
})
server.listen(3000)
readFile()
读取文件的全部内容,并在完成时调用回调函数。
回调中的 res.end(data)
会返回文件的内容给 HTTP 客户端。
如果文件很大,则该操作会花费较多的时间。 以下是使用流编写的相同内容:
const http = require('http')
const fs = require('fs')
const server = http.createServer((req, res) => {
const stream = fs.createReadStream(__dirname + '/data.txt')
stream.pipe(res)
})
server.listen(3000)
当要发送的数据块已获得时就立即开始将其流式传输到 HTTP 客户端,而不是等待直到文件被完全读取。
上面的示例使用了 stream.pipe(res)
这行代码:在文件流上调用 pipe()
方法。
该代码的作用是什么? 它获取来源流,并将其通过管道传输到目标流。
在来源流上调用它,在该示例中,文件流通过管道传输到 HTTP 响应。
pipe()
方法的返回值是目标流,这是非常方便的事情,它使得可以链接多个 pipe()
调用,如下所示:
src.pipe(dest1).pipe(dest2)
此构造相对于:
src.pipe(dest1)
dest1.pipe(dest2)
由于它们的优点,许多 Node.js 核心模块提供了原生的流处理功能,最值得注意的有:
process.stdin
返回连接到 stdin 的流。process.stdout
返回连接到 stdout 的流。process.stderr
返回连接到 stderr 的流。fs.createReadStream()
创建文件的可读流。fs.createWriteStream()
创建到文件的可写流。net.connect()
启动基于流的连接。http.request()
返回 http.ClientRequest 类的实例,该实例是可写流。zlib.createGzip()
使用 gzip(压缩算法)将数据压缩到流中。zlib.createGunzip()
解压缩 gzip 流。zlib.createDeflate()
使用 deflate(压缩算法)将数据压缩到流中。zlib.createInflate()
解压缩 deflate 流。流分为四类:
Readable
: 可以通过管道读取、但不能通过管道写入的流(可以接收数据,但不能向其发送数据)。 当推送数据到可读流中时,会对其进行缓冲,直到使用者开始读取数据为止。Writable
: 可以通过管道写入、但不能通过管道读取的流(可以发送数据,但不能从中接收数据)。Duplex
: 可以通过管道写入和读取的流,基本上相对于是可读流和可写流的组合。Transform
: 类似于双工流、但其输出是其输入的转换的转换流。从 stream
模块获取可读流,对其进行初始化并实现 readable._read()
方法。
首先创建流对象:
const Stream = require('stream')
const readableStream = new Stream.Readable()
然后实现 _read
:
readableStream._read = () => {
}
也可以使用 read
选项实现 _read
:
const readableStream = new Stream.Readable({
read() {
}
})
现在,流已初始化,可以向其发送数据了:
readableStream.push('hi!')
readableStream.push('ho!')
若要创建可写流,需要继承基本的 Writable
对象,并实现其 _write()
方法。
首先创建流对象:
const Stream = require('stream')
const writableStream = new Stream.Writable()
然后实现 _write
:
writableStream._write = (chunk, encoding, next) => {
console.log(chunk.toString())
next()
}
现在,可以通过以下方式传输可读流:
process.stdin.pipe(writableStream)
如何从可读流中读取数据? 使用可写流:
const Stream = require('stream')
const readableStream = new Stream.Readable({
read() {
}
})
const writableStream = new Stream.Writable()
writableStream._write = (chunk, encoding, next) => {
console.log(chunk.toString())
next()
}
readableStream.pipe(writableStream)
readableStream.push('hi!')
readableStream.push('ho!')
也可以使用 readable
事件直接地消费可读流:
readableStream.on('readable', () => {
console.log(readableStream.read())
})
使用流的 write()
方法:
writableStream.write('hey!\n')
使用 end()
方法:
const Stream = require('stream')
const readableStream = new Stream.Readable({
read() {
}
})
const writableStream = new Stream.Writable()
writableStream._write = (chunk, encoding, next) => {
console.log(chunk.toString())
next()
}
readableStream.pipe(writableStream)
readableStream.push('hi!')
readableStream.push('ho!')
writableStream.end()
可以为生产环境和开发环境使用不同的配置。
Node.js 假定其始终运行在开发环境中。 可以通过设置 NODE_ENV=production
环境变量来向 Node.js 发出正在生产环境中运行的信号。
通常通过在 shell 中执行以下命令来完成:
export NODE_ENV=production
但最好将其放在的 shell 配置文件中(例如,使用 Bash shell 的 .bash_profile
),否则当系统重启时,该设置不会被保留。
也可以通过将环境变量放在应用程序的初始化命令之前来应用它:
NODE_ENV=production node app.js
此环境变量是一个约定,在外部库中也广泛使用。
设置环境为 production
通常可以确保:
例如,如果 NODE_ENV
未被设置为 production
,则 Express 使用的模板库 Pug 会在调试模式下进行编译。 Express 页面会在开发模式下按每个请求进行编译,而在生产环境中则会将其缓存。 还有更多的示例。
可以使用条件语句在不同的环境中执行代码:
if (process.env.NODE_ENV === "development") {
//...
}
if (process.env.NODE_ENV === "production") {
//...
}
if(['production', 'staging'].indexOf(process.env.NODE_ENV) >= 0) {
//...
})
例如,在 Express 应用中,可以使用此工具为每个环境设置不同的错误处理程序:
if (process.env.NODE_ENV === "development") {
app.use(express.errorHandler({
dumpExceptions: true, showStack: true }))
})
if (process.env.NODE_ENV === "production") {
app.use(express.errorHandler())
})
使用 throw
关键字创建异常:
throw value
一旦 JavaScript 执行到此行,则常规的程序流会被停止,且控制会被交给最近的异常处理程序。
通常,在客户端代码中,value
可以是任何 JavaScript 值(包括字符串、数字、或对象)。
在 Node.js 中,我们不抛出字符串,而仅抛出 Error 对象。
错误对象是 Error 对象的实例、或者继承自 Error 类(由 Error 核心模块提供):
throw new Error('错误信息')
或:
class NotEnoughCoffeeError extends Error {
//...
}
throw new NotEnoughCoffeeError()
异常处理程序是 try
/catch
语句。
try
块中包含的代码行中引发的任何异常都会在相应的 catch
块中处理:
try {
//代码行
} catch (e) {
}
在此示例中,e
是异常值。
可以添加多个处理程序,它们可以捕获各种错误。
如果在程序执行过程中引发了未捕获的异常,则程序会崩溃。
若要解决此问题,则监听 process
对象上的 uncaughtException
事件:
process.on('uncaughtException', err => {
console.error('有一个未捕获的错误', err)
process.exit(1) //强制性的(根据 Node.js 文档)
})
无需为此导入 process
核心模块,因为它是自动注入的。
使用 promise 可以链接不同的操作,并在最后处理错误:
doSomething1()
.then(doSomething2)
.then(doSomething3)
.catch(err => console.error(err))
你怎么知道错误发生在哪里? 你并不知道,但是你可以处理所调用的每个函数(doSomethingX
)中的错误,并且在错误处理程序内部抛出新的错误,这就会调用外部的 catch
处理程序:
const doSomething1 = () => {
//...
try {
//...
} catch (err) {
//... 在本地处理
throw new Error(err.message)
}
//...
}
为了能够在本地(而不是在调用的函数中)处理错误,则可以断开链条,在每个 then()
函数中创建函数并处理异常:
doSomething1()
.then(() => {
return doSomething2().catch(err => {
//处理错误
throw err //打断链条
})
})
.then(() => {
return doSomething2().catch(err => {
//处理错误
throw err //打断链条
})
})
.catch(err => console.error(err))
使用 async/await 时,仍然需要捕获错误,可以通过以下方式进行操作:
async function someFunction() {
try {
await someOtherFunction()
} catch (err) {
console.error(err.message)
}
}
当在浏览器中运行的 JavaScript 程序中键入 console.log()
时,则会在浏览器的控制台中创建一个漂亮的条目:
如果点击箭头,则会展开日志,可以清楚地看到对象的属性:
在 Node.js 中,也会发生同样的情况。
当我们记录内容到控制台时,没有那么奢侈,因为它会将对象输出到 shell(如果你手动运行 Node.js 程序)或输出到日志文件。 你会获得对象的字符串表示形式。
在达到一定嵌套级别之前一切都很好。 在经过两个级别的嵌套后,Node.js 会放弃并打印 [Object]
作为占位符:
const obj = {
name: 'joe',
age: 35,
person1: {
name: 'Tony',
age: 50,
person2: {
name: 'Albert',
age: 21,
person3: {
name: 'Peter',
age: 23
}
}
}
}
console.log(obj)
{
name: 'joe',
age: 35,
person1: {
name: 'Tony',
age: 50,
person2: {
name: 'Albert',
age: 21,
person3: [Object]
}
}
}
如何打印整个对象?
最好的方法(同时保留漂亮的打印效果)是使用:
console.log(JSON.stringify(obj, null, 2))
其中 2
是用于缩进的空格数。
另一种选择是使用:
require('util').inspect.defaultOptions.depth = null
console.log(obj)
但是有个问题,第 2 级之后的嵌套对象会被展平,这可能是复杂对象的问题。
TypeScript 是由 Microsoft 维护和开发的一个非常流行的开源语言,它受到全世界许多软件开发者的喜爱和使用。
基本上,它是 JavaScript 的超集,为语言增加了新的功能。 最值得注意的新功能是静态类型定义,这是普通 JavaScript 中所没有的。 多亏于类型,我们可以声明期望的参数类型,以及在函数中确切返回的参数,或者所创建对象的确切是什么。 TypeScript 是一个非常强大的工具,它在 JavaScript 项目中开辟了可能性的新世界。 通过在代码交付之前防止大量错误,它使我们的代码更安全,更健壮 - 它会在编写代码时发现问题,并与 Visual Studio Code 这样的代码编辑器完美集成。
稍后再讨论 TypeScript 的其他优点,现在来看一些示例!
看一下下面的代码片段,然后再一起拆解它们:
type User = {
name: string;
age: number;
};
function isAdult(user: User): boolean {
return user.age >= 18;
}
const justine: User = {
name: 'Justine',
age: 23,
};
const isJustineAnAdult: boolean = isAdult(justine);
第一部分带有 type
关键字的是负责声明代表用户的对象的自定义类型。 然后,我们利用这个新创建的类型来创建函数 isAdult
,该函数接受一个类型为 User
的参数并返回 boolean
。 之后,我们创建 justine
,这是我们的示例数据,可用于调用先前定义的函数。 最后,我们用 justine
是否成年的信息来创建新的变量。
您还应该了解有关此示例的其他信息。 首先,如果我们不遵守声明的类型,TypeScript 会警告我们出现问题并防止滥用。 其次,并非所有内容都必须显式地声明类型 - TypeScript 非常聪明,可以为我们推断类型。 例如,即使我们没有显式地声明 isJustineAnAdult
的类型,其类型也会为 boolean
;即使我们未声明变量为 User
类型,justine
仍是函数的有效参数。
好的,我们已经有一些 TypeScript 代码。 现在我们如何运行它?
首先要做的是在我们的项目中安装 TypeScript:
npm install typescript
现在,我们可以在终端中使用 tsc
命令将其编译为 JavaScript。 我们开始做吧!
假设我们的文件名为 example.ts
,则该命令如下所示:
tsc example.ts
此命令会生成一个名为 example.js
的新文件,我们可以使用 Node.js 运行它。 现在,我们知道如何编译和运行 TypeScript 代码,让我们看看 TypeScript 防止错误的功能!
这是我们修改了的代码:
type User = {
name: string;
age: number;
};
function isAdult(user: User): boolean {
return user.age >= 18;
}
const justine: User = {
name: 'Justine',
age: 'Secret!',
};
const isJustineAnAdult: string = isAdult(justine, "I shouldn't be here!");
这是 TypeScript 对此要说的:
example.ts:12:3 - error TS2322: Type 'string' is not assignable to type 'number'.
12 age: "Secret!",
~~~
example.ts:3:3
3 age: number;
~~~
The expected type comes from property 'age' which is declared here on type 'User'
example.ts:15:7 - error TS2322: Type 'boolean' is not assignable to type 'string'.
15 const isJustineAnAdult: string = isAdult(justine, "I shouldn't be here!");
~~~~~~~~~~~~~~~~
example.ts:15:51 - error TS2554: Expected 1 arguments, but got 2.
15 const isJustineAnAdult: string = isAdult(justine, "I shouldn't be here!");
~~~~~~~~~~~~~~~~~~~~~~
Found 3 errors.
如你所见,TypeScript 成功阻止了我们交付可能无法正常工作的代码。 这好极了!
TypeScript 提供了很多其他很棒的机制,例如接口、类、实用类型等。 另外,在较大型的项目中,你还可以在单独的文件中声明 TypeScript 编译器的配置,并细化地调整其工作方式、严格程度、以及将编译后的文件存储在何处。 你可以在 TypeScript 官方文档中阅读有关所有这些出色功能的更多信息。
TypeScript 的其他值得一提的好处有,它可以逐步采用,这有助于使代码更具可读性和可理解性,并且允许开发者在为较旧的 Node.js 版本提供代码时使用现代的语言功能。
TypeScript 在 Node.js 世界中已经建立了良好的基础,并被许多公司、开源项目、工具和框架所使用。 使用 TypeScript 的一些开源项目的知名示例有:
还有很多很多很棒的项目……甚至你的下一个项目!