Skip to content

模块化 ​

本页对应 模块化,共 3 段视频,约 29 分钟。

学习目标 ​

  1. 能说明 Node.js 中“每个文件都是一个独立模块”的含义,并列举模块化在复用性、按需加载和作用域隔离方面的优势。
  2. 能区分 CommonJS 与 ECMAScript 标准模块语法,正确使用 module.exports / require 或 ESM 的 export / import 完成模块关联。
  3. 能通过在执行文件所在目录配置 package.json 的 "type": "module",使 Node.js 正确识别 ECMAScript 标准模块语法。
  4. 能使用 export default 导出对象,并使用 import 变量名 from '模块路径' 完成默认导入,运行代码验证导入结果。
  5. 能使用 export 修饰变量或函数完成命名导出,并使用 import { 同名变量 } from '模块路径' 按需导入,说明命名导入时变量名需与导出名一致。
  6. 能根据“全部加载”或“按需加载”的实际需求,选择默认导出导入或命名导出导入,并编写可运行示例验证。

知识点 ​

本节说明与转写稿使用边界 ​

本节笔记是“知识点自测”,包含两道选择题:一道考 map + reduce 的链式运算,一道考对象解构。需要先说明:转写稿主要讲 Node.js 模块化(CommonJS、ECMAScript 标准、module.exports、require、import、export default 等),与这两道自测题没有直接知识点对应,因此不能把模块化概念硬套到本题。转写稿中可借用的,是讲师反复采用的调试习惯:先打印接收结果,再调用方法验证,例如“打印一下 obj”“定义 result 后打印结果”。另外,转写稿中的同音字应纠正为:Node.js、CommonJS、module.exports、require、import、export default、path、querystring。本节笔记未附图片,故正文不添加图片标签。

自测题一:map 与 reduce 的链式运算 ​

为什么需要: 实际开发中经常要先对数组每一项做统一变换,再把变换后的结果汇总成一个值。map 负责“逐项变换”,reduce 负责“聚合汇总”,两者链式使用很常见。

怎么想: 先看 arr.map(val => val + 1)。原数组是 [10, 20, 30],每一项加 1 后得到新数组 [11, 21, 31]。注意 map 不会修改原数组,而是返回新数组。接着 reduce((sum, val) => sum += val, 0) 从初始值 0 开始累加:0 + 11 + 21 + 31 = 63。所以结果不是原数组和 60,而是 63。

怎么做: 原代码如下:

js
const arr = [10, 20, 30]
const result = arr.map(val => val + 1).reduce((sum, val) => sum += val, 0)
console.log(result)

答案选 B:63。调试时可以拆开看:先 console.log(arr.map(val => val + 1)),确认中间数组是 [11, 21, 31];再打印 result,确认最终值是 63。这正符合转写稿中“先打印、再调用、再核对”的调试思路。

易错点: 一是把 map 后的结果仍当成原数组,误算成 60;二是忽略 reduce 的初始值 0,虽然本题从 0 开始不影响数值,但初始值类型会影响后续累加;三是误以为 map 会改变 arr;四是把 sum += val 当成“只做加法不返回值”,实际上赋值表达式会返回累加后的 sum。

自测题二:对象解构按属性名匹配 ​

为什么需要: 对象解构可以少写重复的 obj.xxx,让取值更简洁。但解构不是“猜你想取哪个属性”,而是严格按照属性名匹配。

怎么想: 代码是 const { uname } = obj,它等价于从 obj 中读取 obj.uname。而 obj 只有 username 和 password 两个属性,没有 uname。因此 uname 的值是 undefined,不是 'itheima'。

怎么做: 原代码如下:

js
const obj = { username: 'itheima', password: '777777' }
const { uname } = obj
console.log(uname)

按实际运行结果应选 B:undefined。这里要特别纠正:笔记中该题答案标注为“A 选项正确”,但根据代码实际运行,A 不正确,应为 B。若想通过解构拿到 username 的值并命名为 uname,应使用别名写法:

js
const { username: uname } = obj
console.log(uname) // 'itheima'

调试时可以先 console.log(obj) 确认对象里没有 uname 属性,再打印 uname,看到 undefined 就能定位原因。

易错点: 一是误以为 uname 会自动匹配 username;二是混淆“解构变量名”和“原属性名”;三是以为解构不存在的属性会报错,实际上普通对象解构不存在的属性得到 undefined;四是拼写错误,如 userName 与 username 大小写不一致。

调试过程与自测建议 ​

做这类自测题,不要只靠眼睛心算。第一题先打印 map 的中间结果,再打印 reduce 的最终结果;第二题先打印原对象,再打印解构变量。转写稿中讲师多次通过“打印 obj”“打印 result”来确认程序实际值,这种习惯可以直接迁移到本节自测。最后核对答案:第一题选 B;第二题按代码实际结果选 B,并注意笔记答案存在标注错误。

本节范围与图片说明 ​

本次转写稿只覆盖“Node.js 模块化”部分,目录中的软件包、npm、Express、接口开发、跨域没有对应讲解内容,本文不编造,留待后续材料补充。原笔记中的语法截图未随文本提供,无法嵌入图片;下面用等价代码复现截图要点,图片 alt 不再虚构。

Node.js 模块化是什么 ​

为什么需要:项目一大,把所有代码写进一个文件会难维护、难复用,变量也容易冲突。Node.js 把每个文件视为独立模块,模块之间通过标准语法关联。

怎么想:一个文件就是一个作用域。想读写文件就引入 fs,想处理路径就引入 path,想创建服务就引入 http,想把查询字符串转对象就引入 querystring;自己封装的 utils.js 也可以被别的文件引入。模块化的价值是复用、按需加载、作用域独立。

怎么做:在需要暴露内容的文件里导出,在使用方导入。Node.js 默认支持 CommonJS 标准,也支持官方 ECMAScript 标准,但后者需要项目配置。

易错点:不要把“文件”理解成“函数”;模块内变量默认私有,不导出外部不能直接用。转写稿里的“作用欲”应纠正为“作用域”,“拷文解S”应纠正为 CommonJS。

CommonJS 的导出与导入 ​

为什么需要:在 utils.js 中封装基地址和求和函数,若要在 index.js 使用,必须先导出再导入。

怎么想:CommonJS 规定导出用 module.exports,导入用 require。导入内置模块写模块名,导入自定义模块写相对路径。

怎么做:

js
// utils.js
const baseURL = 'http://example.com'
function getSum(arr) {
  return arr.reduce((sum, item) => sum + item, 0)
}
module.exports = {
  url: baseURL,
  resum: getSum
}
js
// index.js
const obj = require('./utils.js')
console.log(obj) // 先确认导出的对象
const result = obj.resum([5, 1, 2, 3])
console.log(result)

调试时先打印 obj,确认 url、resum 是否存在,再调用 obj.resum。转写稿中的“mod点expose”应纠正为 module.exports,“youtu/UTO”应纠正为 utils,“indice/index”应纠正为 index.js。

易错点:自定义模块不能只写 utils.js,要写 ./utils.js;module.exports 不要拼错;require 得到的是导出对象,不是模块文件本身。

ECMAScript 默认导出与导入 ​

为什么需要:ECMAScript 是官方模块标准,语法更接近现代前端写法。默认导出适合把一个对象整体暴露出去。

怎么想:导出用 export default,导入用 import 变量名 from '模块路径'。变量名可自定义,因为它接收的是默认导出的整体对象。

怎么做:

js
// utils.js
const baseURL = 'http://example.com'
function getSum(arr) {
  return arr.reduce((sum, item) => sum + item, 0)
}
export default {
  url: baseURL,
  resum: getSum
}
js
// index.js
import obj from './utils.js'
console.log(obj)
console.log(obj.resum([5, 1, 2, 3]))

Node.js 默认按 CommonJS 识别,因此要在执行文件所在文件夹新建 package.json,写入:

json
{
  "type": "module"
}

然后再用 node index.js 执行。转写稿中的“音泡的”应纠正为 import,“デフォート”应纠正为 default,“AQY6”应纠正为 JSON,“piage jason”应纠正为 package.json。

易错点:没有 type: module 时直接写 import/export 会报错;默认导入只写一个变量名,不要加大括号;路径仍要写相对路径。

ECMAScript 命名导出与导入 ​

为什么需要:一个模块可能导出多个变量,但使用方只需要其中一个,这时命名导出更适合按需加载。

怎么想:在定义常量或函数前加 export;导入时用 import { 同名变量 } from '路径',名字必须与导出名一致。

怎么做:

js
// utils.js
export const baseURL = 'http://example.com'
export function getSum(arr) {
  return arr.reduce((sum, item) => sum + item, 0)
}
js
// index.js
import { baseURL, getSum } from './utils.js'
console.log(baseURL)
console.log(getSum([12, 13, 3]))

只想要一个时可写 import { getSum } from './utils.js'。命名导出和默认导出可以同时使用,但本段重点是按需导入。

易错点:命名导入变量名必须与导出名一致;不要漏写大括号;自定义模块路径不要漏 ./。转写稿中的“Xma script/actscript”应统一纠正为 ECMAScript。

两种标准怎么选与调试顺序 ​

为什么需要:选择标准影响项目配置和导入写法,混用容易报错。

怎么想:CommonJS 是 Node.js 默认支持,写法是 module.exports + require;ECMAScript 需要 package.json 中 "type": "module",默认导出用 export default + import 变量,命名导出用 export + import { 同名变量 }。需要整体加载用默认导出,需要按需加载用命名导出。

怎么做:调试时先打印导入结果,确认对象或变量值,再调用函数;报错先检查模块路径、导出拼写、package.json 配置。

易错点:不要在同一文件里随意混用两套语法;不要把 module.exports 写成 module.expose;不要把 package.json 写成 piage jason。

学习目标与材料边界 ​

本节笔记列出的目标是:模块化、软件包与引入、npm、本地/全局包、Express 搭建 Web 服务、同源策略与跨域。需要先说明:转写稿实际只详细讲了 Node.js 模块化中的 CommonJS、ECMAScript 默认导出/导入、命名导出/导入;软件包、npm、Express、跨域在本段转写稿中没有展开。因此下面只依据已有材料讲透模块化,未展开的目标不编造命令和配置,留到后续材料核对。本节笔记未附图片;转写稿提到的“语法截图”,下面用代码块保留其语法要点,并把“拷文解S/common解S”纠正为 CommonJS,“音泡的”纠正为 import,“デフォート”纠正为 default。

模块化是什么,为什么需要 ​

为什么需要:项目变大后,读写文件、拼路径、建 Web 服务、处理查询字符串等逻辑都堆在一个文件里,会难复用、难维护,也容易变量重名。怎么想:Node.js 把每个文件视为单独模块,模块有独立作用域;需要能力时引入模块,需要对外提供能力时导出。怎么做:转写稿中的 index.js 本身就是一个模块;它通过 require 引入内置的 fs、path、http、querystring 等模块,也可以引入自己写的模块文件。易错点:不要以为同一文件夹下的变量天然互通;模块内未导出的常量、函数,外部不能直接使用。模块化的好处是提高复用性、按需加载、作用域独立,减少命名冲突。

CommonJS 的导出与导入 ​

为什么需要:utils.js 里封装了基地址和求和函数,index.js 想用,就必须有标准语法把私有成员暴露出去再引入。怎么想:CommonJS 规定导出用 module.exports,导入用 require;导入得到的值就是 module.exports 导出的对象。怎么做:可写成下面的语法骨架(转写稿未给具体基地址和求和实现,故不虚构实现):

js
// utils.js
const baseURL = '...';
function getSum(arr) { /* 求数组元素总和 */ }

module.exports = {
  url: baseURL,
  resum: getSum
};
js
// index.js
const obj = require('./utils.js');
console.log(obj);

const result = obj.resum([5, 1, 2, 3]);
console.log(result);

运行调试时,在终端执行 node 01/index.js,先打印 obj,确认它含有 url 和 resum,再调用 obj.resum(...) 看结果。易错点:导入内置模块写模块名,如 require('fs');导入自定义模块写路径,如 require('./utils.js'),不要漏掉 ./。module.exports 对象里的属性名可自定义,右边放模块内要暴露的常量或函数。

ECMAScript 默认导出与导入 ​

为什么需要:CommonJS 是 Node 默认支持的标准,但官方还推出 ECMAScript 标准;需要知道如何用 export default 和 import。怎么想:默认导出把一组能力放在一个对象里整体导出;默认导入用一个变量接收这个对象。怎么做:

js
// utils.js
const baseURL = '...';
function getSum(arr) { /* 求数组元素总和 */ }

export default {
  url: baseURL,
  resum: getSum
};
js
// index.js
import obj from './utils.js';
console.log(obj);
const result = obj.resum([5, 1, 2, 3]);
console.log(result);

易错点:Node 默认识别 CommonJS,直接用 node 02/index.js 可能不识别 ESM 语法;要在被执行文件所在文件夹新建 package.json,写入 {"type":"module"},再运行。转写稿中的调试顺序是:先打印导入对象,确认结构,再调用方法。

ECMAScript 命名导出与导入 ​

为什么需要:一个模块可能导出多个成员,但使用方只需要其中一个;命名方式适合按需加载。怎么想:在定义常量或函数前加 export,导入时用大括号列出同名变量。怎么做:

js
// utils.js
export const baseURL = '...';
export function getSum(arr) { /* 求数组元素总和 */ }
js
// index.js
import { baseURL, getSum } from './utils.js';
console.log(baseURL, getSum);

const result = getSum([12, 13, 3]);
console.log(result);

易错点:命名导入的大括号内变量名要和导出名一致;只想要一个就只写一个。默认导出和命名导出可同时使用,但本段重点是比较:想整体加载用默认导出,想按需加载用命名导出。

标准选择与调试要点 ​

为什么需要:同一项目要知道当前用哪套模块标准,否则导入导出会报错。怎么想:CommonJS 是 Node 默认;ECMAScript 需 package.json 的 type: module 切换。怎么做:按转写稿分别运行 node 01/index.js、node 02/index.js、node 03/index.js,通过 console.log(obj) 和调用结果验证。易错点:package.json 要放在被执行模块所在文件夹;自定义模块用相对路径;内置模块写名字。至于软件包、npm、本地/全局包、Express、同源策略与跨域,本段转写稿没有给出具体定义和操作,不能凭空补安装命令或跨域配置,后续材料再展开。

目标 ​

本节要了解模块化概念和好处,以及 CommonJS 标准语法中的导出和导入。学完后,应能解释“为什么一个文件就是一个模块”,并能用 module.exports 和 require 完成一个最小示例。

模块化概念与好处 ​

为什么需要: 项目里经常要读写文件、拼接路径、创建服务、处理查询参数。前面写代码时已经用过 fs、path、http、querystring 等模块;如果每个项目都重写一遍,既麻烦又容易冲突。
怎么想: Node.js 把每个文件都当成一个独立模块。模块内定义的变量和函数是独立作用域的,不会随便污染别的文件。
怎么做: 先记住核心结论:一个文件就是一个模块;项目由多个模块组成;模块可以提高复用性、按需加载,并保持独立作用域。Node.js 执行模块代码时,会用函数封装器封装,如下图所示。

Node.js 模块函数封装器示意

易错点: 不要把“模块”理解成某个函数或某个对象;在 Node.js 里,一个文件就是一个模块。模块内变量默认私有,不能在另一个文件里直接使用。

模块之间如何联系 ​

为什么需要: 模块独立作用域解决了变量冲突,但 utils.js 里封装好的基地址和求数组总和的函数,index.js 也需要使用。
怎么想: 模块之间不能靠“直接访问私有变量”联系,而要用标准语法:先导出,再导入。
怎么做: 项目由多个模块组成,每个模块保持独立,需要对外使用时通过导出/导入建立联系。

多个模块组成项目示意

易错点: 模块独立不等于不能通信;通信要靠 CommonJS 的导出和导入语法。不要因为“在同一个项目里”就以为可以直接访问另一个文件的变量。

CommonJS 导出语法 ​

为什么需要: utils.js 中的 baseURL 和 getArraySum 默认是私有的,index.js 无法直接使用。
怎么想: 把要对外暴露的内容放进 module.exports 对象里:属性名是对外名称,值指向模块内的私有变量或函数。
怎么做:

js
module.exports = {
  对外属性名: 模块内私有变量
}

易错点: module.exports 拼写要准确;对象里写的是“对外属性名: 模块内私有变量”,对外属性名可以自定义,但调用时要和它一致。

CommonJS 导入语法 ​

为什么需要: index.js 要拿到 utils.js 导出的对象,才能调用里面的属性和方法。
怎么想: 用 require 导入;变量接收到的就是目标模块导出的对象。内置模块直接写模块名,自定义模块写模块文件路径。
怎么做:

js
const 变量名 = require('模块名或路径')
// Node.js 环境内置模块直接写模块名(例如:fs,path,http)
// 自定义模块:写模块文件路径(例如:./utils.js)

变量名的值接收的就是目标模块导出的对象

易错点: 自定义模块要写相对路径,例如 ./utils.js;fs、path、http 这类内置模块直接写名字。require('./utils.js') 返回的是导出对象,不是函数本身。

需求落地:utils.js 与 index.js ​

为什么需要: 用一个具体需求体验导出和导入:utils.js 封装基地址和求数组总和的函数,index.js 导入使用。
怎么想: 先在 utils.js 中导出两个对外属性,再在 index.js 中用 require 接收对象,最后通过对象调用方法。
怎么做: 需求示意如下。

utils 与 index 模块需求示意

utils.js:导出。

js
/**
 * 目标:基于 CommonJS 标准语法,封装属性和方法并导出
 */
const baseURL = 'http://hmajax.itheima.net'
const getArraySum = arr => arr.reduce((sum, item) => sum += item, 0)

// 导出
module.exports = {
  url: baseURL,
  arraySum: getArraySum
}

index.js:导入使用。

js
/**
 * 目标:基于 CommonJS 标准语法,导入工具属性和方法使用
 */
// 导入
const obj = require('./utils.js')
console.log(obj)
const result = obj.arraySum([5, 1, 2, 3])
console.log(result)

易错点: 导出时用了 url、arraySum,导入后就要用 obj.arraySum(...);不要写成 obj.getArraySum(...)。路径写错、文件名大小写不一致,也会导致导入失败。

运行与调试 ​

为什么需要: 只看代码不容易确认 require 到底拿到了什么,需要打印验证。
怎么想: 先打印 obj,确认它是不是 module.exports 导出的对象;再调用 obj.arraySum([5, 1, 2, 3]) 看结果。
怎么做: 在示例的 01 文件夹下打开集成终端,执行 node 01/index.js(以实际路径为准)。终端会先打印一个对象,里面能看到 url 和 arraySum 两个属性;随后调用求和方法,数组 [5, 1, 2, 3] 的和应为 11。
易错点: 终端所在目录不对、命令路径不对,都会找不到文件;如果忘记写 ./,Node.js 会按内置模块或包名查找,而不是找当前目录下的自定义文件。

小结 ​

  1. Node.js 中什么是模块化?每个文件都是独立的模块。
  2. 模块之间如何联系?使用特定语法导出和导入。
  3. CommonJS 标准如何导出和导入?导出:module.exports = {};导入:require('模块名或路径')。
  4. 模块名/路径如何选择?内置模块写名字,例如 fs、path、http;自定义模块写模块文件路径,例如 ./utils.js。

从 CommonJS 到 ECMAScript 标准 ​

为什么需要:Node.js 环境默认支持的是 CommonJS 规范,导出用 module.exports,导入用 require。后来官方推出了 ECMAScript 标准语法,本节要掌握其中的“默认导出和默认导入”。模块之间之所以要导出、导入,是因为每个文件都是独立作用域,utils.js 里定义的 baseURL 和 getArraySum 默认只能在当前文件内使用,想让 index.js 使用,就必须按标准语法导出再导入。

怎么想:先明确需求:封装一个工具模块,导出基地址和求数组元素和的函数,再在 index.js 中导入并验证效果。注意 Node.js 默认只支持 CommonJS,所以使用 ECMAScript 语法前要先做项目配置。

怎么做:在 utils.js 中定义常量与函数,再用 export default 导出对象;在 index.js 中用 import 接收对象;最后在模块所在文件夹新建 package.json,设置 "type": "module"。

易错点:不要把 CommonJS 的 module.exports 和 ECMAScript 的 export default 混用;不要忘记 package.json 配置,否则 Node.js 可能仍按 CommonJS 解析并报错。

默认导出的写法 ​

为什么需要:工具模块里的 baseURL、getArraySum 是模块内私有变量,只有通过导出,外部模块才能拿到它们。

怎么想:默认导出使用 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,不要漏掉 default;一个模块通常只保留一个默认导出对象;对象属性名不是固定必须叫 url、arraySum,但导入后要按你定义的属性名使用。

默认导入的写法 ​

为什么需要:index.js 要使用工具模块导出的对象,就必须先导入。

怎么想:默认导入语法是 import 变量名 from '模块名或路径'。变量名接收到的就是目标模块 export default 导出的那个对象。自定义模块要写相对路径。

怎么做:保留笔记中的 index.js 代码:

js
/**
 * 目标:基于 ECMAScript 标准语法,"默认"导入,工具属性和方法使用
 */
// 默认导入
import obj from './utils.js'
console.log(obj)
const result = obj.arraySum([10, 20, 30])
console.log(result)

转写稿中的调试过程是:先打印 obj,确认它接收到了 utils.js 默认导出的对象,能看到 url 和 arraySum 属性;再调用 obj.arraySum([10, 20, 30]),最终得到结果 60。

易错点:默认导入不要加大括号,import { obj } 是命名导入的写法,不是本节默认导入;自定义模块路径要写 ./utils.js,不要只写 utils.js;变量名可以自定义,但建议语义清晰。

配置 package.json 切换模块标准 ​

为什么需要:Node.js 默认支持 CommonJS,直接运行含 import、export default 的文件可能无法识别。需要通过项目清单文件声明模块标准。

怎么想:在执行模块文件所在文件夹新建 package.json,设置 type 为 module,告诉 Node.js 当前项目按 ECMAScript 标准解析模块。

怎么做:

json
{ "type": "module" }

笔记中写作 { “type”: "module" },实际代码应使用标准英文双引号。配置完成后,再到终端执行 node 02/index.js 或对应路径下的 index.js。

易错点:package.json 要放在执行文件所在文件夹;type 的值是小写 module;JSON 文件不能写注释;如果仍报 Cannot use import statement outside a module,优先检查该配置是否生效。

运行验证与小结 ​

怎么做:在终端进入对应目录,执行 node index.js。先看 console.log(obj) 的输出是否为默认导出的对象,再看 console.log(result) 是否为数组元素和。转写稿中通过“打印对象 → 调用方法 → 再打印结果”的方式完成了验证。

易错点:不要只看代码不运行;路径、文件名、package.json 位置都会影响结果;默认导出和默认导入要成对理解:export default { ... } 导出一个对象,import 变量名 from '...' 用变量接收这个对象。

小结:ECMAScript 标准默认导出语法是 export default {},默认导入语法是 import 变量名 from '模块名或路径';让 Node.js 切换为 ECMAScript 标准,需要在模块所在文件夹新建 package.json 并设置 { "type": "module" }。

课件图示 ​

课件第6页:需求:封装并导出基地址和求数组元素和的函数 默认标准使用: 1. 导出:expo

课件第7页:总结 总结 1. ECMAScript 标准规定如何默认导出和导入模块呢? 导

课件第8页:需求:封装并导出基地址和求数组元素和的函数 命名标准使用: 1. 导出:expo

课件第9页:总结 总结 1. Node.js 支持哪2 种模块化标准? CommonJS

课件第10页:包:将模块,代码,其他资料聚合成一个文件夹 包分类:  项目包:主要用于编写项

课件第19页:Node.js 模块化: 概念:每个文件当做一个模块,独立作用域,按需加载 使用

课件第11页:总结 总结 1. 什么是包?  将模块,代码,其他资料聚合成的文件夹 2. 包

课件第3页:定义: 概念:项目是由很多个模块文件组成的 好处:提高代码复用性,按需加载,独立

代码示例 ​

示例一:CommonJS 标准 —— module.exports 导出 / require 导入 ​

utils.js(工具模块,负责导出)

js
/**
 * 目标:基于 CommonJS 标准语法,封装属性和方法并导出
 */
const baseURL = 'http://hmajax.itheima.net'
const getArraySum = arr => arr.reduce((sum, item) => sum += item, 0)

// 导出:属性名自己定,值是模块内的私有变量
module.exports = {
  url: baseURL,
  arraySum: getArraySum
}

index.js(业务模块,负责导入使用)

js
/**
 * 目标:基于 CommonJS 标准语法,导入工具属性和方法使用
 */
// 导入:自定义模块写相对路径
const obj = require('./utils.js')
console.log(obj)                       // { url: 'http://hmajax.itheima.net', arraySum: [Function] }

const result = obj.arraySum([5, 1, 2, 3])
console.log(result)                    // 11

要点解释

  • module.exports = { ... } 是 CommonJS 的导出语法,导出的就是这个对象;require('./utils.js') 的返回值就是这个对象,所以要用 obj.arraySum 调用。
  • 易错点 1:模块内定义的 baseURL、getArraySum 是私有的,不导出在 index.js 里直接用会报错(每个文件独立作用域)。
  • 易错点 2:require 里内置模块写名字(fs、path、http),自定义模块写相对路径(./utils.js),漏掉 ./ 会被当成内置包名去找而报错。
  • 易错点 3:module.exports 写错成 module.export、exports 拼写错误,都会导致导出为空对象 {}。

示例二:ECMAScript 标准 —— 默认导出 export default / 默认导入 import ​

package.json(必须先建,才能用 ESM 语法)

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)                    // 60

要点解释

  • Node.js 默认只支持 CommonJS,想用 export default / import,必须在模块所在文件夹新建 package.json 并写 { "type": "module" },否则会报 Cannot use import statement outside a module 之类错误。
  • 默认导入的变量名是自己随便起的(这里叫 obj),因为它接的就是 export default 后面那个对象。
  • 易错点:默认导入不要加大括号,写成 import { obj } from './utils.js' 是命名导入的写法,会取不到值。
  • 易错点:package.json 的路径要和执行的文件同目录层级,放错文件夹配置不生效。

示例三:ECMAScript 标准 —— 命名导出 export / 命名导入 import { } ​

utils.js(导出)

js
/**
 * 目标:基于 ECMAScript 标准语法,命名导出(按需导出)
 */
export const baseURL = 'http://hmajax.itheima.net'
export const getArraySum = arr => arr.reduce((sum, item) => sum += item, 0)

// 不想对外暴露的就不加 export
const secret = 'internal-only'

index.js(导入)

js
/**
 * 目标:基于 ECMAScript 标准语法,命名导入(按需加载)
 */
import { baseURL, getArraySum } from './utils.js'

console.log(baseURL)        // http://hmajax.itheima.net
console.log(getArraySum)    // [Function: getArraySum]

const result = getArraySum([12, 13, 3])
console.log(result)         // 28

// 只想要其中一个时,就只写那一个
// import { getArraySum } from './utils.js'

要点解释

  • 命名导出就是在定义语句前加 export,一个模块可以导出 N 个;命名导入用 import { 名1, 名2 } from '路径',实现按需加载。
  • 易错点 1:命名导入的变量名必须与导出名完全同名,不能像默认导入那样随便起名字。
  • 易错点 2:大括号不能少,import baseURL from './utils.js' 是默认导入写法,会得到 undefined。
  • 易错点 3:命名导入不需要(也不能)写成 { baseURL: xxx } 那样随手改名;另外同样需要 package.json 里 "type": "module" 才能跑。
  • 使用选择:整个模块的内容都要用 → 默认导出;只按需取其中几个 → 命名导出。两者也可以同时使用。

常见错误 ​

  1. 解构拿到 undefined,却以为是数据没传过来

    • 现象:const { uname } = obj 打印 undefined,而 obj 里明明有用户名。
    • 原因:解构用的是对象的键名,obj 的键叫 username,没有 uname 这个键;解构不是"随便起个名字就能取到值"。
    • 修法:键名对齐 const { username } = obj;确实要改名就用 const { username: uname } = obj。
  2. 在 Node 里写 import / export 直接报 Cannot use import statement outside a module

    • 现象:文件里只有一行 import obj from './utils.js',运行即报语法错误。
    • 原因:Node 默认按 CommonJS 解析 .js,ESM 语法需要显式声明。
    • 修法:在执行文件所在目录建 package.json 写 {"type": "module"};或把文件改成 .mjs;或整体退回 require / module.exports。
  3. require 自定义模块时只写名字,报 Cannot find module 'utils'

    • 现象:内置模块 require('fs') 正常,换成自己的文件就找不到。
    • 原因:不写路径时 Node 只在 node_modules 里找包,不会去当前目录找文件。
    • 修法:自定义模块一律写相对路径且带后缀 require('./utils.js');只有内置模块和已安装的包才写名字。
  4. 命名导入时变量名和导出名不一致,拿到 undefined 或直接报错

    • 现象:export const baseURL = ...,导入写 import { url } from './utils.js',报 "does not provide an export named 'url'"。
    • 原因:命名导出/导入靠名字匹配,不是靠位置;默认导出才允许导入时随意命名。
    • 修法:保持同名 import { baseURL };必须改名时用 import { baseURL as url }。
  5. module.exports 被重复赋值,前面导出的属性"消失"

    • 现象:先后写了两次 module.exports = {...},导入方只看到最后一个对象里的属性。
    • 原因:module.exports 是赋值覆盖,不是合并;后一次把前一次整个替换掉。
    • 修法:一次性写成一个对象导出;或用 module.exports.xxx = ... 逐条追加;exports 与 module.exports 不要混着用。
  6. reduce 忘传初始值 / 误以为 map 改了原数组

    • 现象:arr.reduce((sum, val) => sum += val) 在某些数据上得到意外结果;或 arr.map(v => v + 1) 之后原 arr 没变,误判为"没生效"。
    • 原因:reduce 不传初始值时以第一个元素为起点,空数组会直接抛错;map 返回新数组,不改原数组。
    • 修法:求和统一写 reduce((sum, val) => sum + val, 0);需要新值就接收返回值 const next = arr.map(...)。

练习 ​

  1. CommonJS 闭环:在 01/ 下新建 utils.js 导出 baseURL 和 getArraySum,在 index.js 中 require 引入,打印 getArraySum([5, 1, 2, 3]) 的结果。验收:终端输出 11,且 index.js 中直接写 baseURL 会报未定义。

  2. 报错与修复:把上一题的 require 改成 import,先运行一次记录报错原文,再通过新建 package.json 修复到能跑通。验收:能说出"报错发生在哪一行、修复改动了哪个文件"。

  3. ESM 两种导出各写一遍:在 02/ 用 export default 导出工具对象、import obj from './utils.js' 使用;在 03/ 改用 export 修饰单个常量与函数,导入时只引入 getArraySum,不引入 baseURL。验收:03/index.js 中访问 baseURL 会报错,说明按需加载生效。

  4. 改名导入:把 03/ 的导入改成 import { baseURL as url, getArraySum as sum } from './utils.js',验证两种名字都能正常工作。验收:改回原名或改用 as 都不报错,但写成不存在的名字必须报错。

  5. 找错改错:下面三段代码各有一处问题,指出并修复。

    js
    // A
    const { uname } = { username: 'itheima', password: '777777' }
    // B
    const total = [10, 20, 30].map(v => v + 1).reduce((s, v) => s += v)
    // C
    const obj = require('utils.js')

    验收:A 能取到 'itheima',B 结果为 63,C 能在 01/ 目录下正常引入同级文件。

原视频对照 ​

#视频时长
1模块化简介12:46
2ECMAScript标准-默认导出和导入08:03
3ECMAScript标准-命名导出和导入08:54

小结 ​

  1. 一个文件就是一个模块,模块内变量默认私有,跨文件使用必须显式导出 + 导入,没有"自动全局可见"这回事。
  2. 两套标准并存:CommonJS 是 Node 的默认,ESM 是官方标准;同一目录下的 .js 只能按 package.json 的 type 走一套,混写必然报错。
  3. 语法只需记四组配对:module.exports ↔ require、export default ↔ import 变量、export ↔ import { 同名 }、as 用于改名。
  4. 默认导出适合"整包拿走",命名导出适合"按需取一件";两者可在同一模块共存,但导入写法不能串。
  5. 导入路径规则只有一条分界线:内置模块和已安装的包写名字,自己写的文件写相对路径并带后缀。
  6. 解构取不到值时,第一反应应该是核对键名,而不是怀疑数据源;这是最高频的"假 bug"。
  7. map / reduce 都是返回新值而非改原值,reduce 养成显式传初始值 0 的习惯,能避开空数组和类型不一致的坑。
  8. 遇到 Cannot find module / Cannot use import statement / does not provide an export named 这三类报错,基本可以按"路径没写全 → 模块标准没声明 → 名字对不上"的顺序依次排查。