前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
首页程序员面试题库ESM 副作用导入 import 模块
JavaScript模块与语言机制

只写 import './polyfill.js' 不接收任何导出,有什么用?会执行吗?

会执行。import "./polyfill.js" 不引入任何变量,但模块的顶层代码会完整跑一遍,而且在当前模块的代码之前执行。它的用途就是「只要副作用」:打补丁、注册插件、引入全局样式。

前端进阶之旅 · 一题精讲更新于 2026.09.29
JavaScript#模块与语言机制
先看核心答案读代码示例
理解线索

副作用导入的三个特点

  1. 只执行不创建本地绑定,要的只是顶层代码的执行结果
  2. 先执行所有导入都在当前模块代码之前完成,顺序按书写顺序
  3. 执行一次多个文件都写了同一个副作用导入,也只跑一次

打包器做 tree shaking 时,会根据包的 sideEffects 字段判断能不能删掉「没用到导出」的导入。副作用导入本身就不用任何导出,包如果声明了 "sideEffects": false,这行导入可能被整个删掉,样式和补丁都会悄悄消失。

核心回答

先记住这个答案

会执行。import "./polyfill.js" 这种写法叫副作用导入,它不引入任何变量,但会让这个模块被加载并完整执行一次顶层代码。执行时机和普通导入一样:在当前模块的代码之前,按导入语句的书写顺序执行。所以它适合用在只需要「执行一下」的场景,比如给老浏览器打补丁、注册插件、引入全局样式。和其它导入一样,同一个模块被副作用导入多少次都只执行一次。

  • 副作用导入会完整执行模块顶层代码,但不引入任何名字
  • 在当前模块代码之前、按导入顺序执行
  • 和其它导入一样,同一个模块只执行一次
  • 错设 sideEffects: false 会误删代码

执行时机:永远在当前模块代码之前

main.js 里先写了一行日志,再写副作用导入。

导入写在后面也会先执行JavaScript
// setup.js
console.log('setup 执行了')
globalThis.appReady = true

// main.js
console.log('main 开始', globalThis.appReady)
import './setup.js'
console.log('main 结束', globalThis.appReady)

// 输出:
// setup 执行了
// main 开始 true
// main 结束 true

虽然导入语句写在第二行,setup.js 还是在 main.js 的第一行代码之前就执行完了。这是因为 import 声明在模块开始执行前就已经处理好:先加载、执行所有依赖,再执行当前模块。所以「写在哪一行」不影响时机,影响的只是多个导入之间的先后顺序。 这对 polyfill 很重要:import "./polyfill.js" 要写在其它依赖前面。如果它排在某个依赖后面,那个依赖的顶层代码执行时补丁还没打上。

常见用途和打包时的坑

项目里几乎每天都在写副作用导入,只是很少有人注意到它是一类单独的写法。

典型场景和 sideEffects 字段
打补丁:import "core-js/stable" 修改全局对象,补齐老环境缺的 API。注册:import "dayjs/locale/zh-cn" 把中文语言包注册进 dayjs,自定义元素的定义文件也是导入即注册。样式:Vite、Webpack 里 import "./global.css",打包器把 CSS 插进页面。 打包器的 tree shaking 会参考包 package.json 里的 sideEffects 字段。写成 false 表示「这个包的所有文件都没有副作用,没用到导出就可以删」。副作用导入恰好一个导出都没用,于是整行被删。组件库最容易踩这个坑:声明了 "sideEffects": false,用户项目里 import "lib/style.css" 打包后样式全没了。正确做法是把有副作用的文件列出来,写成 "sideEffects": ["*.css", "./src/polyfill.js"]。
回答前,多想一步

容易答错的地方

以为不用导出就不会执行
执行模块和使用导出是两件事。只要模块被导入,无论用什么形式,它的顶层代码都会执行。不想执行就只能不导入,或者改用动态 import() 按需加载。
以为多写几次就会执行多次
模块按 URL 缓存,十个文件都写 import "./setup.js",它也只执行一次。想要每次都执行的逻辑,应该导出一个函数让调用方去调用,而不是放在模块顶层。
以为它能用在任意位置
副作用导入也是 import 声明,只能写在模块顶层,不能放进 if 里。需要按条件加载时用 if (needPolyfill) await import("./polyfill.js")。
试着用自己的话回答

面试官还会怎么问?

CommonJS 里怎么写副作用导入?

直接 require("./setup"),不接收返回值就行,效果一样:执行一次并缓存。区别在时机,require 在代码执行到那一行时才加载,写在哪一行就在哪一行执行。

副作用导入和 import * as x 在执行上有区别吗?

没有,都会让模块执行一次。区别只是后者额外创建了一个命名空间对象的绑定。打包器看到 import * as x 但 x 没被用到时,同样要参考 sideEffects 决定能不能删。

怎么判断一个模块有没有副作用?

看它的顶层代码有没有改动模块外部的东西:修改全局对象或原型、操作 DOM、注册事件、发请求、往别的模块的对象里写数据。只有声明函数、常量、类并导出的模块,才算没有副作用。

从一道题,走向一组知识

把知识连起来

模块与语言机制

ESM 的 import 声明会被提升吗?对代码执行顺序有什么影响?

副作用导入写在文件中间也会先于当前模块代码执行,这正是 import 提升的效果。

模块与语言机制

import { a as b } 重命名以后,还能读到原模块的最新值吗?

普通导入会建立实时绑定,副作用导入则一个绑定都不建,只保留了「执行模块」这一半。

放回知识体系中复习JavaScript 进阶知识

参考资料

  • import

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 执行时机:永远在当前模块代码之前
  3. 常见用途和打包时的坑
  4. 容易答错的地方
  5. 面试官还会怎么问
  6. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑