Node.js学习
Node.js学习
npm
NPM(Node Package Manager)是一个 JavaScript 包管理工具,也是 Node.js 的默认包管理器。
安装包
npm 安装分为本地安装和全局安装:
-
npm install <pkg>:本地安装,安装到当前目录的node_modules目录下,可以用npm root查看绝对路径; -
npm install -g <pkg>:全局安装,安装到安装Node.js时用npm config set prefix配置的文件夹下,可以用npm root -g查看全局包安装路径,用npm list -g --depth=0来查看所有全局包:PS C:\Users\xxx> npm list -g --depth=0 D:\Program Files\nodejs\node_global # 全局包安装的位置,下面是当前系统中已经安装的全局包 +-- @types/node@24.0.3 +-- @vue/cli-service-global@4.5.19 +-- @vue/cli@5.0.8 +-- axios@1.10.0 +-- ts-node@10.9.2 +-- typescript@5.8.3 `-- vue@3.5.22
当前的工程(包)只能访问Node.js内置包以及本地安装的包,而无法访问全局安装的包(虽然本地安装一个已经全局安装的包会优先拷贝到本地 node_modules 而不是重新联网下载)。因此一般是全局安装命令行工具,而其他作为SDK性质的包则本地安装。
本地安装的包又分为开发依赖(devDependencies)和生产依赖(dependencies)。使用 npm install [--save/-S] 是放在生产依赖(dependencies),如果加上 --save-dev/-D 则是放在开发依赖(devDependencies)。而如果不想作为依赖,只是想下下来临时用下,则可以用 npm install --no-save 不作为依赖。
卸载包
# 卸载本地包
npm uninstall/un <pkg>
# 卸载全局包
npm uninstall/un -g <pkg>
package.json
详解package.json和package-lock.json虽然每天都用npm安装包,但是你们对package.js - 掘金

使用 npm init 创建package.json。主要字段如下:
{
// 基本描述
"name": "my-awesome-project", // 包名
"version": "1.0.0", // 语义化版本
"description": "A project to showcase package.json features", // 包描述
"keywords": [
"example",
"package.json",
"guide"
], // npm搜索时的关键字
"author": "Jane Doe", // 作者
"license": "MIT", // 开源许可证
"private": true, // 指示此包为私有,不会被上传到npm仓库
"repository": { // 代码仓库
"type": "git",
"url": "https://github.com/janedoe/my-awesome-project.git"
},
"homepage": "https://www.baidu.com", // 项目主页
// 依赖配置
"dependencies": { // 生产环境依赖
"express": "^4.17.1",
"mongoose": "^5.12.3"
},
"devDependencies": { // 开发环境依赖
"jest": "^26.6.3",
"eslint": "^7.22.0"
},
"peerDependencies": { // 如果当前包是react的插件,则这里的意思是当前包需要的react版本。不会自动安装这里指定的react
"react": "^16.0.0"
},
"optionalDependencies": { // 在找不到这里的包时,仍允许npm继续往下执行
"fsevents": "^2.0.0"
},
"bundledDependencies": [ // 指定需要随当前包打包时一起被打包的包。这里指定的包必须是在dependencies或devDependencies中出现过的
"@npm/renderized",
"@npm/super-streams"
],
"engines": { // 指定项目兼容的Node.js或npm版本
"node": ">=12.0.0",
"npm": ">=6.0.0"
},
// 文件、目录配置
"main": "index.js", // 包的入口文件(其中会导出js模块,当使用require导入该包时,返回的就是这里的文件中的module.exports属性的值)
"browser": "index.js", // 如果包只跑在浏览器,而不允许在服务端使用,用这个属性指定入口文件
// 命令、脚本配置
"scripts": { // 可以通过npm run <stage>运行的命令。名字随意,如果恰好是npm的生命周期stage的钩子名称,则会随着npm在特定时机自动执行
"start": "node index.js",
"build": "webpack --config webpack.config.js",
"test": "jest",
"lint": "eslint ."
},
// 三方工具的配置
"eslintConfig": {
"extends": "eslint:recommended",
"rules": {
"no-console": "warn"
}
},
"resolutions": { // (yarn专用)
"lodash": "4.17.21"
}
}
模块
Node.js诞生时,Javascript尚未支持模块。所以Node.js利用Javascript的特性,通过函数的方式实现了模块,称为CommonJS模块。而ES6新增的模块语法称为ES模块。
CommonJS模块
在这个规范下,每个.js文件都是一个模块,它们内部各自使用的符号都互不冲突,例如,hello.js和main.js都有全局变量var s = 'xxx',但互不影响。
写一个模块
// hello.js
'use strict';
let g_var = 1.1;
function greet(name) {
console.log(`greeting ${name}`);
}
// 给module对象的exports成员赋值,表示这些符号要从当前模块导出
module.exports = {
greet,
g_var,
}
// 也可以用
exports.greet = greet;
exports.g_var = g_var;
// 但是不可以用
exports = {
greet,
g_var
}
使用这个模块
'use strict';
// 引入模块,注意不要文件后缀
const mod1 = require('./hello');
console.log(mod1.g_var)
mod1.greet(s);
注意:
- 默认情况下,Node.js使用的就是 CommonJS 模块(因此node会认为所有
.js后缀的都是 CommonJS 模块),如果已经在 package.json 中配置了"type": "module",则需要将文件后缀改为.cjs才能被认为是 CommonJS 模块。 require()是Node.js提供的内置函数,非Javascript关键字module是Node.js的内置对象,exports是其成员,非Javascript关键字- node会依次在
内置模块 -> 全局模块 -> 当前模块下查找hello.js - 引入的模块内容会在
require()语句处被执行。
Node.js中CommonJS的实现原理
Node.js在调用 require() 函数时,会将其中引入的js文件包装为一个匿名函数,并就地调用:
// const mod1 = require('./hello')的效果简化后大概类似:
const mod1 = (function() {
// 这里填充hello.js的源码
})();
从而使得原来hello.js中的所有符号都成为mod1的属性,而mod1是引入方定义的不重复名字,自然就避免了符号冲突。
而为了导出模块内符号,Node.js是为每个.js文件都在加载前都预定义了一个module对象:
// 实际上完整的hello.js文件
let module = {
id: 'hello',
exports: {}
};
let load = function (exports, module) { // 这里有exports参数是为了方便使用 exports.xxx = xxx 的语法
// 这里填充hello.js中的源码,其中会为module.exports赋值,从而实现符号的导出
return module.exports;
}
let exported = load(module.exports, module);
save(module, exported);
然后在导入方使用 require(模块路径) 时,require() 就会查找该文件对应的 module.id ,找到匹配的后就返回对应的 module.exports ,从而就可以使用其中的导出符号了(没赋值给 exports 自然就是未导出符号)。
另外,前面说了还可以直接 exports.xxx = xxx ,但是不允许 exports = {xxx} ,原因也显而易见了:exports = {xxx} 是创建了一个新的对象,赋值给的 exports 由于只是参数的指针拷贝,对其修改无法影响到外部的 module.exports ,自然使得 exports = {xxx} 不会其效果。
ECMAScript模块
ESM模块用 export 关键字导出一个JS对象,用 import 关键字导入一个模块的导出对象。
写一个模块
// hello.mjs
// 'use strict'; // ES6 默认开启严格模式
let g_var1 = 1.1;
export let g_var2 = 2.2;
export function greet1(name) {
console.log(`greeting1 ${name}`);
}
export function greet2(name) {
console.log(`greeting2 ${name}`);
}
// 默认导出
// 定义一个临时对象,存储所要导出的所有符号
const hello = {
g_var1, // g_var1没有用命名导出,但是被默认导出了,所以也是导出符号
g_var2, // g_var2已经被命名导出过了
greet: greet1 // greet1默认导出时取了个导出名
};
// 设置这个临时对象位默认导出符号
export default hello;
注意:
- ECMAScript模块的文件名后缀是
.mjs。或者在package.json中配置"type": "module"字段来说明使用的当前包默认是 ES Module(因此node会认为所有.js后缀的都是 ECMAScript模块)。 - ES6 默认开启严格模式
export 符号定义是命名导出,导入时需要用import { 对应的符号名 } from ...来导入,使用时直接使用对应的符号名;export default 符号集合是默认导出,导入时需要用import 默认导出名 from ...来导入,使用时需要使用默认导出名.符号名来访问;- 默认导出和命名导出之间不冲突,可以同时写。默认导出的存在意义只是方便导入者不必在
import中全部写出想要导入的符号。
使用这个模块
import hello2 from './hello.mjs'; // 此时导入了g_var1、g_var2、greet1
import { g_var2, greet1, greet2 } from './mod2/hello.mjs'; // 此时导入了g_var2、greet1、greet2
console.log(hello2.g_var1);
console.log(g_var2);
hello2.greet('meha555'); // 只能使用默认导出名了
greet1('meha555');
greet2('meha555');
注意:
- 默认导出名
hello2随便起的,不必和模块定义的.mjs中的export default xxx的符号名称相同; - 默认导入和命名导入不能混用,如果一个符号是用默认导入来导入的,则他就得按照默认导入的用法
默认导出名.符号名来使用。命名导入同理。 - 一旦一个符号被默认导出(尽管可能也命名导出了),则它就成为了默认导出符号,使用时只能按照默认导出符号的导入和使用方式来使用。
基本模块
内置全局对象
与浏览器提供的Web API提供的全局对象 window 类似,Node.js提供的全局对象是 global 。
process也是Node.js提供的一个对象,它代表当前Node.js进程。

浙公网安备 33010602011771号