主题
npm 包管理
本页对应 npm 包管理,共 4 段视频,约 46 分钟。
学习目标
- 能说明 Node.js 中“包”的概念,区分项目包与软件包,并指出
package.json的作用及包入口文件的查找规则。 - 能创建并组织一个自定义软件包,使用 CommonJS 的
module.exports与require完成工具模块的导出、统一出口和导入调用。 - 能使用
npm init -y初始化项目清单文件,并使用npm install <包名>下载软件包,说明node_modules、package.json和package-lock.json的变化及作用。 - 能在缺少
node_modules的项目中执行npm install安装全部依赖,并运行项目验证依赖包可正常引入和使用。 - 能使用
npm install -g nodemon安装全局软件包,使用nodemon执行目标 JS 文件,并通过修改代码观察程序自动重启。 - 能区分本地软件包与全局软件包在封装内容、作用范围和安装方式上的差异,并为具体场景选择正确命令完成安装与使用。
知识点
本节目标与背景:为什么要切换模块标准
为什么需要: 笔记开头明确:CommonJS 规范是 Node.js 环境中默认的,后来官方推出 ECMAScript 标准语法。我们这一节要在“封装基地址和求数组元素和的函数,再导入到 index.js 使用”的需求中,体验 ECMAScript 标准里的默认导出和导入。
怎么想: 先不要急着写 import,而要意识到 Node.js 默认按 CommonJS 解析文件;如果不改项目配置,直接写 ECMAScript 的 import/export 就可能不按预期运行。转写稿中也反复强调 CommonJS 是 Node 环境默认支持的标准语法,并用 require、module.exports 做对比。这里统一纠正转写稿中的同音字:package点jason 应为 package.json,model点exse 应为 module.exports,UTos/UT 在本节对应的是 utils.js 这类工具模块。
怎么做: 先明确本节需求:utils.js 负责导出 baseURL 和 getArraySum,index.js 负责默认导入并使用。
易错点: 不要把 CommonJS 的 require/module.exports 和 ECMAScript 的 import/export default 混写在同一套模块标准里;也不要以为变量名必须和导出时同名,默认导入的变量名可以自定义。
准备:让 Node.js 识别 ECMAScript 模块
为什么需要: 笔记第 5 点指出:Node.js 默认只支持 CommonJS 标准语法,如果想在当前项目环境下使用 ECMAScript 标准语法,需要新建 package.json 文件并设置 type: "module"。
怎么想: 这是“项目级开关”,不是某个文件里的语法。先让 Node.js 知道当前项目按 ECMAScript 模块解析,再写 export default 和 import。
怎么做: 在项目根目录准备 package.json,写入:
json
{ "type": "module" }注意笔记中曾出现中文引号,实际 JSON 必须使用英文双引号。若项目已有 package.json,补充该字段即可;若没有,就新建。转写稿里调试时常提醒“看好终端所在的文件夹”,这里同理:终端要进入项目根目录再运行文件。
易错点: type 的值是字符串 "module";JSON 不能写注释;文件位置要在当前项目根目录,否则 Node.js 可能读不到这个配置。
默认导出:用 export default 暴露一个整体对象
为什么需要: 需求要把基地址和求和函数给外部使用,但模块内它们是私有变量/函数。默认导出可以把它们组织成一个对外对象。
怎么想: 默认导出适合“一个模块主要暴露一个整体对象”的场景。语法是:
js
export default {
对外属性名: 模块内私有变量
}也就是左边写外部访问名,右边写模块内部变量名。
怎么做: 在 utils.js 中保留笔记代码:
js
/**
* 目标:基于 ECMAScript 标准语法,封装属性和方法并"默认"导出
*/
const baseURL = 'http://hmajax.itheima.net'
const getArraySum = arr => arr.reduce((sum, item) => sum += item, 0)
// 默认导出
export default {
url: baseURL,
arraySum: getArraySum
}这里外部看到的是 url 和 arraySum,内部实际使用的是 baseURL 和 getArraySum。
易错点: export default 后面是一个值/对象,不是声明变量;reduce 的初始值 0 不能漏,否则空数组或累加逻辑容易出问题;对外属性名写错,导入后访问就会得到 undefined。
默认导入:用 import 变量名 from 路径接收对象
为什么需要: utils.js 已经默认导出一个对象,index.js 需要把它拿进来,才能调用 arraySum。
怎么想: 默认导入语法是:
js
import 变量名 from '模块名或路径'笔记特别说明:变量名的值接收的就是目标模块导出的对象。因此这里的 obj 就是 { url, arraySum }。
怎么做: 在 index.js 中保留笔记代码:
js
/**
* 目标:基于 ECMAScript 标准语法,"默认"导入,工具属性和方法使用
*/
// 默认导入
import obj from './utils.js'
console.log(obj)
const result = obj.arraySum([10, 20, 30])
console.log(result)导入自己的文件要写相对路径 ./utils.js。转写稿中也提到:自己创建的模块文件写路径,内置模块或 NPM 模块才直接写名字。这里统一纠正同音字:不是“UTos 点 GS”,而是 utils.js。
易错点: 路径不要漏掉 ./;扩展名 ./utils.js 要写清楚;默认导入的变量名不必叫 utils,叫 obj 也可以,因为它接收的是默认导出对象;不要写成 CommonJS 的 const obj = require('./utils.js') 后又在 type: "module" 项目里混用。
完整实现与调试:从两个文件到运行结果
为什么需要: 单独看导出和导入语法容易懂,但真正运行时还要确认项目配置、文件路径和访问属性都正确。
怎么想: 按“配置 → 导出 → 导入 → 运行 → 检查输出”的顺序排查。运行 index.js 后,先看 console.log(obj) 是否打印出包含 url、arraySum 的对象,再看 obj.arraySum([10, 20, 30]) 的结果是否符合预期(按代码推导应为 60)。
怎么做: 文件结构可理解为项目根目录下有 package.json、utils.js、index.js。在项目根目录终端执行:
bash
node index.js如果报 Cannot use import statement outside a module,优先检查 package.json 是否设置了 "type": "module";如果报找不到模块,检查 import obj from './utils.js' 的路径和文件名。转写稿中“终端所在文件夹要看好”的调试习惯在这里同样适用。
易错点: 不要只改文件不检查终端目录;不要漏写 type 字段;不要导入后直接写 obj.getArraySum(),因为默认导出对象里的对外属性名是 arraySum,应调用 obj.arraySum(...)。
小结与易错点
为什么需要: 这一节的核心是掌握 ECMAScript 标准下默认导出和默认导入的固定写法,并能和 Node.js 默认的 CommonJS 区分开。
怎么想: 记住两条主线:导出用 export default {},导入用 import 变量名 from '模块名或路径';Node.js 要切换模块标准,就在 package.json 设置 { "type": "module" }。
怎么做: 保留本节两个关键代码:utils.js 默认导出 url、arraySum;index.js 默认导入 obj 并调用 obj.arraySum([10, 20, 30])。
易错点: 默认导出对象中的属性名要和导入后访问的名字一致;自己写的模块导入要写相对路径;配置、路径、属性名三者任一错误,运行结果都会不符合预期。本节笔记未提供图片资源,因此不额外插入图片,以上保留并整理了笔记中的代码。
说明与本节目标
说明:本节转写稿主要围绕“包的概念、npm、nodemon”等内容,未出现“命名导出和导入”的推导过程;因此下面只依据本节笔记中的语法、代码和小结补全推导与调试,不把转写稿中的包管理内容混入本节,避免编造。
本节目标是:掌握 ECMAScript 标准语法中“命名导出”和“命名导入”的写法,并能判断什么时候用命名导出、什么时候用默认导出。
命名导出:用 export 修饰定义语句
为什么需要:当一个工具模块里要提供多个具名成员,比如基地址 baseURL 和数组求和函数 getArraySum,导入方可能只想按需取其中几个。命名导出能把每个成员以名字暴露出去,导入时按名字取用。
怎么想:先确定“要导出哪些具名成员”,再在定义变量或函数时直接加 export。导出名就是变量名或函数名,导入方要写同名变量。
怎么做:在 utils.js 中写:
js
/**
* 目标:基于 ECMAScript 标准语法,封装属性和方法并"命名"导出
*/
export const baseURL = 'http://hmajax.itheima.net'
export const getArraySum = arr => arr.reduce((sum, item) => sum += item, 0)这里 baseURL 和 getArraySum 都是命名导出。getArraySum 用 reduce 从初始值 0 开始累加,传入 [10, 21, 33] 时结果为 64。
易错点:命名导出不是 export default;导出名与定义名保持一致;reduce 要带初始值 0,否则空数组等情况容易出问题;箭头函数 sum += item 的返回值就是累加后的值,别误写成没有返回的语句块。
命名导入:import { 同名变量 } from 路径
为什么需要:命名导出后,导入方需要明确写出要加载的成员名,做到“按需加载”。这也让代码阅读时能直接看出使用了模块里的哪些工具。
怎么想:大括号里写的是“模块内导出的变量名”,不是随便起的对象属性;示例中使用相对路径 ./utils.js 引入自己写的模块。
怎么做:在 index.js 中写:
js
/**
* 目标:基于 ECMAScript 标准语法,"命名"导入,工具属性和方法使用
*/
// 命名导入
import {baseURL, getArraySum} from './utils.js'
console.log(obj)
console.log(baseURL)
console.log(getArraySum)
const result = getArraySum([10, 21, 33])
console.log(result)调试时先看 baseURL 是否打印为基地址,再看 getArraySum 是否打印为函数,最后调用 getArraySum([10, 21, 33]),应得到 64。
易错点:第一,import { baseURL, getArraySum } 中的名字必须与导出名一致;第二,自己写的模块要用相对路径,如 ./utils.js,不要只写 utils.js;第三,命名导入的大括号不能丢;第四,笔记示例里保留了 console.log(obj),但本示例并没有导入或声明 obj,直接运行会报 ReferenceError: obj is not defined。教学运行时应删除或注释这一行,或改成打印已导入的 baseURL。这是示例调试中最容易踩的坑。
命名导出与默认导出如何选择
为什么需要:导出方式决定导入写法。选错方式,导入语法就会不匹配。
怎么想:按笔记原则判断——按需加载,使用命名导出和导入;全部加载,使用默认导出和导入。模块里有多个具名工具、导入方只取部分时,用命名方式更直观;模块整体作为一个对象或功能全部引入时,用默认方式更简洁。
怎么做:命名导出用 export 修饰定义语句,命名导入用 import { 同名变量 } from '模块名或路径';默认导出对应 export default {},默认导入对应 import 变量名 from '模块名或路径',默认导入不需要大括号。
易错点:不要把默认导入写成大括号形式,也不要把命名导入写成 import baseURL from './utils.js';命名导入的大括号内是导出名,不是随意重命名;一个模块可以有多个命名导出,而默认导出通常用于“整体加载”的场景。掌握这两种方式的区别,再结合“按需”还是“全部”选择即可。
包是什么:把多个模块聚合成一个文件夹
为什么需要:当代码越来越多时,如果所有函数都写在一个文件里,阅读、维护和复用都会很困难。Node.js 里“模块”通常是一个文件,而“包”是把模块、代码和其他资料整合成一个文件夹,用来解决“多个相关模块如何组织在一起”的问题。
怎么想:先分清“模块”和“包”的层级关系:模块是文件,包里可以包含多个模块文件和其他资料。转写稿里举的例子是封装一个 utils 工具包:里面有数组相关方法、字符串相关方法,还有一个入口文件 index.js。因此 utils 这个文件夹本身就是一个包。
怎么做:包分为两类:项目包主要用于编写项目和业务逻辑;软件包用于封装工具和方法供开发者使用。本节要做的 utils 封装了数组求和、用户名和密码长度判断等工具函数,所以它属于软件包。
易错点:不要把“一个 js 文件”直接叫成包;包是文件夹层面的概念。也不要把项目包和软件包混为一谈:项目包偏业务,软件包偏工具复用。
package.json:包的清单文件
为什么需要:一个包需要让别人知道它叫什么、版本是多少、入口文件是哪个、作者和许可证等信息。package.json 就是记录这些清单信息的文件,因此包根目录中必须有它。
怎么想:转写稿中说明,package.json 常见字段包括 name(包名)、version(版本号)、description(描述)、main(入口文件)、author(作者)、license(许可证)。其中 main 与“引入包文件夹时到底加载哪个文件”直接相关。
怎么做:在包文件夹根目录准备 package.json。笔记中给出了清单文件与目录结构的示意:


易错点:package.json 必须在包的根目录,不要放到 lib 等子目录里;字段名要写准确,例如 main 不要拼错。main 指向的文件如果不存在,后续引入包时就可能找不到入口。
引入包文件夹时加载谁:index.js 或 main
为什么需要:外部使用者通常只想写 require('./utils') 引入整个包,而不关心包内部有多少模块文件。因此 Node.js 需要一套默认规则来决定加载哪个入口文件。
怎么想:转写稿的推导是:引入包文件夹时,默认找包文件夹下的 index.js;如果没有 index.js,再找 package.json 中 main 属性指定的文件模块,并取得它导出的对象。
怎么做:在 utils 包中建立统一出口 index.js,把 lib/arr.js 和 lib/str.js 中的函数集中导出。这样外部只需要引入包文件夹。
易错点:自己定义的包要用相对路径,例如 require('./utils');不要只写 require('utils'),那更像内置模块或 npm 包的写法。还要注意:默认入口是 index.js,不是随便一个文件名。
实战:封装 utils 软件包并调试
为什么需要:只理解概念不够,需要把“包文件夹 + package.json + 统一出口 + 外部引入”串起来。需求是:封装数组求和函数、判断用户名和密码长度函数,形成软件包,再导入 index.js 使用查看效果。
怎么想:utils/lib 下的模块代码已准备好,我们只需要在 utils/index.js 做统一出口:先用 require 引入各模块导出的函数,再用 module.exports 统一导出。外部 index.js 引入的是包文件夹,不是具体模块文件。
怎么做:先写 utils/index.js:
js
/**
* 本文件是,utils 工具包的唯一出口
* 作用:把所有工具模块方法集中起来,统一向外暴露
*/
const { getArraySum } = require('./lib/arr.js')
const { checkUser, checkPwd } = require('./lib/str.js')
// 统一导出所有函数
module.exports = {
getArraySum,
checkUser,
checkPwd
}再写外部 index.js:
js
/**
* 目标:导入 utils 软件包,使用里面封装的工具函数
*/
const obj = require('./utils')
console.log(obj)
const result = obj.getArraySum([10, 20, 30])
console.log(result)调试时先运行 index.js,打印 obj,应能看到统一导出的对象,包含 getArraySum、checkUser、checkPwd;再调用 obj.getArraySum([10, 20, 30]),结果为 60。这说明 require('./utils') 确实加载了 utils/index.js 的导出对象。
易错点:module.exports 不要写成 model.exports;解构导入时属性名要与导出名一致;require('./utils') 引入的是文件夹,不要误写成 require('./utils/index.js') 才能用——两种都可,但本节要理解文件夹入口规则。统一出口的意义是集中暴露,避免外部到处深入 lib 目录直接引用。
npm 是什么:先理解它解决的问题
为什么需要:前面我们自己写过工具包,但功能往往单一。实际项目里经常需要日期格式化、工具函数等成熟能力,如果都自己写,成本高且容易出错。npm 是 Node.js 安装时一并提供的软件包管理器,用来下载和管理 Node.js 环境中的软件包。

怎么想:把 npm 理解成“项目依赖的下载与记录工具”。它不只把包下载下来,还会把包名、版本记录到项目清单里,方便别人拿到项目后恢复依赖。
怎么做:先确认电脑已安装 Node.js,终端能执行 npm -v。后续在项目根目录使用 npm 命令。
易错点:node 用来执行 JS 文件,npm 才是包管理器,不要混用;下载的包源码在 node_modules 中,不要手动改里面的代码。
初始化清单文件 package.json
为什么需要:项目需要一个清单文件记录“我下载过哪些包、版本是多少”。没有它,依赖关系不清晰,别人拿到项目也不好恢复。
怎么想:如果项目根目录已有 package.json,跳过初始化;没有就执行初始化命令。-y 表示所有选项使用默认值。
怎么做:在项目文件夹中打开终端,确认终端路径就是当前项目目录,然后执行:
bash
npm init -y执行后会生成 package.json。注意所在文件夹不要有中文或特殊符号,建议用英文、数字、下划线或中划线,因为 npm 包名有命名限制。
易错点:终端开错文件夹,package.json 会生成到别处;不要手写一堆依赖版本,优先让 npm 记录。
下载软件包:npm i 包名
为什么需要:初始化只建立清单,真正要用的 dayjs 还没有源码,必须下载到本地项目。
怎么想:使用 npm i 软件包名称。npm 会从资源库找到对应包,下载到当前项目的 node_modules 中,并把包名和版本写入 package.json 的 dependencies,同时生成 package-lock.json 锁定版本。
怎么做:
bash
npm i dayjs终端出现类似 added 1 package 就表示成功。此时项目下会多出 node_modules 文件夹,里面是 dayjs 源码;package.json 中会记录依赖。
易错点:npm i 后面不写包名时,含义是“根据 package.json 安装所有依赖”,不是随便下载一个包;不要直接把 node_modules 拷来拷去,拿到别人项目时通常执行 npm i 恢复依赖即可。
在代码中使用 dayjs 并调试
为什么需要:下载包不是目的,目的是在业务代码中引入并调用它完成需求:格式化日期并打印。
怎么想:npm 下载的包,引入时直接写包名,不写相对路径;自己写的模块才写 ./ 这类路径。dayjs 导出的是函数,调用后得到 dayjs 对象,再调用 format。
怎么做:在 index.js 中写:
js
const dayjs = require('dayjs')
const now = dayjs().format('YYYY-MM-DD')
console.log(now)然后在终端运行:
bash
node index.js调试时先看终端所在目录是否为项目根目录,再看 node_modules 中是否有 dayjs,最后看 require('dayjs') 是否写成了路径。运行后能打印当前年月日,说明下载、引入、调用链路都通了。

易错点:把 require('dayjs') 写成 require('./dayjs') 会找不到自己项目里的文件;用 npm index.js 运行 JS 文件是错误用法,执行文件用 node。
小结与检查
npm 软件包管理器的作用是下载软件包以及管理版本;初始化清单文件用 npm init -y;下载软件包用 npm i 软件包名字;下载的包存放在当前项目下的 node_modules 中,并记录在 package.json 中。补充:拿到缺少 node_modules 的项目时,在项目根目录执行 npm i 即可按清单安装所有依赖;像 nodemon 这类封装命令的工具包可用 npm i nodemon -g 安装到全局,用 nodemon 目标文件 替代 node 实现代码变更后自动重启,若提示命令不存在可重启 VS Code 再试。
本节目标与场景
为什么需要:我们经常拿到别人写好的项目,或者从网上找到别人的项目。打开后能看到业务代码、package.json,甚至还有 package-lock.json,但唯独没有 node_modules。这时直接运行项目通常会报错,因为项目代码里 require 的 dayjs、lodash 等软件包源码不在本地。

怎么想:package.json 记录了当前项目下载过哪些包和对应版本;package-lock.json 用来固定每个软件包的版本。只要这两个文件在,就可以让 npm 按记录把依赖重新下载回来。转写稿里说的“DGS/AGS”应理解为 dayjs,“low大/娄大师”应理解为 lodash,“n modules”应理解为 node_modules,“pack jason”应理解为 package.json。
怎么做:进入项目根目录,在终端执行 npm i。本节掌握的就是“安装所有依赖”这个动作。
易错点:不要只运行 node index.js 而不安装依赖;也不要把 npm i 和 npm i 包名 混淆,前者是安装 package.json 里记录的所有依赖。
为什么别人不传 node_modules
为什么需要:理解这一点,才知道为什么拿到项目后第一反应不是找别人要 node_modules。转写稿明确说明:每个人在本机使用 npm 下载,比磁盘之间互相传递要快,因为 npm 在本机有缓存。
怎么想:node_modules 体积可能很大,转写稿提到以后一个 node_modules 可能有几百兆,传递很慢;而用 npm 下载可能几秒钟完成。因此通常只传递 package.json 和 package-lock.json,不传递 node_modules。
怎么做:拿到项目后保留 package.json、package-lock.json 和业务代码,在项目目录下重新安装依赖。
易错点:不要手动拷贝别人的 node_modules 当作常规方案;它不适合跨机器传递,也可能和当前系统、版本记录不一致。
用 npm i 安装所有依赖
为什么需要:项目缺少依赖源码时不能正常运行。npm i 会根据 package.json 中记录的包名和版本,把项目需要的所有软件包下载到当前项目的 node_modules 中。
怎么想:命令要在“当前项目文件夹”的终端里执行。转写稿演示时特别强调看好终端所在目录,因为命令以当前文件夹为起点。执行后 npm 会自动创建 node_modules,并把包下载进去。
怎么做:
bash
npm i
易错点:终端目录不对,可能会在别的文件夹生成 node_modules;项目里必须已有 package.json,否则 npm i 没有依赖清单可读;安装后不要随意删除 package-lock.json,它用于固定版本。
调试与验证:安装后运行入口文件
为什么需要:安装完成不代表一定成功,还要运行入口文件验证依赖是否能被正确 require。转写稿的调试过程是:06 模板项目中入口文件里引入了 dayjs 和 lodash,本地没有 node_modules 时不能正常运行;确认 package.json 已记录包名和版本后,在 06 项目目录执行 npm i,终端提示成功添加两个包,项目下出现 node_modules,dayjs 和 lodash 的源码被下载下来;再运行入口文件,结果正常输出。
怎么想:笔记需求是在素材项目中安装所有依赖并运行 index.js 看是否正常;转写稿演示中的入口文件是 server.js,操作逻辑一致:先 npm i,再用 node 运行入口文件。
怎么做:
bash
npm i
node index.js如果演示项目入口是 server.js,则对应执行:
bash
node server.js易错点:运行文件用 node,下载包用 npm,不要记混;入口文件名要和项目实际文件一致;如果仍报“找不到模块”,先检查终端是否在项目根目录、npm i 是否成功、node_modules 是否生成。
小结与自测
为什么需要:把操作沉淀成固定判断。当前项目只有 package.json 没有 node_modules 时,在项目目录打开终端执行 npm i 安装所有依赖软件包。为什么 node_modules 不进行传递?因为用 npm 下载有本机缓存,比磁盘之间传递更快。怎么做:记住“进项目根目录 → npm i → 再运行入口文件”。易错点:不要跳过安装直接运行,也不要在错误目录执行安装命令。
先分清本地软件包与全局软件包
为什么需要: 学 nodemon 前必须先分清“本地软件包”和“全局软件包”,否则容易误以为 dayjs、lodash 这类包也能随便装到全局,再让所有项目直接使用。
怎么想: 本地软件包作用在当前项目内,一般封装属性和方法,源码放在项目的 node_modules 中;全局软件包作用在本机所有项目,一般封装命令和工具,安装在系统设置的位置。转写稿特别提醒:不是所有包都适合 -g,只有封装“命令和工具”的包才安装到全局。
怎么做: 本地包通过 require 引入到项目代码中使用;全局包安装后,主要是给本机提供一个可执行命令。
易错点: 不要把 dayjs 这类业务库装到全局来代替项目依赖;全局包不在当前项目的 node_modules 里,不能按本地包的方式理解。
nodemon 解决什么问题
为什么需要: 用 node 运行服务后,每次修改代码都要在终端结束进程,再重新执行 node,调试很麻烦。
怎么想: nodemon 的作用是替代 node 命令,检测代码更改并自动重启程序。它不改变业务代码逻辑,只负责监听和重启。
怎么做: 安装后,用 nodemon 执行目标 js 文件,而不是再用 node。
易错点: nodemon 不是“自动修改代码”,只是检测保存后的变化并重启;运行的文件路径要写对。
安装 nodemon:npm i nodemon -g
为什么需要: 本机默认没有 nodemon 命令,必须先安装。
怎么想: 因为 nodemon 提供的是命令工具,所以安装时要加 -g,表示安装到全局环境。转写稿中强调:安装到全局时,不用考虑终端当前在哪个项目文件夹,在哪里执行都可以。
怎么做:
bash
npm i nodemon -g笔记中的安装命令就是 npm i nodemon -g,其中 -g 代表安装到全局环境中。转写稿调试时,终端提示添加了 32 个包,这是因为 nodemon 自身还依赖其他包,被 npm 一并下载,不是只下载了一个文件。
易错点: -g 不要漏写;如果安装后提示 nodemon 命令不存在,转写稿建议把 VS Code 重启一下再试。还要注意命令拼写是 nodemon,不是转写稿里同音读出的“note moon”。
用 nodemon 启动目标文件并观察自动重启
为什么需要: 只看概念不够,要实际观察“修改代码保存后终端自动重启”的效果。
怎么想: 运行格式是 nodemon 待执行的目标 js 文件。如果终端当前在代码文件夹下,而目标文件在 07 文件夹里,就要把相对路径写清楚。
怎么做:
bash
nodemon 待执行的目标 js 文件转写稿的调试过程是:在 07 文件夹下准备 server.js,里面原有格式化日期、求数组最大值等代码;安装完全局 nodemon 后,在终端执行类似 nodemon 07/server.js 的命令。运行后终端打印结果,并且不会立刻结束,因为 nodemon 启动了一个监听服务。接着修改数组里的值,按 Ctrl+S 保存,终端会自动重启并输出新结果。
易错点: 目标文件路径要相对于终端当前目录;保存后要观察终端是否重启;结束时可手动结束进程。还要记住:执行 js 文件仍然可以用 node,但想自动重启就用 nodemon。
小结:三个问题要能答上来
- 本地软件包和全局软件包区别?本地软件包作用在当前项目,封装属性和方法;全局软件包供本机所有项目使用,封装命令和工具。
- nodemon 作用?替代
node命令,检测代码更改,自动重启程序。 - nodemon 怎么用?先确保安装
npm i nodemon -g,再用nodemon执行目标 js 文件。
把这三点记住,再按转写稿的调试过程实际改一次代码、保存一次,就能理解 nodemon 的价值。
课件图示








代码示例
示例 1:ECMAScript 标准 —— 默认导出与默认导入
package.json(放在项目根目录,用于把模块标准切换为 ECMAScript)
json
{
"type": "module"
}utils.js
js
/**
* 目标:基于 ECMAScript 标准语法,封装属性和方法并"默认"导出
*/
const baseURL = 'http://hmajax.itheima.net'
const getArraySum = arr => arr.reduce((sum, item) => sum += item, 0)
// 默认导出
export default {
url: baseURL,
arraySum: getArraySum
}index.js
js
/**
* 目标:基于 ECMAScript 标准语法,"默认"导入,工具属性和方法使用
*/
// 默认导入
import obj from './utils.js'
console.log(obj)
const result = obj.arraySum([10, 20, 30])
console.log(result)运行:node index.js
要点解释
export default { ... }一个模块只能有一个默认导出,导出的是一整个对象。- 对象里的
url、arraySum是对外属性名,baseURL、getArraySum是模块内私有变量名,两者可以不同,这正是export default的灵活之处。 - 导入时
import 变量名 from '模块名或路径',变量名(这里是obj)可以随意起,它接收的就是export default后面的那个对象,所以要用obj.arraySum(...)调用。 - 易错点 1:Node.js 默认只支持 CommonJS,不加
package.json的{"type": "module"}会直接报Cannot use import statement outside a module。 - 易错点 2:导入自己写的文件时路径必须带
./,且默认导出/导入写法下建议写全扩展名./utils.js。 - 易错点 3:同一个项目(
type: "module")里不要再混用require/module.exports。
示例 2:ECMAScript 标准 —— 命名导出与命名导入
utils.js
js
/**
* 目标:基于 ECMAScript 标准语法,封装属性和方法并"命名"导出
*/
export const baseURL = 'http://hmajax.itheima.net'
export const getArraySum = arr => arr.reduce((sum, item) => sum += item, 0)index.js
js
/**
* 目标:基于 ECMAScript 标准语法,"命名"导入,工具属性和方法使用
*/
// 命名导入
import { baseURL, getArraySum } from './utils.js'
console.log(baseURL)
console.log(getArraySum)
const result = getArraySum([10, 21, 33])
console.log(result)要点解释
- 命名导出语法是
export 修饰定义语句,即直接在const/let/function前加export,可以导出多个。 - 命名导入必须用大括号
import { 同名变量 } from '模块名或路径',大括号里的名字必须和导出时的变量名完全一致,否则是undefined或报错。 - 易错点:笔记原文的
index.js里多写了一行console.log(obj),命名导入并没有obj这个变量,运行会抛ReferenceError: obj is not defined,上面已删掉;从默认导入复制代码过来时要检查这类残留。 - 易错点:命名导出不需要(也不能)写成
export default { baseURL },export default与export是两套写法。 - 选择原则:按需加载用命名导出导入,全部加载用默认导出导入。
示例 3:包的概念 —— 统一出口 + 导入软件包文件夹
utils/lib/arr.js(素材模块,CommonJS 导出)
js
const getArraySum = arr => arr.reduce((sum, item) => sum += item, 0)
module.exports = { getArraySum }utils/lib/str.js(素材模块,CommonJS 导出)
js
const checkUser = user => user.length >= 8
const checkPwd = pwd => pwd.length >= 6
module.exports = { checkUser, checkPwd }utils/index.js(包的唯一出口)
js
/**
* 本文件是,utils 工具包的唯一出口
* 作用:把所有工具模块方法集中起来,统一向外暴露
*/
const { getArraySum } = require('./lib/arr.js')
const { checkUser, checkPwd } = require('./lib/str.js')
// 统一导出所有函数
module.exports = {
getArraySum,
checkUser,
checkPwd
}index.js(导入的是包文件夹,不是模块文件)
js
/**
* 目标:导入 utils 软件包,使用里面封装的工具函数
*/
const obj = require('./utils')
console.log(obj)
const result = obj.getArraySum([10, 20, 30])
console.log(result)要点解释
- 包 = 把模块、代码、其他资料聚合成一个文件夹;
utils就是自封装的软件包(对外提供工具方法),而写业务代码的文件夹叫项目包。 - 包根目录必须有
package.json清单文件,记录 name、version、description、main(入口文件)、author、license 等信息。 utils/index.js的作用是唯一出口:先用require('./lib/arr.js')把分散模块里的方法引进来(配合对象解构简写),再module.exports = { ... }统一向外暴露。- 易错点 1:
require('./utils')里写的是文件夹,不带文件名。默认引入的是包下的index.js;如果没有index.js,才会去package.json的main属性指定的文件。所以入口文件名写错就会Cannot find module。 - 易错点 2:
utils/index.js里require的路径是相对当前文件的,必须写./lib/arr.js,不能写成./arr.js。 - 易错点 3:本例是 CommonJS 写法(
require/module.exports),运行它的项目不要加"type": "module",否则require会失效。
常见错误
SyntaxError: Cannot use import statement outside a module- 现象:
utils.js、index.js里写了import/export,node index.js直接抛语法错误。 - 原因:项目仍是 CommonJS 环境(无
package.json或未声明"type": "module")。 - 修法:在模块所在文件夹执行
npm init -y,再手动加"type": "module";或把文件后缀改为.mjs后运行。
- 现象:
默认导入拿到
undefined或解构出来是undefined- 现象:
export default { url, arraySum }之后写import { url } from './utils.js',打印undefined。 - 原因:默认导出只有一个整体值,不能被
{}按名解构(那是命名导入的语法)。 - 修法:改为
import obj from './utils.js'再用obj.url;若确实想按名取,就把导出改成export const url = ...的命名导出。
- 现象:
SyntaxError: The requested module './utils.js' does not provide an export named 'baseUrl'- 现象:命名导入时报"没有该导出"。
- 原因:导入名与导出名大小写/拼写不一致(
baseURLvsbaseUrl),或目标模块其实用的是默认导出。 - 修法:逐一核对导出名与导入名完全一致;不确定时先用
import * as obj from './utils.js'打印对象看真实键名。
Error: Cannot find module 'utils'(自己写的文件却提示找不到)- 现象:
import obj from 'utils.js'或require('utils')报找不到模块。 - 原因:漏了相对路径前缀
./,Node 把它当成 npm 包去node_modules里找;ESM 下还要求写全扩展名。 - 修法:本地文件一律写
./utils.js(ESM 带扩展名);只有内置模块和 npm 包才直接写名字。
- 现象:
require is not defined in ES module scope- 现象:切换
"type": "module"后,原来能跑的const obj = require('./utils')全挂。 - 原因:同一文件夹内模块标准只能二选一,ESM 环境不提供
require。 - 修法:把该文件整体改为
import写法;确需混用时用import { createRequire } from 'module'生成require。
- 现象:切换
拿到别人的项目,
node index.js报Cannot find module 'dayjs'- 现象:有
package.json、有源码,但一跑就缺包。 - 原因:
node_modules不随项目传递,本地没装依赖。 - 修法:在项目根目录执行
npm i,按package.json记录一次性装齐;装完再看package-lock.json是否被改动。
- 现象:有
nodemon : 无法将"nodemon"项识别为 cmdlet…- 现象:
npm i nodemon -g显示成功,敲nodemon仍提示命令不存在。 - 原因:全局 bin 目录未进入当前终端的 PATH,或终端是安装前打开的旧会话。
- 修法:重启终端 / VS Code 后重试;仍不行用
npm root -g与npm bin -g检查全局路径并加入 PATH。
- 现象:
把
dayjs装到全局,想在项目里import却报找不到- 现象:
npm i dayjs -g后项目里仍Cannot find module 'dayjs'。 - 原因:全局包供命令行使用,Node 解析模块时不会去全局目录找;只有封装命令/工具的包才适合装全局。
- 修法:项目依赖一律本地安装(
npm i dayjs,不加-g);-g只留给nodemon这类命令行工具。
- 现象:
练习
同一需求的两种写法:把"基地址 + 数组求和"分别用默认导出/导入、命名导出/导入各实现一遍,放在两个不同子文件夹里(各自带
package.json),分别node运行,比较导入语句和取用方式的差异。混合导出排错:写一个模块同时包含
export default {...}和export const version = '1.0.0',在主文件中用一条import语句同时取到默认对象和version,再用import * as obj打印全部键名,验证你对"默认导出在命名空间里叫什么"的理解。自建软件包:新建
tools/文件夹,内含lib/arr.js、lib/str.js和一个只做统一出口的index.js,主文件用import obj from './tools/index.js'使用。然后删掉index.js,改在package.json的main里指向别的入口,验证入口解析顺序。依赖复原演练:用
npm init -y+npm i dayjs建一个小项目,把日期格式化为YYYY-MM-DD HH:mm:ss。然后删除整个node_modules,先运行确认报错内容,再npm i复原并运行成功;对比复原前后package-lock.json是否有变化,写出你的结论。nodemon 对照实验:用
node启动一个带console.log的脚本,改代码保存,观察终端;再用nodemon启动同一脚本重复操作。记录两次现象差异,并额外尝试"故意写一个语法错误"时 nodemon 的表现。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 包的概念 | 16:16 |
| 2 | npm软件包管理器 | 14:55 |
| 3 | npm安装所有依赖 | 06:58 |
| 4 | npm全局软件包-nodemon | 08:41 |
小结
"type": "module"是文件夹级开关,一改全改;同一目录里不要指望 CommonJS 与 ESM 混着跑。- 选默认导出还是命名导出,看调用方需要"整体拿走"还是"按需取用";拿不准就先用
import * as obj探一下真实导出结构。 - 路径三原则:本地文件写
./且 ESM 带扩展名,内置模块与 npm 包直接写名字,包文件夹可写到文件夹名。 - 包 = 文件夹,
package.json是身份证;入口优先级是index.js→main字段,找不到就是"模块不存在"。 npm init -y只做一次,npm i 包名是加依赖,npm i(无参数)是按清单复原全部依赖——三者别混。package-lock.json的价值在于锁版本,别随手删;它和node_modules都不该靠拷贝传递。-g只给"封装命令与工具"的包(如 nodemon),业务依赖一律本地装;装完全局命令不生效,先重启终端。- 遇到模块类报错,排查顺序固定为:模块标准是否声明 → 路径与扩展名 → 导出名是否匹配 → 依赖是否已安装。