Node.js 初识
# Node.js 初识
# 1. Node.js 诞生史
Node.js之父:Ryan Dahl(瑞安·达尔)
- 并非科班出身的开发者,在2004年在纽约的罗彻斯特大学数学系读博士。
- 2006年退学,来到智利的Valparaiso小镇。
- 期间曾熬夜做了一些不切实际的研究,例如如何通过云进行通信。
- 偶然的机会,走上了编程之路,生活方式变为接项目,然后去客户的地方工作。
- 工作中遇到了主流服务器的瓶颈问题,尝试着自己去解决,费尽周折没有办法。
- 2008年Google公司Chrome V8引擎横空出世,JavaScript脚本语言的执行效率得到质的提升,他的想法与Chrome V8引擎碰撞出激烈的火花。
- 2009年的2月,按新的想法他提交了项目的第一行代码,这个项目的名字最终被定名为 “node”。
- 2009年5月,正式向外界宣布他做的这个项目。
- 2009年底,Ryan Dahl在柏林举行的JSConf EU会议上发表关于Node.js的演讲,之后Node.js逐渐流行于世。
- Ryan Dahl于2010年加入Joyent公司,全职负责Node.js项目的开发。此时Node.js项目已经从个人项目变成一个公司组织下的项目。
# 2. Node.js 是什么
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。
传统的 JavaScript 只能运行在浏览器端,脱离浏览器不能运行,也就不能操作本地的文件或者创建文件,也不能进行网络编程,Node.js 的出现打破了这一局面。
Node.js 编写的代码还是 JS,所以开发者需要利用 Chrome V8 来运行 JS。Node.js 借助了 C/C++ 中的 libuv 库来实现文件读取和事件循环。我们不必深挖其中的原理,只需要知道如何使用就行,Node.js 已经为我们打包好了相关接口。
# 3. Node.js 的特点
# 3.1 优点
- 异步非阻塞的 I/O(I/O线程池)
- 特别适用于 I/O 密集型应用(对比传统 Java 服务器)
- 事件循环机制
- 单线程(成也单线程,败也单线程)
- 跨平台
解释:
- 异步非阻塞的 I/O(I/O线程池)
I/O是指inout/ouput,这里是指文件的读写,数据库的操作等等。同步会造成阻塞问题,按照顺序来进行操作。异步是指做这一件事的时候可以做其他事情,非阻塞。
I/O线程池:让一个线程随时随地处于待命状态,以便下次更加快速的执行任务。 - 特别适用于 I/O 密集型应用
某个项目需要频繁进行I/O操作,就成为I/O 密集型应用。 - 事件循环机制
Node.js 脱离了浏览器,浏览器有事件循环机制,但 Node.js 也提供了自己的独有的一个事件循环机制。 - 单线程(成也单线程,败也单线程)
单线程要想实现异步,就必须要有自己的 “事件循环模型”。 - 跨平台
- JS 跨平台:js——js引擎——由谷歌等设计
- java跨平台:java——jvm虚拟机
- Node.js 也跨平台
# 3.2 不足之处
- 回调函数嵌套太多、太深(俗称回调地狱)
- 单线程,处理不好CPU 密集型任务
CPU密集型与IO密集型:
- CPU 密集型:需要过多判断,要做的事情不明确
- IO 密集型:事情明确
简单 web 交互模型:
Node.js 和 Java 服务器对比:
- Java 服务器可以有多 “服务员” ,增加服务器空间来实现高并发,成本也高,适用于大企业。
- Node.js 的服务器只有一个 “服务员”,每次收到任务请求的时候,一对一的服务,向数据库请求数据,通过回调函数实现高并发。适用于 I/O 密集型应用,适用于 个人或中小型企业或者微信小程序搭建服务器。
- 所以对于 CPU 密集型应用,会频繁 “点餐”,这时候一对一的 “Node” 就废掉了。
# 4. Node.js 的应用场景
- Web服务API,比如RESTful API(本身没有太多的逻辑,只需要请求API,组织数据进行返回即可)
- 服务器渲染页面,提升速度
- 后端的 Web 服务,例如跨域、服务器端的请求
# 5. Node 中函数的特点
在 Node.js 中,每个模块(即每个 JavaScript 文件)都会被一个外层函数包裹。这种机制为模块化开发提供了支持,并且在某种程度上保护了变量的作用域。
# 5.1 外层函数的作用
Node.js 在每个模块的外层包裹了一个函数,该函数有如下结构:
function (exports, require, module, __filename, __dirname) {
// 模块代码
}
2
3
exports
:用于支持 CommonJS 模块化的导出语法。require
:用于支持 CommonJS 模块化的引入语法。module
:用于支持 CommonJS 模块化的导出语法。__filename
:当前运行文件的绝对路径。__dirname
:当前运行文件所在文件夹的绝对路径。
# 5.2 输出外层函数
可以通过 arguments.callee
来获取函数自身,从而输出外层函数的内容:
function demo() {
// 输出函数本身
console.log(arguments.callee.toString());
}
demo();
2
3
4
5
6
在 Node.js 中执行以下代码,可以获取到外层函数的结构:
console.log(arguments.callee.toString());
执行后,得到类似以下结构的外层函数:
function (exports, require, module, __filename, __dirname) {
// 模块内容
}
2
3
# 5.3 直接使用外层函数参数
Node.js 允许我们在模块内部直接使用这些参数:
console.log(__filename); // 输出当前文件的绝对路径
console.log(__dirname); // 输出当前文件所在目录的绝对路径
2
# 5.4 外层函数的作用
- 模块化支持:提供模块化语法的支持,使得每个文件可以独立成为一个模块。
- 作用域保护:隐藏模块内部的实现细节,避免变量污染全局作用域,从而提高安全性。
# 6. Node 中的 global
在 Node.js 中,全局对象 global
类似于浏览器中的 window
对象,但在服务器端环境中有其独特之处。
# 6.1 Node 的组成
在浏览器中,JavaScript 由以下三部分组成:
- BOM(Browser Object Model):浏览器对象模型,提供与浏览器交互的能力。
- DOM(Document Object Model):文档对象模型,允许对 HTML 和 XML 文档进行动态访问和操作。
- ECMAScript:JavaScript 语言的核心规范。
在 Node.js 中,JavaScript 环境有以下特点:
- 没有 BOM:因为服务器不需要与浏览器交互。
- 没有 DOM:因为没有浏览器窗口,也就没有文档对象模型。
- 支持 ECMAScript:几乎包含了所有的 ECMAScript 规范。
- 没有
window
对象:Node.js 使用global
作为全局对象。
# 6.2 global
的一些常用属性
Node.js 提供了 global
对象,用于全局变量和函数的访问。以下是一些常用的全局属性和方法:
console.log(global);
setInterval
:设置循环定时器。clearInterval
:清空循环定时器。setTimeout
:设置延迟定时器。clearTimeout
:清空延迟定时器。setImmediate
:设置立即执行函数。clearImmediate
:清空立即执行函数。
# 6.3 示例代码
以下是一个简单的示例,演示如何使用 global
对象设置和清除定时器:
// 设置一个每秒执行一次的循环定时器
const intervalId = setInterval(() => {
console.log('每秒执行一次');
}, 1000);
// 3秒后清除定时器
setTimeout(() => {
clearInterval(intervalId);
console.log('定时器已清除');
}, 3000);
// 设置一个立即执行函数
const immediateId = setImmediate(() => {
console.log('立即执行函数');
});
// 清除立即执行函数
clearImmediate(immediateId); // 由于被清除,这行不会输出任何内容
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 6.4 global
与 this
的区别
- 在 Node.js 中,
global
是全局对象,类似于浏览器中的window
,但更适合服务器端应用。 - 在模块中,
this
默认指向模块的exports
对象,而不是global
,这与浏览器中的行为不同。
console.log(this); // 输出:{}
console.log(global === this); // 输出:false
2
总结
Node.js 的模块化特性和全局对象 global
为服务器端开发提供了强大的工具。通过外层函数的封装,Node.js 实现了模块化开发和作用域隔离,提高了代码的安全性和可维护性。同时,global
对象提供了一些常用的全局方法,使得开发者可以方便地处理定时任务和立即执行函数。了解这些特性和工具,可以帮助开发者更好地利用 Node.js 开发高效的服务器端应用。
Node.js 的事件循环模型是其高性能和非阻塞 I/O 的核心机制。它通过事件驱动架构,在单线程中高效处理多任务。以下是 Node.js 事件循环模型的详细介绍。
# 7. Node.js 中的事件循环模型
# 概览
Node.js 的事件循环模型分为 6 个阶段,每个阶段处理特定类型的事件和回调函数。这些阶段按顺序循环执行。
┌───────────────────────────┐
┌─>│ timers │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │
│ └─────────────┬─────────────┘ ┌───────────────┐
│ ┌─────────────┴─────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └─────────────┬─────────────┘ │ data, etc. │
│ ┌─────────────┴─────────────┐ └───────────────┘
│ │ check │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──┤ close callbacks │
└───────────────────────────┘
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 阶段描述
# 1. Timers 阶段
- 功能:执行
setTimeout
和setInterval
设定的回调。 - 过程:
- 计时器开始计时。
- 到达计时器设定的时间后,执行相应的回调函数。
# 2. Pending Callbacks 阶段
- 功能:处理一些系统操作(如 TCP 错误类型)的回调。
- 说明:此阶段主要用于处理延迟执行的 I/O 回调。
# 3. Idle, Prepare 阶段
- 功能:仅用于内部系统的准备工作。
- 说明:在事件循环的这部分通常不需要开发者关注。
# 4. Poll 阶段
- 功能:获取新的 I/O 事件,执行与 I/O 相关的回调。
- 过程:
- 如果有回调需要执行:
- 从回调队列中取出回调函数,依次执行,直到队列为空或达到系统限制。
- 如果回调队列为空:
- 如果存在
setImmediate
回调,则进入 Check 阶段。 - 如果没有
setImmediate
回调,则在此阶段等待新的 I/O 事件。 - 如果定时器到点,则进入 Check 阶段。
- 如果存在
- 如果有回调需要执行:
# 5. Check 阶段
- 功能:执行
setImmediate
设定的回调。 - 说明:专门用于处理
setImmediate
的回调。
# 6. Close Callbacks 阶段
- 功能:执行一些关闭事件的回调,如
socket.on('close', ...)
。 - 说明:在此阶段处理关闭事件的回调。
特殊情况
process.nextTick()
:- 用于在当前操作完成后立即执行的回调。
- 优先级比事件循环的其他阶段高,被称为“VIP 回调”。
# setTimeout
vs setImmediate
- 当没有主线程阻塞时,
setTimeout
和setImmediate
的执行顺序不确定,取决于事件循环的执行时间。 - 当存在主线程任务时,
setTimeout
会优先于setImmediate
执行。
以下是一个示例,展示了不同回调的执行顺序:
// 延迟执行函数
setTimeout(() => {
console.log('setTimeout 指定的回调');
});
// 立即执行函数(回调)
setImmediate(() => {
console.log('我是 setImmediate 执行的回调');
});
// 立即执行函数(VIP 回调)
process.nextTick(() => {
console.log('process.nextTick 指定的回调函数');
});
console.log('我是主线程上的代码');
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
输出结果
我是主线程上的代码
process.nextTick 指定的回调函数
setTimeout 指定的回调
我是 setImmediate 执行的回调
2
3
4
解释
- 主线程代码:首先执行主线程上的代码,输出
"我是主线程上的代码"
。 process.nextTick()
:在主线程代码执行完后立即执行,输出"process.nextTick 指定的回调函数"
。setTimeout
:由于定时器已经到达,因此在 Timers 阶段执行,输出"setTimeout 指定的回调"
。setImmediate
:在 Check 阶段执行,输出"我是 setImmediate 执行的回调"
。
总结
Node.js 的事件循环模型通过多个阶段处理不同类型的事件和回调,确保在单线程环境中高效处理 I/O 操作。通过理解事件循环的工作原理,开发者可以更好地优化代码,提升应用性能。process.nextTick()
和 setImmediate
等工具允许开发者精确控制代码的执行顺序,在实际应用中具有重要意义。