在Node.js环境和浏览器环境中使用JavaScript存在一些关键的区别,这些区别主要涉及到运行环境的特性、API的可用性以及代码执行的上下文。以下是Node.js和浏览器中使用JavaScript的主要区别:
1.运行环境
- 客户端:JavaScript在浏览器中运行时,实现用户交互效果以。
- 服务端:Node.js是一个服务器端的JavaScript运行环境,主要用于构建服务器端应用程序和处理文件系统等任务。
2.全局对象
- Node.js中的全局对象是
global,类似于浏览器中的window对象。但是,在Node.js中没有DOM和BOM(浏览器对象模型),因此没有浏览器特有的API(如document、window、navigator等)。
- 浏览器环境中的全局对象是
window,它提供了访问浏览器窗口、文档和其他浏览器功能的API(如document、navigator等)。
3.模块系统
-
Node.js:使用CommonJS模块系统(即
require和module.exports),支持模块化开发。可以使用npm(Node Package Manager)管理第三方模块。- Node.js也支持ES6模块(通过
.mjs扩展名或在package.json中设置"type": "module"),可以使用import和export关键字。
- Node.js也支持ES6模块(通过
- 浏览器:使用ES Modules(ES6模块)系统,通过
import和export语法实现模块化。现代浏览器已经广泛支持ES Modules,但在旧版本浏览器中可能需要使用构建工具(如Webpack)进行转换和兼容性处理。
4. 内置模块和API
- Node.js:提供了丰富的内置模块(如
http、fs、path等)和第三方模块(通过npm安装),可以进行文件操作、网络通信、加密解密、操作系统任务等。 - 浏览器:浏览器提供了DOM操作相关的API(如
document、Element、XMLHttpRequest、Fetch API等),以及许多Web API(如Canvas、WebGL、Web Audio等),用于操作网页内容和实现丰富的客户端功能。
5. 事件循环和异步
浏览器中JS事件循环:
- 宏任务:setTimeout,setInterval,sctipt整体代码
- 微任务:promise.then,MutationObserver
- 执行顺序:一个宏任务(sctipt整体代码)--->清空微任务队列--->页面渲染--->webWorker任务--->一个宏任务
NodeJS的事件循环
在NodeJS中,事件循环是基于libuv实现,libuv是一个多平台的专注于异步IO的库。
Node中所有异步的API
-
定时器API---Timer队列
setTimeout(callback, delay, [arg1], [arg2], ...)
setInterval(callback, delay, [arg1], [arg2], ...)
-
I/O操作API---Poll队列
- 文件读写
- 数据库操作
- 网络请求
-
node独有---Check队列和nextTick队列
nexprocess.nextTick(callback, [arg1], [arg2], ...)
setImmediate(callback, [arg1], [arg2], ...)
相应队列
不同的异步API对应的队列在前边已经标出,具体再看看每个队列的作用:
- Timer队列:处理定时器回调
- Poll队列:处理I/O操作回调,事件循环在空闲的情况下(空闲的情况指的是Timer队列和Check队列为空)在这里暂停,等待新的IO事件(更快处理客户端请求)
- Check队列:处理
setImmediate回调。
- nextTick队列:用于处理
process.nextTick回调。在启动事件循环前检测nextTick队列是否为空,若不为空则清空后进入事件循环
注意,在node中,setTimeout的delay的最小取值是1,即使人为设置为0,最快也要1ms才会加入到Timer队列。
如果系统运行足够快setImmediate会先于setTimeout。当然若系统运行不够快结果就是相反的。
若要确定二者的执行顺序,需要将二者放到一个I/O操作中,在Poll中进行处理:
fs.readFile(__filename,()=>{
setTimeout(()=>{
console.log('setTimeout')
},0);
setImmediate(()=>{
console.log('setImmediate')
})
})
NodeJS中,Timer---Poll---Check称为一个tick,process.nextTick作用就是将回调函数放到每个tick的头部。这样process.nextTick包裹的函数会在下一个tick之前执行。
微任务队列
微任务队列在nextTick队列之后,tick之前,即:nextTick---微任务--Timer---Poll---Check。
测试一下
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(function () {
console.log('setTimeout0')
}, 0)
setTimeout(function () {
console.log('setTimeout2')
}, 300)
setImmediate(() => console.log('setImmediate'));
process.nextTick(() => console.log('nextTick1'));
async1();
process.nextTick(() => console.log('nextTick2'));
new Promise(function (resolve) {
console.log('promise1')
resolve();
console.log('promise2')
}).then(function () {
console.log('promise3')
})
console.log('script end')
/**
script start
async1 start
async2
promise1
promise2
script end
nextTick1
nextTick2
async1 end --- 微任务前去执行await后的内容
promise3
setTimeout0
setImmediate
setTimeout2---有300ms延迟,所以晚于setImmediate
*/