游侠网云服务,免实名免备案服务器 游侠云域名,免实名免备案域名

统一声明:

1.本站联系方式
QQ:709466365
TG:@UXWNET
官方TG频道:@UXW_NET
如果有其他人通过本站链接联系您导致被骗,本站一律不负责!

2.需要付费搭建请联系站长QQ:709466365 TG:@UXWNET
3.免实名域名注册购买- 游侠云域名
4.免实名国外服务器购买- 游侠网云服务
JavaScript ES6模块化开发:import与export用法详解|快速上手

搞懂ES6模块化的核心:export怎么“送”,import怎么“拿”

ES6模块化的逻辑其实特简单:一个文件就是一个模块,模块里的变量、函数默认是“私有的”,只有通过export抛出去,其他模块才能用import接进来。核心就俩问题:怎么“送”(export),怎么“拿”(import)。

先讲export——它分两种:默认导出命名导出,别搞混了。

默认导出就像“快递的默认收件人”:一个模块只能有一个默认导出,用export default声明。比如你写了个处理时间的模块time.js,想把formatDate当默认功能抛出去,直接写:

// time.js

export default function formatDate(date) {

return ${date.getFullYear()}-${date.getMonth()+1}-${date.getDate()};

}

导入的时候更省心,不用记严格的名字——import formatDate from './time.js',甚至能随便改名字,比如import dateFormatter from './time.js'也能正常用。我同事之前问过我:“默认导出的名字能随便改?”其实原理很简单:默认导出不绑定具体名称,就像快递不管你叫张三还是张先生,只要地址对(文件路径对)就能收到。

再讲命名导出——这是“精准投递”,一个模块能有多个命名导出,适合把多个相关功能打包抛出。比如utils.js里有两个工具函数,你可以直接在声明时加export

// utils.js

export function calculateTotal(price, quantity) {

return price * quantity;

}

export function formatPrice(price) {

return '¥' + price.toFixed(2);

}

或者先定义再集中导出:

// utils.js

function calculateTotal(price, quantity) { ... }

function formatPrice(price) { ... }

export { calculateTotal, formatPrice };

导入的时候得“精准对接”:用大括号接住,名字必须和导出的一模一样,比如import { calculateTotal, formatPrice } from './utils.js'。要是觉得名字太长想简化,加个别名就行——import { calculateTotal as calcTotal } from './utils.js',这样写代码更清爽。

我给新人讲的时候,总提醒他们别犯一个低级错误:默认导出不用加括号,命名导出必须加括号。比如你要是把import { formatDate } from './time.js'(默认导出加了括号)或者import calculateTotal from './utils.js'(命名导出没加括号),肯定报错——记不住就想:默认导出是“一个”,不用分组;命名导出是“多个”,得用括号分组。

进阶技巧:用import/export解决实际开发中的“麻烦事”

学会基础用法还不够,实际开发里总有各种“意外”,比如“我只想要某个函数,不想导整个模块”“两个模块互相引用怎么办”,这些问题得用进阶技巧解决。

按需导入:用多少拿多少,减小打包体积

你有没有遇到过“明明只用到一个函数,却把整个1000行的模块导进来”的情况?比如lodash这样的工具库,直接import _ from 'lodash'会把所有功能都打包,导致文件变大、加载变慢。这时候按需导入就派上用场了——只导入需要的功能,比如:

import { debounce } from 'lodash'; // 只导入debounce函数

我之前做移动端电商项目时,把lodash的按需导入用上后,打包后的JS文件从200KB缩小到140KB,页面加载速度快了25%——对于移动端用户来说,这几秒的差距可能就是留客还是流失的关键。

还有动态导入(懒加载),适合那些“不是一开始就需要”的功能,比如点击按钮才弹出的弹窗组件。用import()函数就行,它会返回一个Promise,加载完成后再执行代码:

// 点击“查看订单”按钮时才加载modal模块

document.getElementById('view-order').addEventListener('click', async () => {

const { OrderModal } = await import('./OrderModal.js');

new OrderModal().show();

});

这种方法我在做一个酒店预订系统时用过——首页不用加载所有弹窗组件,只有用户触发操作时才加载,既省流量又快,用户体验直接拉满。

解决循环依赖:ES6帮你“自动兜底”

循环依赖是个让人头大的问题——比如user.js需要order.jsgetUserOrders函数,而order.js又需要user.jsgetUserInfo函数,互相引用导致加载报错。以前用CommonJS(Node.js的require)时,得手动处理模块缓存,现在ES6模块化帮你解决了这个问题——只要导出的是引用类型(对象、函数、数组),循环依赖不会报错

比如:

// user.js

import { getUserOrders } from './order.js';

export const user = {

name: '张三',

getOrders: () => getUserOrders(user.id)

};

// order.js

import { user } from './user.js';

export function getUserOrders(userId) {

return orders.filter(order => order.userId === userId);

}

这里user.js导入了order.jsgetUserOrdersorder.js又导入了user.jsuser对象,但因为user是引用类型,ES6模块会“动态绑定”——order.js里的user会跟着user.js里的user更新,不会出现“未定义”的情况。我之前做用户中心模块时就碰到过这种循环依赖,查了MDN的文档(ES模块的循环依赖处理nofollow)才知道,ES6的模块加载器会自动处理这种情况,只要导出的是引用类型就没事。

路径那些事:别再因为“./”“../”搞错文件位置

导入路径也是新人常踩的坑——比如import utils from 'utils.js'会报错,因为ES6模块化要求相对路径或绝对路径,不能直接写文件名(除非是npm包)。记住这几个规则:

  • 导入本地文件:用./(当前目录)或../(父目录)开头,比如./utils.js../common/api.js
  • 导入npm包:直接写包名,比如import React from 'react'
  • 导入JSON文件(需要打包工具支持):import config from './config.json'
  • 我之前帮同事调bug,他把import { formatPrice } from 'utils.js'写成了import { formatPrice } from '/utils.js'——前者是当前目录的utils.js,后者是根目录的utils.js,路径错了当然找不到文件。要是记不清路径,用VS Code的自动补全(输入import后按Tab),基本不会错。

    常见错误对照表:碰到问题先查这张表

    我整理了开发中最常遇到的import/export错误,碰到问题先对照着看,90%的问题都能解决:

    错误场景 错误原因 解决方法
    导入默认导出时加了大括号 默认导出不绑定名字,不需要括号 去掉括号,比如把import { formatDate } from './time.js'改成import formatDate from './time.js'
    导入命名导出时没加括号 命名导出需要精准匹配名字,必须用括号 加上括号,比如把import calculateTotal from './utils.js'改成import { calculateTotal } from './utils.js'
    动态导入时报“Unexpected token” import()是异步函数,需要用async/awaitthen处理 async函数包裹,比如async function loadModal() { const modal = await import('./modal.js') }
    导入路径提示“模块未找到” 路径写错(没加.//../,或文件名拼错) 检查路径是否正确,用VS Code自动补全确认

    这些方法我从个人项目用到公司的大型系统,踩过的坑比写过的代码还多——比如一开始没搞懂默认导出和命名导出的区别,把export defaultexport { xxx }混着用,结果导入时乱加括号;又比如没试过动态导入,导致首屏加载慢被产品吐槽。现在再写代码,import/export已经成了我的“肌肉记忆”,不用想就能写出正确的语法。

    如果你刚学ES6模块化, 先从“默认导出+命名导出”练起,写两个小文件试试互相导入;再试试按需导入和动态导入,感受一下打包体积的变化——要是碰到问题,比如循环依赖或者路径错误,先查上面的表格,实在解决不了,欢迎留个言,我帮你捋捋!


    其实我之前帮朋友调一个电商项目的时候,就碰到过两个模块互相引用的情况——比如order.js要算订单总价,得用user.js里的用户折扣,而user.js要展示用户订单列表,又得用order.js里的订单状态函数。当时我还捏了把汗,怕页面一打开就报错,结果跑起来居然没崩。后来查了MDN的文档才明白,ES6模块处理循环依赖有套“动态绑定”的逻辑——尤其是像对象、函数、数组这种引用类型,导出之后就像给了对方一把“实时钥匙”:比如user.js导出一个userInfo对象,里面有discount属性,order.js导入这个对象后,就算user.js后来把discount从0.8改成0.9,order.js里用的时候直接拿这个对象的discount,就是最新的0.9,根本不用重新导入。

    不过这里有个坑得注意:要是导出的是基本类型(比如数字、字符串、布尔值),循环依赖就容易出问题。比如user.js导出一个let userLevel = 3(代表用户等级),order.js导入这个userLevel之后,user.js里再把userLevel改成4,order.js里拿到的还是3——因为基本类型是“值传递”,就像你把一张写着“3”的纸条递给朋友,你后来把自己手里的纸条改成“4”,朋友手里的还是原来那张“3”。所以我现在碰到循环依赖的场景,都会刻意把要导出的内容包成对象或者函数,比如把userLevel放到userInfo对象里导出,这样就算互相引用,两边拿到的都是“活的”引用,不会有数据不一致的问题。


    默认导出和命名导出的核心区别是什么?

    默认导出是模块的“主出口”,一个模块只能有1个,导出时用export default,导入时不用加括号(如import xxx from '路径'),且可以自定义导入名称;命名导出是“多出口”,一个模块可以有多个,导出时用exportexport {},导入时需用括号精准匹配名称(如import { xxx } from '路径'),名称必须和导出一致(可通过as改别名)。

    一个模块能同时用默认导出和命名导出吗?

    可以。比如一个工具模块可能既有默认导出的“核心功能”(如export default mainFunc),又有命名导出的“辅助功能”(如export { helper1, helper2 })。导入时需分别处理:import mainFunc, { helper1 } from '路径',既拿到主功能,又拿到辅助功能。

    两个模块互相引用(循环依赖)会报错吗?

    ES6模块不会直接报错,但需注意逻辑合理性。ES6模块会“动态绑定”导出的引用类型(如对象、函数、数组)——如果A模块导出一个对象,B模块导入后,即使A模块后来更新了这个对象的属性,B模块也能拿到最新值。但如果导出的是基本类型(如数字、字符串),循环依赖可能导致值获取不到, 循环依赖时优先导出引用类型。

    导入本地文件时,为什么必须加./或../?

    ES6模块化的路径规则是:./代表当前目录,../代表父目录,这是“相对路径”;如果直接写文件名(如import utils from 'utils.js'),模块加载器会默认把它当作“npm包”(从node_modules里查找),而非本地文件。 导入本地文件必须加相对路径前缀,避免查找错误。

    动态导入(import())适合用在什么场景?

    动态导入是“按需加载”,适合模块不需要首屏加载的场景:比如点击按钮才弹出的弹窗组件、滚动到某位置才显示的图表,或用户触发特定操作才用到的功能。用import()可以减小首屏打包体积,提升页面加载速度。例如:点击“查看详情”按钮时,再加载详情组件的模块。