|
浏览器里跑JavaScript,引用一个文件不够用的引用时候,就得想办法把另一个.js拉进来。引用最原始的引用做法是在HTML里写两个<script>标签,顺序排好,引用后面的引用文件就能用前面文件里定义的变量和函数。比如a.js里有个let count = 0;,引用b.js里直接写count++,引用只要a.js先加载,引用这就能跑。引用但问题也明显:全局变量满天飞,引用命名冲突迟早找上门,引用而且加载顺序一乱就报错,引用维护起来头疼。引用 后来大家用document.createElement('script')动态插标签。引用写个函数,传URL进去,创建script元素,设src,挂到head或body上。异步加载,不阻塞页面渲染。但异步意味着你不知道它什么时候加载完。得监听onload事件,或者用回调。我见过一个老项目,用这种方式加载地图API,结果回调里又去调另一个还没加载完的工具函数,页面直接白屏。排查半天,就是加载时序没控好。 CommonJS规范出来之后,Node.js里用require,同步加载,写起来舒服。但浏览器不认require,得靠打包工具比如Webpack、Rollup把文件合并或转译。打包工具的好处是能处理依赖图,你写import,它帮你分析谁依赖谁,最后生成一个或多个bundle。坏处是配置复杂,热更新有时候抽风,构建速度随着项目变大越来越慢。我自己的小项目现在更倾向用原生ES模块,浏览器支持已经很好。 ES模块的写法很直接:a.js里写export function add(a, b) { return a + b; },b.js里写import { add } from './a.js';。注意路径要带./或../,不能省略扩展名(浏览器环境)。然后在HTML里用<script type="module" src="b.js"></script>。浏览器会自动处理依赖,按需加载。模块作用域是独立的,不会污染全局。import是静态的,意味着不能在if语句里动态导入——除非用import()函数,返回Promise,适合懒加载。 有个细节容易踩坑:ES模块默认是延迟执行的,相当于defer。多个模块之间的执行顺序由依赖关系决定,不是按script标签在HTML里的顺序。如果你在a.js里console.log('a'),b.js里console.log('b'),而b.js导入了a.js,那么先输出a再输出b。这跟传统脚本的直觉不一样,刚开始用会愣一下。 还有跨域问题。ES模块加载遵循CORS,本地直接双击HTML文件打开,file://协议下模块加载会被浏览器拦住,报跨域错误。必须起个本地服务器,比如npx serve或者VSCode的Live Server插件。这个坑我见新手踩过无数次,明明代码没问题,就是白屏,控制台一条红色CORS错误。 实际项目里,如果不想上打包工具,又需要模块化,可以用importmap。在HTML里写<script type="importmap">{ "imports": { "lodash": "https://cdn.jsdelivr.net/npm/lodash-es/lodash.js" } }</script>,然后import _ from 'lodash'就能直接解析到CDN地址。适合原型开发或者小工具页面,省去npm install和构建步骤。但生产环境还是建议打包,减少请求数,做tree-shaking。 选择哪种方式,看场景。老项目维护,可能还在用全局脚本或动态插入;新项目,ES模块是默认答案;需要兼容旧浏览器或者用npm生态,打包工具跑不掉。没有银弹,知道每种方案的代价就行。 |