就是指的:排版引擎(页面渲染引擎)。
职责:JS是高级语言------>汇编语言------->机器语言----->cpu执行
#安装最新的lts版本
n lts
#安装最新的current版本(支持更多语法)
n latest
#安装指定版本
n 12.18.1
REPL(Read-Eval-Print Loop)译为:“读取-求值-输出” 循环;
REPL是一个简单的、交互式的编程环境。(相当于浏览器的console控制台)
直接在终端输入:node
回车进入。
浏览器中是有window、document等全局对象的,而node中则没有,而是有global、process等全局对象
获取参数:process.argv
//index.js
console.log(process.argv)
1、当我们直接 node index.js
回车执行
//打印出内容:
[
'/usr/local/bin/node',//node的全局绝对路径
'/Users/mac/Desktop/react_project/NodeRestudy/index.js'//index.js的绝对路径
]
2、当我们传递参数 node index.js env=production platform=ios author=rayhomie
回车执行
//打印出内容:
[
'/usr/local/bin/node',//node的全局绝对路径
'/Users/mac/Desktop/react_project/NodeRestudy/node命令行传参.js',//index.js的绝对路径
'env=production',//接收的参数
'platform=ios',//接收的参数
'author=rayhomie'//接收的参数
]
__dirname
、__filename
、exports
、module
、require()
//index.js
console.log(__dirname)
console.log(__filename)
/*
/Users/mac/Desktop/react_project/NodeRestudy
/Users/mac/Desktop/react_project/NodeRestudy/特殊全局对象.js
*/
setTimeout
:在n毫秒后执行一次setInterval
:每n毫秒重复执行一次setImmediate
:I/O事件后的回调“立即”执行process.nextTick
:添加到下一次tick队列中;JavaScript被称之为披着C语言外衣的Lisp。(函数式编程)
但是早期JS还是存在很多缺陷:
模块化规范:AMD、CMD、CommonJS(CJS)、ES Module(ESM)
Node中对CommonJS进行了实现和支持:
exports
、module.exports
、require
exports
和module.exports
负责对模块中的内容进行导出。require
函数可以导入其他模块(自定义模块,系统模块,第三方库模块)中的内容。//------------------- CJS/module1.js -------------
let name = 'rayhomie'
let age = 20
const person = {
name: 'rayhomie',
age: 20
}
function modify(newName, newAge) {
name = newName
age = newAge
person.name = newName
person.age = newAge
}
setTimeout(() => {
console.log(name, age);//leihao 30
}, 1000)
/*
//使用exports导出
exports.name = name
exports.age = age
exports.person = person
exports.modify = modify
*/
//使用module.exports导出
module.exports = {
name, age, person, modify }
//----------------- CJS/main.js -----------------
const module1 = require('./module1.js')
//实际上是浅拷贝拷贝了一份module1.js模块对象
console.log(module1);
/*
{
name: 'rayhomie',
age: 20,
person: { name: 'rayhomie', age: 20 },
modify: [Function: modify]
}
*/
module1.modify('leihao', 30)
console.log(module1);
/*
{
name: 'rayhomie',
age: 20,
person: { name: 'leihao', age: 30 },
modify: [Function: modify]
}
*/
/*
//module1.js中的setTimeout打印:
leihao 30
*/
理解:默认情况下模块的exports变量指向一个空对象,require()
执行返回的是exports变量指向的对象。我们导出的操作是exports.key=value
或module.exports={...}
,这样对对象设置属性也就是:将要暴露的属性浅拷贝到exports指向的对象上。所以CJS是导出其实是浅拷贝模块。
通过维基百科中对CommonJS的规范的解析:
new Module()
),也就是每一个文件都是一个module实例。因为源码里面做了一件事情:在当前模块最顶层执行module.exports=exports
//------------ CJS/module2.js -------------------
let name = 'rayhomie'
exports.name = name
console.log(exports, module.exports, exports === module.exports);
//{ name: 'rayhomie' } { name: 'rayhomie' } true
//------------- CJS/module3.js --------------------
let name = 'rayhomie'
module.exports ={
name }
console.log(exports, module.exports);
//{} { name: 'rayhomie' }
常见:require(X)
./
或../
或/
(根目录)开头的
X
、X.js
、X.json
、X.node
的顺序查找X/index.js
、X/index.json
、X/index.node
/node_modules
根目录下(全局依赖)规范这是定义代码应该如何去编写,只有有了具体的实现才能被应用:
require.js的使用:
<script src="./lib/require.js" data-main="./index.js" >script>
//------------ index.js --------------
//入口用于声明配置管理模块
(function () {
//require是外部require.js加载的全局变量
require.config({
//需要配置
baseUrl: '',
paths: {
//模块名与路径的映射(不加后缀名)
"module1": "./modules/module1",
"module2": "./modules/module2"
}
})
//加载module2.js中的代码
require(['module2'], function (module2) {
console.log(module2);
})
})()
//------------ ./modules/module1.js --------------
//每个模块需要使用define来定义
define(function () {
const name = 'rayhomie'
const age = 20
const sayHello = (name) => {
console.log('你好' + name)
}
return {
//暴露给外部
name,
age,
sayHello
}
})
//------------ ./modules/module2.js --------------
//每个模块需要使用define来定义
define([
'module1'//依赖的模块
], function (module1) {
//使用参数来接收导入的变量
console.log(module1.name)
console.log(module1.age)
module1.sayHello('leihao')
return {
}
})
<script src="./lib/sea.js">script>
<script>
//直接使用脚本的主入口(区别于AMD,主入口中不需要配置管理各个模块)。
seajs.use('./index.js')
script>
//------------ index.js --------------
define(function (require, exports, module) {
//和Commonjs规范差不多的导出导入
const {
name, age, sayHello } = require('./modules/module1.js')
console.log(name);
console.log(age);
sayHello('leihao')
})
//------------ ./modules/module1.js --------------
define(function (require, exports, module) {
const name = 'rayhomie'
const age = 20
const sayHello = function (name) {
console.log('你好', name);
}
//和Commonjs规范差不多的导出导入
module.exports = {
name, age, sayHello }
})
import()
的运行时环境。
parse->ast->bytecode->runtime
use strict
<script src="./index.js" type="module">script>
//------------ index.js --------------console.log(111)
报错问题:直接在浏览器打开index.html本地文件,会报跨域错误,由于浏览器的安全限制,不支持module以file的协议引入。解决办法是把index.html放在服务器上面,然后浏览器去请求。(VScode中可以安装Live Server插件,然后打开)
export const x
//------------ ./modules/module1.js --------------
export const name = 'rayhomie'
export const age = 20
export const sayHello = function (name) {
console.log('你好', name);
}
//------------ index.js --------------
//分别导入变量
import {
name, age, sayHello } from './modules/module1.js';
//分别导入变量并起别名
import {
name as Name, age as Age, sayHello as SayHello } from './modules/module1.js';
//分别导入全部变量放在一个对象中并起别名
import * as module1 from './modules/module1.js';
export { x, y, z }
//------------ ./modules/module1.js --------------
const name = 'rayhomie'
const age = 20
const sayHello = function (name) {
console.log('你好', name);
}
export {
name, age, sayHello }//导出的不是对象,只是语法(放置要导出的变量的引用列表)
//------------ index.js --------------
import {
name, age, sayHello } from './modules/module1.js';
console.log(name, age);
sayHello('leihao')
export { x as X, y as Y, z as Z }
//------------ ./modules/module1.js --------------
const name = 'rayhomie'
const age = 20
const sayHello = function (name) {
console.log('你好', name);
}
export {
name as Name, age as Age, sayHello as SayHello}
//------------ index.js --------------
import {
Name, Age, SayHello } from './modules/module1.js';
console.log(Name, Age);
SayHello('leihao')
export default { xxx, yyy, zzz }
//------------ ./modules/module1.js --------------
const name = 'rayhomie'
const age = 20
const sayHello = function (name) {
console.log('你好', name);
}
export default {
name, age, sayHello }
//------------ index.js --------------
import module1 from './modules/module1.js';
const {
name, age, sayHello } = module1
console.log(name, age);
sayHello('leihao')
export { * as module1 } from './modules/module1.js'
一般用于封装库的时候,将所有接口放在一个文件中暴露
//------------ ./modules/module1.js --------------
const name = 'rayhomie'
const age = 20
const sayHello = function (name) {
console.log('你好', name);
}
export {
name, age, sayHello }
//------------ index.js --------------
//先导入全部变量,再导出
export {
* as module1 } from './modules/module1.js'
parse->ast->bytecode->runtime
ESM由js引擎解析,在解析时已经确定了模块的依赖关系(在转换成AST之前),所以导入必须写在模块最前面。但ESM还提供了import()
的运行时环境(异步加载)。(浏览器环境没有require()
来加载模块,因为不支持CommonJS)
//------------ index.js --------------
let flag = true;
if (flag) {
//运行时环境
const promise = import('./modules/module1.js')
//返回promise
promise.then((res) => {
console.log(res);
})
}
在webpack中使用ESM规范的import()
会将该文件进行单独打包,用于动态加载。
webpack、rollup等模块化打包工具,只是实现了ESM规范,然后帮我们把代码解析转化模块化打包,打包出来的代码是常规浏览器都支持运行的代码(不需要浏览器再去解析一次,直接到浏览器运行代码即可)。需要跟浏览器环境的ESM做区别,浏览器环境的ESM代码是由JS引擎去解析并执行。
浏览器ESM加载js文件的过程是编译(解析)时加载的,并且是异步的。
import不能和运行时相关的内容放在一起使用:from后面的路径不能动态获取;不能建import放在条件语句中;
异步:体现在在script标签中设置了type='module'
,那么这个srcipt脚本就是异步的就不会阻塞其他的脚本内容加载。相当于默认加了一个async属性。
<script src="./index.js" type="module">script> <script src="./react.js">script><script>console.log('hello')script>
ESM通过export导出的是变量本身的引用(无论 基本 还是 引用 类型变量,都可以实时的获取到修改后的值,区别于CommonJS)
//------------ ./modules/module1.js --------------
let name = 'rayhomie'
setTimeout(() => {
name = 'leihao'
}, 500)
//CommonJS导出的是对象,所以基本类型就无法获取到变化之后的值。
export {
name }//记住导出的不是对象,而是一种语法!!!
//------------ index.js --------------
import {
name } from './modules/module1.js';
console.log(name);
setTimeout(() => {
console.log(name);
}, 1000)
//结果:立即输出rayhomie,一秒后输出leihao。
ESM做了基本数据的实时绑定:(导入的基本数据类型值不能修改,但是引用数据类型可以修改它的属性)
node的JS引擎也是本身有ES module的实现,但是默认是使用的CommonJS模块,我们需要以下操作,才可以让nodeJS引擎去解析ES module的模块代码。
type:module
module.exports
导出的内容作为export default
导出方式来使用//Node v16.1.0
//------------ ./modules/module.js --------------
const name = 'rayhomie'
const age = 20
module.exports = {
name, age }//CommonJS导出
//------------ index.mjs --------------
import module2 from './modules/module2.js';//ES module导入
console.log(module2);//{ name: 'rayhomie', age: 20 }
path.resolve
path.join
区别:resolve会判断我们拼接的路径字符串第一个参数中,是否有以/
或./
或../
开头的路径
/
:如果以/
开头则直接拼接无/
或./
:如果是以./
开头,则是从电脑根路径开始cd拼接(没有/
也会被当成./
,比如:'user/mac'等价于'./user/mac'
)../
:如果以../
开头也是从电脑根路径开始cd拼接,但是需要cd到上一层目录//记住下面的这三个拼接是一样的:
path.resolve(__dirname,'index.js')// /Users/mac/index.js
path.join(__dirname,'index.js')// /Users/mac/index.js
path.join(__dirname,'/index.js')// /Users/mac/index.js
//但是下面这个不一样:
path.resolve(__dirname,'/index.js')// /index.js
写一个resolve方法:
const resolve = dir => path.resolve(__dirname, dir)
console.log(resolve('src'));///Users/mac/src
//webpack中就使用这样的方式起别名:
{
alias:{
"@":resolve("src"),
"components":resolve("src/components")
}
}
path.dirname
:文件夹路径path.basename
:文件名path.extname
:后缀名①同步操作②异步回调③异步promises
const fs = require('fs')
const filePath = './text.txt'
//同步操作
const syncFile = fs.readFileSync(filePath)
console.log(syncFile);
//异步回调
fs.readFile(filePath, (err, data) => {
if (err) {
console.log(err);
return;
}
console.log('读取的内容是:', data)//默认是读取到十六进制的buffer类型
console.log(data.toString())//Buffer转成String类型
})
//使用promises
const promisesFile = fs.promises.readFile(filePath)
promisesFile.then(data => console.log(data))
const EventEmitter = require('events')
//1.创建发射器实例
const emitter = new EventEmitter()
//2.监听某个事件
//on是addListener的简写
emitter.on('eventname', (...args) => {
console.log('监听到了eventname事件第1次', ...args);
})
emitter.addListener('eventname', (...args) => {
console.log('监听到了eventname事件第2次', ...args);
})
//3.触发事件
emitter.emit('eventname','rayhomie','leihao')
/*
打印结果:
监听到了eventname事件第1次 rayhomie leihao
监听到了eventname事件第2次 rayhomie leihao
*/
const EventEmitter = require('events')const emitter = new EventEmitter()emitter.on('tap', (...args) => {
console.log('监听到了tap', ...args);})emitter.on('click', (...args) => {
console.log('监听到了click', ...args);})console.log(emitter.eventNames());// [ 'tap', 'click' ]console.log(emitter.listenerCount('tap'));// 1console.log(emitter.listeners('click'));// [ [Function (anonymous)] ]
必填写的属性:name、version
name是项目的名称
version是的当前项目的版本号
private记录当前的项目是否私有
description是描述信息
author是作者相关信息
license是开源协议
main属性(在webpack中这个mian字段没有用,因为入口交给webpack去打包了,而且这个main定义的入口只遵循CommonJS规范,不支持tree-shaking)
//别人使用我们的包时,require时就会到node_modules下的包里面找main入口文件去加载。
const rayhomieui=require('rayhomieui')
module属性不是npm官方的字段(webpack和rollup联合推出),定义了ES module规范的入口(方便tree-shaking)
types属性是定义类型声明文件入口
browserslist属性:用于配置打包后的JavaScript浏览器的兼容情况,否则我们需要配置polyfills来支持语法。
semver版本规范X.Y.Z:
^x.y.z
:表示x保持不变的,y和z永远安装最新版本。
~x.y.z
:表示x和y保持不变的,z永远安装最新版本。
查看npm缓存
#获取当前的缓存文件夹npm config get cache
弥补npm早期的缺陷,npm5改进升级了很多。
npm i yarn -g
全局安装yarn。
npx是npm5.2之后自带的一个工具
作用:常用用它来调用项目中的某个模块的命令。
举例:比如我们全局安装[email protected],项目中安装[email protected]。此时我们在项目中使用命令webpack --version
,使用的是全局的5.37.0。此时我们想要使用项目中的局部命令,①只能通过./node_modules/.bin/webpack --version
来使用局部webpack,②在package.json中设置脚本也是使用的局部的命令,③也可通过npx webpack --version
来调用局部命令。
需要使用到Linux中的**shebang(hashbang)**符号#!
,作用是根据环境来执行脚本。
下面这个就是在index.js文件中写了一句shebang代码,表示的是别人在运行这个脚本的时候,需要根据环境去找node可执行文件,然后找到node之后去执行index.js后面的代码内容。
#!/usr/bin/env node
console.log('hello world')
在package.json中有一个bin字段,可以对当前包的终端命令行进行配置:
{
"name": "lh",
"version": "1.0.0",
"description": "",
"main": "index.js",
"bin": {
//配置命令行
"lh": "index.js"//当别人安装我们的包之后,可以指向lh命令。然后就去运行index.js
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "rayhomie <[email protected]>",
"license": "MIT"
}
流程:但别人安装了我们的包之后可以使用我们在bin中定义的lh
命令,去执行index.js
文件,而且会根据文件中的shebang
指令去当前环境找node,器执行文件中的脚本。
我们在开发时,可以在当前包下使用npm link
命令。将包链接到当前环境,然后就可以模拟用户安装了我们的包的操作,再使用我们的包了。软链接后的包可以在全局包中目录下查看npm root -g
,正式发包后可以把它删除了。
此时在终端命令行使用lh
命令,即可看到终端输出hello world
。
例子,写一个lh --version
查看版本号的命令。
#!/usr/bin/env node
//参数-v,--version获取版本号
process.argv.find(item => ['--version', '-V'].includes(item))
&& console.log(require('./package.json').version);
这样通过process.argv来获取参数判断显然很麻烦,所以我们可以使用一个commander库进行命令行开发更方便。
#!/usr/bin/env node
const program = require('commander')
program.version(require('./package.json').version)
program.parse(process.argv)//解析参数,--help
此时在终端输入lh --help
可以解析出所有的参数。lh --version
就可以显示当前版本号。
#!/usr/bin/env node
const program = require('commander')
//查看版本号
program.version(require('./package.json').version)
//添加自己的options
program.option('-h --lh', 'a lh cli')
//手动设置路径,<>为可选参数
program.option('-d --dest ' , 'a destination folder, example: -d /src/components')
//监听参数传递
program.on('--help', function () {
console.log('监听到--help!!!');
})
//解析一定要写在参数配置的后面
program.parse(process.argv)
/*
//在program.parse后面可以获取到参数信息
const opts = program.opts()
//lh -h,打印:true
console.log(opts.lh);
//lh -d /src/components,打印:/src/components
console.log(opts.dest);
*/
//命令
program
.command('create [others...]' )//创建命令
.description('clone repository into a folder')//描述
.action((project, others) => {
//可以获取参数
console.log(project, others);
})//触发
使用这个库可以用node克隆git的仓库到本地。
在node中可以使用node提供的工具,将回调形式转成promise
const {
promisify } = require('util')
const download = promisify(require('download-git-repo'))
download(...).then(res=>{
}).catch(err=>{
})
需要用到child_process模块(封装terminal.js)
//进程通信
const {
exec, spawn } = require('child_process')
const commandSpawn = (...args) => {
return new Promise((resolve, reject) => {
//创建子进程,执行终端命令,并返回子进程,获取子进程信息
const childProcess = spawn(...args)//返回值是该子进程(进行进程间通信)
childProcess.stdout.pipe(process.stdout)//将子进程的输出流导入到当前进程的输出流中
childProcess.stderr.pipe(process.stderr)//错误信息导入
childProcess.on('close', () => {
resolve()
})//监听关闭
})
}
const commandExec = (...args) => {
return new Promise((resolve, reject) => {
const childProcess = exec(...args)
childProcess.stdout.pipe(process.stdout)
childProcess.stderr.pipe(process.stderr)
childProcess.on('close', () => {
resolve()
})
})
}
module.exports = {
commandSpawn,
commandExec
}
进行调用执行终端命令。具体exec和spawn的参数可以在官网查看。
commandSpawn(npm, ['install', '--force'], {
cwd: `./${
project}` })//cwd选择执行路径
这里就举个栗子,修改packge.json中的name字段
const path = require('path')
//使用promisify包裹
const {
promisify } = require('util')
const readFile = promisify(require('fs').readFile)
const writeFile = promisify(require('fs').writeFile)
async function writeProjectName(Path, name) {
//修改package.json的name(传入的参数是项目根路径,name字段需要更改的名字)
//读取文件
const data = await readFile(path.join(Path, 'package.json'), 'utf8')
const newList = {
}//暂存新对象
const list = JSON.parse(data)
for (let key in list) {
if (key === 'name') {
newList[key] = name
continue
}
newList[key] = list[key];
}
let newContent = JSON.stringify(newList, null, 2);
//重写文件
await writeFile(path.join(Path, 'package.json'), newContent, 'utf8')
}
module.exports = writeProjectName
计算机中所有的内容最终都会使用二进制来表示,如文字、数字、图片、音频、视频等。
js中0x开头的都是16进制数。
使用常用场景:
//推荐使用Buffer.from,而不是new Buffer
const message = '你好啊'
//1.对中文使用buffer进行utf8的编码
let utf8buffer = Buffer.from(message, 'utf8')
console.log(utf8buffer);//
//2.toString默认对utf8解码
console.log(utf8buffer.toString());// 你好啊
console.log(utf8buffer.toString('utf16le'))// 뷤붥闥
//1.对中文使用buffer进行utf16le的编码
let utf16lebuffer = Buffer.from(message, 'utf16le')
console.log(utf16lebuffer);//
//2.toString对utf16le解码
console.log(utf16lebuffer.toString('utf16le'));// 你好啊
Deprecated: Use Buffer.from(string[, encoding])
instead.
被重声明,请使用Buffer.from
//Buffer.from(string[, encoding])
const buf1 = Buffer.from('buffer');
const buf2 = Buffer.from(buf1);
buf1[0] = 0x61;
console.log(buf1.toString());
// Prints: auffer
console.log(buf2.toString());
// Prints: buffer
//Buffer.alloc(size[,fill[,encoding]])
let buffer = Buffer.alloc(8)//给当前buffer分配8个字节内存
console.log(buffer);//
//直接修改buffer内容
buffer[0] = 88// 十进制的88
buffer[1] = 0x88// 十六进制的88
console.log(buffer);//
let fillbuffer= Buffer.alloc(8,'12')// 分配8字节并填充满字符串
console.log(fillbuffer);//
console.log(fillbuffer.toString());// 12121212
读取文件的本质都是读取二进制数据
//文本文件处理
const fs = require('fs')
fs.readFile('./text.txt', {
encoding: 'utf8' }, (err, data) => {
console.log(data);// 雷浩
})
fs.readFile('./text.txt', (err, data) => {
console.log(data);//
console.log(data.toString());// 雷浩
})
(它内部也是需要拿到buffer数据来处理图片)
//处理图片文件
const fs = require('fs')
//使用sharp库来处理图片数据https://github.com/lovell/sharp#documentation
const sharp = require('sharp');
//方式一:传入buffer
fs.readFile('./picture.jpg', (err, data) => {
sharp(data)
.resize(20, 20)
.toFile('./20x20.jpg')
})
//方式二:传入路径
sharp('./picture.jpg')
.resize(100, 100)
.toFile('./100x100.jpg')
宏任务:同步代码、timer回调、浏览器事件回调、ajax回调、DOM监听、UI Rendering
微任务:Promise的then回调、Mutation Observer API、queueMicrotask()等
宏任务执行结束完成后立即去清空微任务队列,直到微任务被清空才去执行下一轮宏任务。(只维护两个队列:消息队列和微任务队列)
<script>
console.log('开始')
setTimeout(() => {
console.log('结束')
}, 0)
new Promise((resolve, reject) => {
resolve('then')
}).then(res => console.log(res))
for (let i = 0; i < 1000000000; i++) {
}
//顺序:开始----------->then-->结束
//等待若干秒后才执行结束。因为timer的回调需要等待宏任务结束才执行
//浏览器事件、ajax等和timer一样都是异步宏任务,会被加到宏任务队列
script>
setTimeout(() => {
console.log('set1');
new Promise((resolve) => {
resolve()
}).then(() => {
new Promise((resolve) => {
resolve()
}).then(() => {
console.log('then4');
})
console.log('then2');
})
})
new Promise((resolve) => {
console.log('pr1');
resolve()
}).then(() => {
console.log('then1');
})
setTimeout(() => {
console.log('set2');
})
console.log(2);
queueMicrotask(() => {
console.log('queueMicrotask1');
})
new Promise((resolve) => {
resolve()
}).then(() => {
console.log('then3');
})
//答案:
/*
pr1
2
then1
queueMicrotask1
then3
set1
then2
then4
set2
*/
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(() => {
console.log('setTimeout')
}, 0)
async1()
new Promise((resolve) => {
console.log('promise1');
resolve()
}).then(() => {
console.log('promise2');
})
console.log('script end');
//答案:
/*
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
*/
await async2()
可以看做成new Promise(resolve=>{ async2() .. })
await
下面的代码就是认为是then
回调函数中的代码
浏览器中的EventLoop是根据HTML5规范来实现的,不同的浏览器可能会有不同的实现,而Node中是由libuv实现的。
操作系统通常提供了两种调用方式:阻塞式调用和非阻塞式调用
开发中的很多耗时操作都是基于非阻塞式调用:
如果主线程频繁的去进行轮询的工作,那么必然会大大降低性能,并且开发中我们不只是一个文件的读写,可能是多个文件,而且可能是多个功能的系统非阻塞式调用,如:网络IO、数据库IO、子进程调用等。所以libuv提供了一个线程池(thread pool):
阻塞和非阻塞是对于被调用者来说的
同步和异步是对于调用者来说的
无论是我们的文件IO、数据库、网络IO、定时器、子进程,在完成对应的操作后,都会将对应的结果和回调函数放到事件循环(任务队列)中,事件循环会不断地从任务队列中取出对应的事件(回调函数)来执行。
Node中一次完整的事件循环Tick分成很多个阶段:(也就是我们注册调度任务(交给Node完成一些IO事件),Node处理完成之后,将结果以回调函数的形式返回给我们,这些回调函数按照事件循环的顺序来执行)
setTimeout()
和setInterval()
的调度回调函数setImmediate()
的回调函数在这里执行socket.on('close',...)
Node宏任务:setTimeout、setInterval、IO事件、setImmediate、close事件;
Node微任务:Promise的then回调、queueMicrotask、process.nextTick
注意:process.nextTick是放在单独的一个队列里面存放(不和Promise.then、queueMicrotask放在一起)
微任务队列:next ticks队列、其他微任务队列(promise.then、queueMicrotask)
宏任务队列:timers队列(setTimeout、setInterval)、轮询队列(io事件)、check队列(setImmediate)、close队列(close回调)
async function async1() {
console.log('async1 start');
await async2()
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(() => {
console.log('setTimeout0');
}, 0)
setTimeout(() => {
console.log('setTimeout2');
}, 300)
setImmediate(() => console.log('setImmediate'))
process.nextTick(() => console.log('nextTick1'))
async1()
process.nextTick(() => console.log('nextTick2'))
new Promise((resolve) => {
console.log('promise1');
resolve();
console.log('promise2');//会被同步执行
}).then(() => {
console.log('promise3');
})
console.log('script end');
//答案:
/*
script start
async1 start
async2
promise1
promise2
script end
nextTick1
nextTick2
async1 end
promise3
setTimeout0
setImmediate
setTimeout2
*/
setTimeout(() => {
console.log('setTimeout');
}, 0)
setImmediate(()=>{
console.log('setImmediate');
})
//答案:
/*
setTimeout
setImmediate
或者
setImmediate
setTimeout
*/
原因:初始化事件循环队列需要时间,timers的回调先保存到红黑树再放到timers队列中也需要时间,setImmediate的回调不需要保存直接加入到check队列几乎不耗时。