创建 Node.js 应用:package.json
首先,创建一个新文件夹以便于容纳需要的所有文件,并且在此其中创建一个 package.json 文件,描述你应用程序以及需要的依赖:
配合着你的 package.json 请运行 npm install。如果你使用的 npm 是版本 5 或者之后的版本,这会自动生成一个 package-lock.json 文件,它将一起被拷贝进入你的 Docker 镜像中。
{
"name": "docker_web_app",
"version": "1.0.0",
"description": "Node.js on Docker",
"author": "First Last " ,
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"express": "^4.18.2"
}
}
Node.js项目入口:server.js(可以自定义命名)
const http = require('http');
const hostname = '127.0.0.1';
const port = 3000;
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello World');
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
请使用 Buffer.from()/Buffer.alloc()
ESLint 规则(推荐)
ESLint 规则不使用缓存构造函数或 node/ 未废除的 Api 也会寻找到使用 Buffer() 废弃的函数。 这些规则预先已经包含了。
时间轴的分配
在 Chrome 中连接开发工具实例,然后:
采样内存堆剖析器信息
在 Chrome 中连接开发工具的实例,然后:
阻塞
阻塞
发生时,事件循环无法继续运行 JavaScript。在 Node.js 标准库中使用libuv
的同步方法是最常用的 阻塞
操作。原生模块中也有 阻塞
方法。非阻塞
非阻塞
,并且接受回调函数。某些方法也有对应的 阻塞
版本,名字以 Sync
结尾。代码比较
同步示例
看上去比异步示例
简单些,但是有一个缺陷:第二行
语句会 阻塞 其它 JavaScript 语句的执行直到整个文件全部读取完毕,也就是moreWork()在同步示例中要等待执行
,而在异步示例不需要等待
const fs = require('fs');
const data = fs.readFileSync('/file.md'); // blocks here until file is read
console.log(data);
moreWork(); // 在 console.log 之后执行
const fs = require('fs');
fs.readFile('/file.md', (err, data) => {
if (err) throw err;
console.log(data);
});
moreWork(); // 在 console.log 之前执行
并发和吞吐量
在 Node.js 中 JavaScript 的执行是单线程的
,因此并发性是指事件循环
在完成其他工作后执行 JavaScript 回调函数
的能力。任何预期以并行方式运行的代码必须让事件循环
能够在非 JavaScript 操作
(比如 I/O )执行的同时继续运行
让我们思考这样一种情况:每个对 Web 服务器的请求需要 50 毫秒完成,而那 50 毫秒中的 45 毫秒是可以异步执行的数据库 I/O。选择 非阻塞
异步操作可以释放每个请求的 45 毫秒来处理其它请求。仅仅是选择使用非阻塞
方法而不是 阻塞 方法,就能造成并发的显著差异。
事件循环不同于许多其他语言的模型,其它语言创建额外线程来处理并发工作。
混合阻塞和非阻塞代码的危险
const fs = require('fs');
fs.readFile('/file.md', (err, data) => {
if (err) throw err;
console.log(data);
});
fs.unlinkSync('/file.md');
fs.unlinkSync()
极有可能在 fs.readFile()
之前执行,它会在实际读取之前删除 file.md
。更好的写法是完全 非阻塞 并保证按正确顺序执行:以下代码在 fs.readFile()
的回调中对 fs.unlink()
进行了 非阻塞
调用,这保证了正确的操作顺序。const fs = require('fs');
fs.readFile('/file.md', (readFileErr, data) => {
if (readFileErr) throw readFileErr;
console.log(data);
fs.unlink('/file.md', (unlinkErr) => {
if (unlinkErr) throw unlinkErr;
});
});
非阻塞 I/O 操作
的机制——尽管 JavaScript 是单线程处理
的——当有可能的时候,它们会把操作转移到系统内核中去
。大多数内核都是多线程的
,所以它们可以在后台处理多种操作。当其中的一个操作完成的时候,内核通知 Node.js 将适合的回调函数添加到 轮询 队列中等待时机执行。
事件循环机制解析
当 Node.js 启动后,它会初始化事件循环
,处理已提供的输入脚本,它可能会调用一些异步的 API
、调度定时器
,或者调用process.nextTick()
,然后开始处理事件循环。
┌───────────────────────────┐
┌─>│timers (定时器阶段
)此阶段执行由 setTimeout() 和 setInterval() 排序)
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │pending callbacks(挂起的回调阶段
) 执行 I/O 回调推迟到下一个循环迭代
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │idle, prepare(空闲,准备阶段
) 仅在内部使用
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │poll (轮询阶段
)
│ 检索新的 I/O 事件; 执行与 I/O 相关的几乎任何回调
│ (由“计时器”或 “setImmediate()”所设的紧邻回调除外);
│ node 将在适当时机在此处暂停
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │check(检查阶段
) setImmediate() 回调在此处被调用
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──┤close callbacks(关闭回调阶段
) 一些关闭的回调函数,如:socket.on('close', ...)
└───────────────────────────┘
注意:每个框被称为事件循环机制的一个阶段。
定时器阶段解读
计时器
可以 在回调
后面 指定 阈值
,而不是
用户希望回调
执行的确切时间
。因为在经过指定的一段时间间隔后, 计时器回调将被尽可能早地运行
。但是,操作系统调度或其它正在运行的回调
可能会延迟
它们。轮询 阶段
控制执行计时器的时间。轮询 阶段
挤占事件循环
的执行,libuv(实现 Node.js 事件循环和平台上所有异步行为的 C 函数库)还设有一个最大硬性限制(取决于系统)
,以避免继续轮询更多事件。轮询阶段解读
例如,您调度了一个在 100 毫秒后执行回调的定时器,并且您的脚本开始异步读取文件,这会耗费 95 毫秒:
(一旦 轮询 队列为空,事件循环将检查 已达到时间阈值的计时器。如果一个或多个计时器已准备就绪,则事件循环将绕回计时器阶段以执行这些计时器的回调。)
轮询队列
不是空的 ,事件循环将循环访问回调队列并同步执行
它们,直到队列已用尽,或者达到了与系统相关的硬性限制。轮询队列
是空的 ,还有两件事发生:setImmediate()
调度,则事件循环
将结束 轮询 阶段
,并继续 检查 阶段
以执行那些被调度的脚本。 未
被 setImmediate()
调度,则事件循环将等待回调
被添加到队列
中,然后立即执行。轮询 阶段事件循环陷入吃不饱的状态
,libuv(实现 Node.js 事件循环和平台的所有异步行为的 C 函数库)在停止轮询以获得更多事件之前,还有一个硬性的最大值
(依赖于系统)。检测阶段
完成后执行脚本。已使用 setImmediate()调度过
,并且轮询阶段变为空闲状态
,则它将结束此阶段
,并继续到检查阶段
而不是继续等待轮询事件。最小阈值
后运行。将在事件循环继续之前解析
递归 process.nextTick()
调用来饿死
您的 I/O,阻止事件循环到达轮询阶段
。let bar;
function someAsyncApiCall(callback) {
process.nextTick(callback);
}
someAsyncApiCall(() => {
console.log('bar', bar); // 1
});
bar = 1;
function apiCall(arg, callback) {
if (typeof arg !== 'string')
return process.nextTick(
callback,
new TypeError('argument should be string')
);
}
另一个例子是扩展 EventEmitter, 并在构造器内释放一个事件:
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {
constructor() {
super();
this.emit('event');
}
}
const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
console.log('an event occurred!');
});
你不能立即从构造函数中触发事件,因为脚本尚未处理到用户为该事件分配回调函数的地方。因此,在构造函数本身中可以使用 process.nextTick() 来设置回调,以便在构造函数完成后发出该事件,这是预期的结果
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {
constructor() {
super();
// use nextTick to emit the event once a handler is assigned
process.nextTick(() => {
this.emit('event');
});
}
}
const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
console.log('an event occurred!');
});
循环机制(初始化和回调)的方式运行
JavaScript 代码,并且提供了一个线程池
处理诸如文件 I/O 等高成本的任务。事件循环线程(也被称为主循环,主线程,事件线程等)
。另外一个是在工作线程池里的 k 个工作线程
(也被称为线程池)。抽象来说,事件轮询线程和工作池线程分别为等待中的事件回调和等待中的任务维护一个队列。
而事实上,事件轮询线程本身并不维护队列,它持有一堆要求操作系统使用诸如 epoll (Linux),kqueue (OSX),event ports (Solaris) 或者 IOCP (Windows) 等机制去监听的文件描述符。 这些文件描述符可能代表一个网络套接字,一个监听的文件等等。 当操作系统确定某个文件的描述符发生变化,事件轮询线程将把它转换成合适的事件,然后触发与该事件对应的回调函数。
相对而言,工作线程池则使用一个真实的队列,里边装的都是要被处理的任务。 一个工作线程从这个队列中取出一个任务,开始处理它。当完成之后这个工作线程向事件循环线程中发出一个“至少有一个任务完成了”的消息。
Node.js有两种线程:一个事件循环和 k 工作者。 事件循环负责JavaScript 回调和非屏蔽I/O, 和一个工人执行对 C++ 代码的任务,完成异步请求,包括阻塞I/O 和 CPU密集工作。 两种类型的线程每次只能在一个活动上工作。 如果任何回调或任务需要很长时间,运行的线程会被屏蔽 。 如果您的应用程序阻止回调或任务,这最多可能导致通过量化(客户/秒),最坏情况下完全拒绝服务。
要写入一个高吞吐量,更多地预防 DoS 服务攻击的服务,您必须确保在良性和恶意输入中, 你的事件循环和你的 Worker 始终不会屏蔽。
Node.js 中的定时器
function myFunc(arg) {
console.log(`arg was => ${arg}`);
}
setTimeout(myFunc, 1500, 'funky');
不要把 setImmediate() 和 process.nextTick() 相混淆。它们有一些主要的差别:第一, process.nextTick() 将在任何设置好的 Immediate 以及任何安排好的 I/O 前 执行。第二, process.nextTick() 是不可擦除的,换句话说,一旦有代码使用 process.nextTick() 执行,执行无法中断,这就像一个普通函数一样
console.log('before immediate');
setImmediate((arg) => {
console.log(`executing immediate: ${arg}`);
}, 'so immediate');
console.log('after immediate');
before immediate
after immediate
executing immediate: so immediate
function intervalFunc() v.
console.log('Cant stop me !');
}
setInterval(intervalFunc, 1500);
const timeoutObj = setTimeout(() => {
console.log('timeout beyond time');
}, 1500);
const immediateObj = setImmediate(() => {
console.log('immediately executing immediate');
});
const intervalObj = setInterval(() => {
console.log('interviewing the interval');
}, 500);
clearTimeout(timeoutObj);
clearImmediate(immediateObj);
clearInterval(intervalObj);
你已经记住了 Timeout 对象是通过 setTimeout 和 setInterval 返回的。 Timeout 对象提供了两个针对 Timeout 行为的函数
unref()
取消setTimeout
和setInterv
函數的調用ref()
恢復setTimeout
和setInterv
函數的調用const timerObj = setTimeout(() => {
console.log('will i run?');
});
// if left alone, this statement will keep the above
// timeout from running, since the timeout will be the only
// thing keeping the program from exiting
timerObj.unref();
// we can bring it back to life by calling ref() inside
// an immediate
setImmediate(() => {
timerObj.ref();
});
下载nodejs
安装
配置环境变量
新建文件夹如下
更改文件夹权限
添加环境变量
验证是否安装成功
C:\Users\Administrator>node -v
v18.17.1
C:\Users\Administrator>npm -v
9.6.7
C:\Users\Administrator>
修改模块下载位置
C:\Users\Administrator>npm get prefix
C:\Users\Administrator\AppData\Roaming\npm
C:\Users\Administrator>npm get cache
C:\Users\Administrator\AppData\Local\npm-cache
C:\Users\Administrator>
C:\Users\Administrator>npm config set prefix "D:\nodejs\node_global"
C:\Users\Administrator>npm config set cache "D:\nodejs\node_cache"
C:\Users\Administrator>
测试默认位置是否更改成功
C:\Users\Administrator>npm install express -g
added 58 packages in 20s
8 packages are looking for funding
run `npm fund` for details
C:\Users\Administrator>
设置淘宝镜像,并安装cnpm
C:\Users\Administrator>npm config get registry
https://registry.npmjs.org/
C:\Users\Administrator>npm config set registry https://registry.npmmirror.com
C:\Users\Administrator>npm config get registry
https://registry.npmmirror.com
C:\Users\Administrator>npm install -g cnpm --registry=https://registry.npmmirror.com
added 399 packages in 18s
28 packages are looking for funding
run `npm fund` for details
C:\Users\Administrator>
npm config get registry
npm config set registry https://registry.npm.taobao.org
npm config set registry https://registry.npmmirror.com
npm config get registry
npm install -g cnpm --registry=https://registry.npm.taobao.org
npm install -g cnpm --registry=https://registry.npmmirror.com
查看cnpm模块
执行命令查看cnpm是否安装成功
C:\Users\Administrator>cnpm -v
cnpm@9.2.0 (D:\nodejs\node_global\node_modules\cnpm\lib\parse_argv.js)
npm@9.8.1 (D:\nodejs\node_global\node_modules\cnpm\node_modules\npm\index.js)
node@18.17.1 (C:\Program Files\nodejs\node.exe)
npminstall@7.9.0 (D:\nodejs\node_global\node_modules\cnpm\node_modules\npminstall\lib\index.js)
prefix=D:\nodejs\node_global
win32 x64 10.0.19044
registry=https://registry.npmmirror.com
C:\Users\Administrator>
附上所有命令
Microsoft Windows [版本 10.0.19044.3086]
(c) Microsoft Corporation。保留所有权利。
C:\Users\Administrator>node -v
v18.17.1
C:\Users\Administrator>npm -v
9.6.7
C:\Users\Administrator>npm get prefix
C:\Users\Administrator\AppData\Roaming\npm
C:\Users\Administrator>npm get cache
C:\Users\Administrator\AppData\Local\npm-cache
C:\Users\Administrator>npm config set prefix "D:\nodejs\node_global"
C:\Users\Administrator>npm config set cache "D:\nodejs\node_cache"
C:\Users\Administrator>npm install express -g
added 58 packages in 20s
8 packages are looking for funding
run `npm fund` for details
C:\Users\Administrator>npm config get registry
https://registry.npmjs.org/
C:\Users\Administrator>npm config set registry https://registry.npmmirror.com
C:\Users\Administrator>npm config get registry
https://registry.npmmirror.com
C:\Users\Administrator>npm install -g cnpm --registry=https://registry.npmmirror.com
added 399 packages in 18s
28 packages are looking for funding
run `npm fund` for details
C:\Users\Administrator>cnpm -v
cnpm@9.2.0 (D:\nodejs\node_global\node_modules\cnpm\lib\parse_argv.js)
npm@9.8.1 (D:\nodejs\node_global\node_modules\cnpm\node_modules\npm\index.js)
node@18.17.1 (C:\Program Files\nodejs\node.exe)
npminstall@7.9.0 (D:\nodejs\node_global\node_modules\cnpm\node_modules\npminstall\lib\index.js)
prefix=D:\nodejs\node_global
win32 x64 10.0.19044
registry=https://registry.npmmirror.com
C:\Users\Administrator>