所有分类
  • 所有分类
  • 游戏源码
  • 网站源码
  • 单机游戏
  • 游戏素材
  • 搭建教程
  • 精品工具

前端JavaScript模块化解析|ESModule超详细用法示例|实战新手指南

前端JavaScript模块化解析|ESModule超详细用法示例|实战新手指南 一

文章目录CloseOpen

先搞懂ESModule的“底层逻辑”:为什么它能解决模块化问题?

在ESModule出现之前,前端开发者解决模块化的办法要么是用script标签串行加载(容易变量冲突),要么是用CommonJS(Node.js的模块化方案,但浏览器不原生支持)。直到ES6推出ESModule,才终于有了浏览器和Node.js都能兼容的原生模块化标准

我先给你打个比方:你可以把ESModule理解为“快递系统”——每个JS文件是一个“快递盒”,盒子里装着你写的功能(比如工具函数、组件)。要让别人能用这个盒子里的东西,你得给盒子“贴标签”:比如贴“formatTime函数”“PI常量”(这叫导出);别人要用的时候,得按标签“取快递”(这叫导入)。这样一来,就算你改了盒子里的内容,只要标签没变,别人用的时候就不会乱——这就是ESModule解决模块化的核心逻辑。

那ESModule和CommonJS有什么区别?别被专业术语吓到,我用大白话讲:CommonJS是“快递到了再拆箱”(运行时加载),比如require('./utils')是在代码运行的时候才去读utils.js的内容;而ESModule是“寄快递前先填清单”(编译时静态分析),比如import { formatTime } from './utils.js'是在代码编译的时候就知道要取什么——这意味着浏览器可以提前加载需要的模块,速度更快,而且能做“按需导入”(只取你需要的功能)。

我之前踩过一个典型的坑:写了个utils.js,里面用export导出了formatTime函数,结果在另一个文件里写成import formatTime from './utils'——直接报错“没有默认导出”。后来才明白,ESModule的导出分两种:具名导出(named export)和默认导出(default export)。具名导出就像“贴多个小标签”,每个标签对应一个功能,导入的时候得用大括号“按标签取”;默认导出是“贴一个主标签”,导入的时候不用大括号,直接取主功能。比如:

  • 具名导出:export function formatTime() {} → 导入:import { formatTime } from './utils.js'
  • 默认导出:export default function formatTime() {} → 导入:import formatTime from './utils.js'
  • 这一步搞懂了,80%的ESModule报错都能解决——我后来帮同事调试过类似的问题,十次有八次都是导出导入的方式弄反了。

    ESModule实战:从“hello world”到复杂项目的3个关键技巧

    光懂逻辑不够,得落地到实战。我把自己常用的技巧拆成3个部分,每个部分都带真实项目的例子,你跟着做就能上手。

  • 最常用的两种导出:具名vs默认,别再搞混了
  • 先明确一个 具名导出适合“多功能模块”,默认导出适合“单功能模块”。比如:

  • 如果你写了个工具函数文件utils.js,里面有formatTime(格式化时间)、validateEmail(验证邮箱)、trimStr(去除字符串空格)三个功能,就用具名导出——每个功能贴一个标签,这样导入的时候可以按需选,不用把整个文件都引进来:
  •  // utils.js(具名导出)
    

    export function formatTime(date) {

    return new Intl.DateTimeFormat('zh-CN', { hour: '2-digit', minute: '2-digit' }).format(date);

    }

    export function validateEmail(email) {

    return /^[w-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9]+$/.test(email);

    }

    export const PI = 3.14159;

    导入的时候,你可以只取需要的功能:

    javascript

    // index.js

    import { formatTime, validateEmail } from './utils.js';

    console.log(formatTime(new Date())); // 输出“14:30”

    console.log(validateEmail('test@example.com')); // 输出true

  • 如果你写了个Vue组件
  • MyButton.vue,核心功能就是一个按钮组件,就用默认导出——贴一个主标签,导入的时候不用记复杂的函数名,直接用组件名:

    vue

    <!-

  • MyButton.vue(默认导出) >
  • export default {

    name: 'MyButton',

    props: ['text']

    }

    导入的时候超简单:

    vue

    <!-

  • Home.vue >
  • import MyButton from './MyButton.vue'; // 不用大括号,直接取默认导出

    export default {

    components: { MyButton }

    }

    我之前做电商项目时,把所有接口请求放在

    api.js里,用具名导出每个接口函数(比如getGoodsListgetGoodsDetail),这样在商品列表页只需要导入getGoodsList,在商品详情页只导入getGoodsDetail——既节省了加载时间(不用加载不需要的接口函数),代码也更清晰(一眼就知道这个页面用了哪个接口)。

    为了让你更清楚,我做了个对比表格:

    特点 具名导出 默认导出
    导出方式 用export+功能名(如export function fn() 用export default+功能(如export default fn
    导入方式 需用大括号(如import { fn } from './file.js' 不用大括号(如import fn from './file.js'
    适用场景 多功能模块(如工具函数、接口请求) 单功能模块(如组件、类)

  • 必踩的“细节坑”:后缀、路径、循环依赖怎么处理?
  • 我见过很多新手栽在“细节”上——明明逻辑对了,就是报错,其实就是没注意这些小问题:

    ① 为什么导入要写

    .js后缀?

    浏览器原生支持的ESModule必须写完整的文件路径和后缀。比如你写

    import from './utils',浏览器会找不到文件;得写成import from './utils.js'才行。但为什么用Vite或Webpack的项目不用写?因为脚手架帮你“自动补全后缀”了——但我 你不管用不用脚手架,都养成写后缀的习惯,这样代码在原生环境下也能运行,更规范。

    我之前用

    file://协议打开本地文件测试时,没写.js后缀,直接报“模块未找到”——后来改成./utils.js才解决,这个坑我记到现在。

    ② 路径怎么写才不会错?

    ESModule的路径规则和

    script标签的src一样:
  • ./:当前目录(比如./utils.js就是和当前文件同目录的utils.js)
  • ../:上一级目录(比如../components/MyButton.vue就是当前目录的父目录下的components文件夹里的组件)
  • /:根目录(比如/src/utils.js就是项目根目录下的src文件夹里的utils.js)

    我之前做一个多页面项目,把

    index.js放在pages/home目录下,要导入src/utils.js,一开始写成./src/utils.js——报错,后来改成/src/utils.js才对。记住:路径的起点是“当前文件的位置”,别搞反了层级。

    ③ 循环依赖怎么办?

    循环依赖就是“A导入B,B又导入A”,比如:

  • a.jsimport { b } from './b.js'
  • b.jsimport { a } from './a.js'

    我之前做表单组件时就遇到过:

    Form.js导入了Input.jsInput.js又导入了Form.js里的验证函数,结果页面直接崩了。后来查MDN文档才知道,ESModule会静态处理循环依赖——但得确保导出的内容是“静态的”(比如变量、函数声明,不是运行时生成的内容)。解决办法也简单:把循环依赖的部分拆成独立模块。比如我把验证函数从Form.js里拆出来,放到utils/validate.js里,这样Form.jsInput.js都导入validate.js,就不会循环了。

  • 结合框架的实战:Vue/React里的ESModule怎么用?
  • 现在前端项目基本都用框架,ESModule早就和框架深度融合了——你每天写的Vue组件、React组件,其实都是ESModule的默认导出。

    比如Vue单文件组件(SFC):

    vue

    <!-

  • MyComponent.vue >
  • // 这就是ESModule的默认导出

    export default {

    name: ‘MyComponent’,

    data() {

    return { message: ‘Hello ESModule!’ }

    }

    }

    导入的时候直接用默认导出:

    vue

    <!-

  • App.vue >
  • import MyComponent from ‘./MyComponent.vue’; // 不用大括号

    export default {

    components: { MyComponent }

    }

    再比如React组件:

    jsx

    // App.jsx

    // 具名导出一个函数组件

    export function App() {

    return

    Hello React!

    ;

    }

    // 或者默认导出

    // export default function App() { … }

    导入的时候:

    jsx

    // index.jsx

    import { App } from ‘./App.jsx’; // 具名导出用大括号

    // 或者 import App from ‘./App.jsx’; // 默认导出不用大括号

    ReactDOM.render(, document.getElementById(‘root’));

    我用Vue做过一个博客项目,把所有组件放在src/components目录下,每个组件都是默认导出——这样在页面里导入的时候,代码非常简洁,一眼就能看出用了哪个组件。
    

    用ESModule重构旧代码:从“一团乱”到“模块化”的实操步骤

    如果你手里有个旧项目,代码都堆在一个

    app.js里,怎么用ESModule拆分?我去年帮朋友重构过他的个人博客前端——原来的app.js有2000多行,混着ajax请求、DOM操作、工具函数,改个功能得翻半小时。我分了三步帮他拆:

    第一步:拆“通用工具”到

    utils.js

    把所有通用的、不依赖业务的函数拆出来,比如

    formatDate(格式化日期)、stripHtml(去除HTML标签)、debounce(防抖函数)——这些函数在哪里都能用,用具名导出

    javascript

    // utils.js

    export function formatDate(date) {

    return new Intl.DateTimeFormat(‘zh-CN’, { year: ‘numeric’, month: ‘2-digit’, day: ‘2-digit’ }).format(date);

    }

    export function stripHtml(html) {

    return html.replace(/]+>/g, ”);

    }

    ### 第二步:拆“接口请求”到api.js
    

    把所有和后端交互的ajax请求拆出来,比如

    getArticles(获取文章列表)、getComments(获取评论)——这些是业务相关的通用功能,用具名导出

    javascript

    // api.js

    export async function getArticles() {

    const res = await fetch(‘/api/articles’);

    return res.json();

    }

    export async function getComments(articleId) {

    const res = await fetch(/api/articles/${articleId}/comments);

    return res.json();

    }

    ### 第三步:拆“页面逻辑”到page.js
    

    剩下的页面逻辑(比如渲染文章列表、绑定点击事件),导入

    utilsapi里的函数来用:

    javascript

    // page.js

    import { formatDate, stripHtml } from ‘./utils.js’;

    import { getArticles } from ‘./api.js’;

    // 渲染文章列表

    async function renderArticles() {

    const articles = await getArticles();

    const articleList = document.getElementById(‘article-list’);

    articles.forEach(article => {

    const li = document.createElement(‘li’);

    li.innerHTML =

    ${article.title}

    ${stripHtml(article.content).slice(0, 100)}…

    ${formatDate(new Date(article.createdAt))}

    ;

    articleList.appendChild(li);

    });

    }

    // 初始化页面

    renderArticles();

    拆分之后,朋友的代码量其实没减少,但可维护性提升了10倍——现在他想改日期格式,直接找

    utils.js里的formatDate;想改接口地址,直接找api.js里的getArticles;想改页面渲染逻辑,直接找page.js——再也不用翻2000行代码了。

    你是不是已经跃跃欲试了?不如今天就找个小项目试试:比如把你之前写的一个JS文件拆成两个ESModule文件,导出一个函数再导入——如果遇到报错,先检查这三点:后缀写了吗?导出导入方式对吗?路径对吗?要是试了之后有效果,或者遇到了新问题,欢迎回来留个言,我帮你一起看看!


    我之前帮一个刚转前端的朋友调代码,他拿着电脑问我:“为什么Node.js里用require引模块没问题,到浏览器里就报错‘模块未找到’?”其实核心就一点——ESModule和CommonJS的“加载逻辑”根本不是一回事儿。

    你想啊,ESModule就像你寄快递前先填好“收件清单”:比如你要寄个手机壳,清单上写清楚“手机壳×1”,快递员一看就知道要寄什么,对方收到直接按清单取;可CommonJS是啥?是你把手机壳、充电器、数据线一股脑塞进箱子,没写清单,对方收到箱子得自己拆开翻——这差别大了去了。具体到代码里,ESModule的import语句是“编译时静态分析”,比如你写import { formatTime } from './utils.js',浏览器在编译代码的时候就“提前知道”:哦,这个文件要用到utils里的formatTime函数,直接去加载这个函数就行,不用把整个utils.js都读一遍;可CommonJS的require是“运行时加载”,比如require('./utils'),得等代码运行到这一行,才会去读utils.js的全部内容,再从中找你要的formatTime——这就像你去超市买瓶水,ESModule是直接跟收银员说“拿瓶矿泉水”,收银员直接递过来;CommonJS是你说“拿个饮料”,收银员把整箱饮料搬过来让你自己挑,能不慢吗?

    我之前还踩过个坑:用CommonJS写Node.js脚本,require了一个很大的工具库,结果运行的时候卡了两秒——后来换成ESModule,只导入我需要的那个“时间格式化”函数,直接快了一倍不止。还有浏览器里的“tree shaking”(按需摇掉没用的代码),也是靠ESModule的静态分析才能实现:比如你导入了一个有10个函数的库,但只用到其中一个,打包工具会自动把另外9个没用的函数删掉,文件瞬间变小一半,加载速度能不快吗?

    再说浏览器的支持——ESModule是浏览器原生就认的,你直接写script type="module"就能用,不用装什么Babel转译;可CommonJS呢?浏览器根本不认识require,还得用Webpack转一遍才能跑。我那朋友后来把代码改成ESModule,加了个.js后缀,浏览器立马就不报错了——你看,这就是逻辑不一样带来的差别。

    还有个细节你可能没注意:ESModule的导入路径必须写全后缀(比如./utils.js),CommonJS可以省略——为啥?因为ESModule是编译时找文件,得精确到“哪个文件”;CommonJS是运行时找,能自动补全。我之前帮朋友调的时候,他把路径写成./utils没加.js,浏览器直接报“模块未找到”,改成./utils.js就好了——这也是静态分析的“严格性”带来的,虽然麻烦点,但胜在准确啊。

    不用 你就记着:前端项目优先用ESModule,因为快、省、浏览器原生支持;Node.js里CommonJS还能用,但现在Node.js也支持ESModule了(加个”type”: “module”在package.json里)——反正不管啥场景,ESModule都是 的趋势,早学早省心。


    ESModule和CommonJS的核心区别是什么?

    ESModule是“编译时静态分析”(寄快递前先填清单),导入语句在代码编译时就明确要取的功能,支持按需导入,速度更快;CommonJS是“运行时加载”(快递到了再拆箱),require语句在代码运行时才读取模块内容,无法提前优化。简单说,ESModule更适合浏览器环境的前端项目,CommonJS更常用在Node.js后端。

    为什么导入ESModule时有时候要用大括号,有时候不用?

    取决于模块的导出方式:如果是“具名导出”(用export直接导出函数/变量,如export function formatTime()),导入时需要用大括号按名称取(import { formatTime } from ‘./utils.js’);如果是“默认导出”(用export default导出,如export default function formatTime()),导入时不用大括号(import formatTime from ‘./utils.js’)。

    ESModule导入时必须写.js后缀吗?

    原生浏览器环境(如用file://协议打开本地文件)必须写完整后缀(如./utils.js),否则浏览器找不到模块;但用Vite、Webpack等脚手架时,脚手架会自动补全后缀,所以可以省略。 养成写后缀的习惯,让代码在原生环境也能运行,更规范。

    ESModule遇到循环依赖(A导入B,B又导入A)怎么办?

    解决核心是“拆分独立模块”:把循环依赖的功能(比如A和B都需要的验证函数)拆到单独的utils文件(如utils/validate.js),让A和B都导入这个utils模块,而非直接互相导入。这样既避免了循环,又保持功能复用。

    Vue/React组件里的ESModule是怎么用的?

    框架中的组件本质是ESModule的“默认导出”:比如Vue单文件组件(.vue)用export default导出组件选项对象,导入时直接写import MyComponent from ‘./MyComponent.vue’;React函数组件常用export default导出,导入时同样不用大括号。框架通过ESModule实现了组件的模块化复用,这也是前端项目的基础用法。

    原文链接:https://www.mayiym.com/47113.html,转载请注明出处。
    0
    显示验证码
    没有账号?注册  忘记密码?

    社交账号快速登录

    微信扫一扫关注
    如已关注,请回复“登录”二字获取验证码