小满zs专栏:juejin.cn/column/7274…
介绍
Nodejs
并不是JavaScript
应用,也不是编程语言,它是JavaScript
运行时环境,是开源、跨平台的Nodejs
是构建在V8引擎之上的,V8引擎是由C/C++编写的,因此我们的Js代码需要有C/C++转化后再执行
Npm Package.json
-
npm
(全称 Node Package Manager)是 Node.js 的包管理工具,它是一个基于命令行的工具,用于帮助开发者在自己的项目中安装、升级、移除和管理依赖项npm 命令
-
npm init
-
npm install
-
npm install <package-name>
-
npm install <package-name> --save
-
npm install <package-name> --save-dev
-
npm install -g <package-name>
-
npm update <package-name>
-
npm uninstall <package-name>
-
npm run <script-name>
-
npm search <keyword>
-
npm info <package-name>
-
npm list
:列出当前项目中安装的所有包 -
npm outdated
:列出当前项目中需要更新的包 -
npm publish
-
npm login
-
npm logout
-
npm config list
-
npm get registry
Npm install 原理
-
首先安装的依赖都会存放在根目录的node_modules,默认采用扁平化的方式安装,并且排序规则
.bin
第一个然后@系列
,再然后按照首字母排序abcd等,并且使用的算法是广度优先遍历 -
在遍历依赖树时,npm会首先处理项目根目录下的依赖,然后逐层处理每个依赖包的依赖
-
在处理每个依赖时,npm会检查该依赖的版本号是否符合依赖树中其他依赖的版本要求,如果不符合,则会尝试安装适合的版本
扁平化安装
-
理想情况下,安装某个二级模块时,若发现第一层级有相同名称,相同版本的模块,便直接复用那个模块
-
非理想情况下,需要安装不同版本的模块,不能进行复用,会单独在一级模块下搞一层node_modules
npm install 后续流程
Npm run 原理
-
用
npm run vite
举例- 先从当前项目的 node_modules/.bin 去查找可执行命令vite
- 如果没找到就去全局的 node_modules 去找可执行命令vite
- 如果还没找到就去环境变量查找
- 再找不到就进行报错
Npm 生命周期
"predev": "node prev.js",
"dev": "node index.js",
"postdev": "node post.js"
执行 npm run dev 命令的时候 predev 会自动执行 他的生命周期是在dev之前执行,然后执行dev命令,再然后执行postdev,也就是dev之后执行
模块化
- Nodejs 环境 原生支持
CommonJS
模块,这意味着你可以使用require
来引入模块、module.exports
来导出模块。从 Node.js 14开始,Node.js也开始支持ES Modules
,你可以使用import
和export
关键字来导入导出模块 - 浏览器环境 原生支持
ES Modules
,这意味着你可以使用import
和export
。但是,浏览器环境不支持Node.js独有的require
、module.exports
,也就是不支持CommonJS
模块
在vite项目中,根目录下的JavaScript文件(如vite.config.js或者index.js)运行的环境主要是Nodejs环境,该环境用于处理项目配置、插件加载、开发服务器启动等任务。src目录下的JavaScript文件运行的环境通常是浏览器环境。这意味着你可以使用浏览器提供的API,例如:
document
,window
等对象。但是Nodejs环境下的一些模块和方法,例如:require
、module.exports
、process
等在浏览器环境中是无法使用的
Nodejs 全局变量
- 在 Nodejs 中使用
global
定义全局变量,定义的变量,可以在引入的文件中直接使用,但要注意引入的顺序要在定义之后。
global.xxx = 'xxx';
- 在浏览器中我们定义的全局变量都在
window
,nodejs在global
,不同的环境还需要判断,于是在ECMAScript 2020 出现了一个globalThis
全局变量,在nodejs环境会自动切换成global
,浏览器环境自动切换window
由于 Nodejs 中没有DOM和BOM,除了这些API,其他的ECMAscript API都能用
Nodejs 内置全局API
__dirname
__filename
require module
process
BUffer
CSR SSR SEO
- 上面讲到 nodejs 环境无法直接操作DOM和BOM,但是使用第三方库可以做到
npm i jsdom
,jsdom
是一个模拟浏览器环境的库,可以在 nodejs 中使用 DOM API
const fs = require('node:fs')
const { JSDOM } = require('jsdom')
const dom = new JSDOM(`<!DOCTYPE html><div id='app'></div></html>`)
const document = dom.window.document
const window = dom.window
fetch('https://api.thecatapi.com/v1/images/search?limit=10&page=1').then(res => res.json()).then(data => {
const app = document.getElementById('app')
data.forEach(item=>{
const img = document.createElement('img')
img.src = item.url
img.style.width = '200px'
img.style.height = '200px'
app.appendChild(img)
})
fs.writeFileSync('./index.html', dom.serialize())
})
- 上述 demo 属于SSR(
Server-side Rendering
服务端渲染),请求数据和拼接都在服务端完成 - 现在常用的 vue、react 等框架是在客户端完成渲染拼接的,属于CSR(
Client-Side Rendering
客户端渲染) - SEO(搜索引擎优化),CSR应用对SEO不友好
path 模块
主要用于处理和转换文件路径,这个API提供了多个方法来操作字符串形式的路径
path.basename(path, [ext])
:返回路径的最后一部分,如果提供了ext参数,那么返回结果会去除该扩展名path.dirname(path)
:返回path的目录名,就是路径中消除文件名后的部分path.extname(path)
:返回扩展名,比如'.txt'path.join(path1, path2,..)
:拼接路径,支持 ../ 操作path.resolve([from ...] , to)
:用于将给定的路径转化为绝对路径
const path = require('node:path');
path.resolve(__dirname,'./index.js')
path.parse(pathString)
:返回path字符串的对象表示path.format(pathObject)
:从对象中返回路径字符串。这与 path.parse 功能相反
os 模块
用于跟操作系统交互
os.type()
:返回操作系统的类型os.platform()
:返回操作系统平台os.release()
:返回操作系统版本os.arch()
:返回操作系统cpu架构os.cpus()
:返回cpu线程以及详细信息os.networkInterfaces()
:返回网络信息
process API
与其他API不同的是,process对象是一个全局对象,无需导入或定义,用于操作当前进程和控制当前进程
process.arch
:与 os.arch() 一样process.cwd()
:返回当前的工作目录process.argv
:获取执行进程后面的参数,返回的是一个数组process.memoryUsage
:获取当前进程的内存使用情况process.exit()
:强制进程尽快退出process.kill()
:与 exit 类似,用来杀死一个进程,接收一个参数进程id,可以通过 process.pid 获取process.env
:读取操作系统所有的环境变量,也可以修改和查询环境变量,但不会真正影响操作系统的变量,只在当前进程生效。可以配合 cross-env 第三方库使用
child_process 子进程模块
子进程是 Nodejs 核心API,可以用来执行 shell 命令,编写前端工程化工具,处理 cpu 密集型应用 Nodejs 创建子进程共有7个API,Sync是同步API,不加Sync是异步API
exec
执行命令execSync
同步执行命令
const { exec, execSync } = require('child_process');
// exec 异步方法,回掉函数返回buffer,可以执行shell命令,或者跟软件交互
// execSync 同步方法
// 只执行较小的shell命令,想要立即拿到结果的shell,字节上线200kb,超过报错
exec('node -v', (err, stdout, stderr) => {
if (err) {
return err;
}
console.log(stdout);
})
const nodeVersion = execSync('node -v');
console.log('sync', nodeVersion.toString());
// sync v18.16.0
// v18.16.0
spawn
执行命令spawnSync
同步执行命令
// spawn 用于执行一些实时获取的信息,没有字节上线,返回的是个流
// spawnSync 用的较少
const { stdout } = spawn('netstat', ['-a'], {
// options配置信息
});
stdout.on('data', (msg) => {
console.log(msg.toString());
})
// spawn在执行完成后会抛出close事件监听,并返回状态码
stdout.on('close', (msg) => {
console.log('结束了');
})
options 配置信息,exec和spawn都有
cwd <string> 子进程的当前工作目录。
env <Object> 环境变量键值对。
encoding <string> 默认为 'utf8'。
shell <string> 用于执行命令的 shell。 在 UNIX 上默认为 '/bin/sh',在 Windows 上默认为 process.env.ComSpec。 详见 Shell Requirements 与 Default Windows Shell。
timeout <number> 默认为 0。
maxBuffer <number> stdout 或 stderr 允许的最大字节数。 默认为 200*1024。 如果超过限制,则子进程会被终止。 查看警告: maxBuffer and Unicode。
killSignal <string> | <integer> 默认为 'SIGTERM'。
uid <number> 设置该进程的用户标识。(详见 setuid(2))
gid <number> 设置该进程的组标识。(详见 setgid(2))
execFile
执行可执行文件execFileSync
同步执行可执行文件
execFile(path.resolve(process.cwd(),'./bat.cmd'),null,(err,stdout)=>{
console.log(stdout.toString())
})
echo '开始'
mkdir test
cd ./test
echo console.log("test1232131") >test.js
echo '结束'
node test.js
exec 底层是通过 execFile 实现,execFile 底层是通过 spawn 实现
-
fork
创建node子进程,只能接收js模块,适合大量的计算,或者容易阻塞主进程操作的一些代码index.js
const {fork} = require('child_process')
const testProcess = fork('./test.js')
testProcess.send('我是主进程')
testProcess.on("message",(data)=>{
console.log('我是主进程接受消息111:',data)
})
test.js
process.on('message',(data)=>{
console.log('子进程接受消息:',data)
})
process.send('我是子进程')
- send 发送信息 ,message接收消息,可以相互发送接收
更高效的子进程管理:Execa 模块
Execa 模块用于执行外部命令,构建在 child_process 模块之上,提供了简洁的API和更好的错误处理机制。但它属于第三方模块,需要 npm 额外安装
使用 Promise 封装,默认支持 Promise API; 提供了更好的错误处理机制,能够捕获并处理命令执行过程中的错误
- 先看demo
const execa = require('execa');
async function runCommand() {
try {
const { stdout } = await execa('ls', ['-l', '-a'], { cwd: '/path/to/directory' });
console.log('命令执行结果:', stdout);
} catch (error) {
console.error('命令执行失败:', error);
}
}
runCommand();
Execa模块的execa()
函数接收三个参数:
- command(String):要执行的外部命令,例如‘ls’、‘echo’等
- args(Array):传递给外部命令的参数,以数组形式传入
- option(Object 可选):见上面