- Published on
如何在一个JavaScript文件中引入另一个JavaScript文件
- Authors
- Name
旧版本的JavaScript没有import、include或require,因此开发者们采用了许多不同的方法来解决这个问题。但自从2015年(ES6)以来,JavaScript已经有了ES6模块标准,可以在Node.js中导入模块,并且大多数现代浏览器也支持这一标准。
为了兼容老版本浏览器,可以使用诸如Webpack和Rollup这样构建工具,以及Babel等转译工具。
ES6 模块
自Node.js v8.5(需要--experimental-modules
标志)以来,Node.js支持ECMAScript(ES6)模块。从Node.js v13.8.0起,无需任何标志即可使用ES6模块。要启用"ESM"(与Node.js之前的CommonJS模块系统"CJS"相比),你可以在package.json
中使用"type": "module"
,或者将文件拓展名改为.mjs
。同样,使用Node.js之前的CJS模块可以命名为.cjs
,如果你的默认模块类型是ESM。
package.json
:
使用{
"type": "module"
}
然后在module.js
中:
export function hello() {
return 'Hello'
}
最后在main.js
中:
import { hello } from './module.js'
let val = hello() // val是"Hello"
使用.mjs
文件:
export function hello() {
return 'Hello'
}
然后在main.mjs
中:
import { hello } from './module.mjs'
let val = hello() // val是"Hello"
浏览器中的ECMAScript模块
浏览器从Safari 10.1、Chrome 61、Firefox 60和Edge 16开始支持直接加载ECMAScript模块(无需Webpack等工具)。检查当前支持情况可以访问caniuse。这里不需要使用Node.js的.mjs
扩展名;浏览器会完全忽略模块/脚本的文件扩展名。
<script type="module">
import { hello } from './hello.mjs' // 或者扩展名可以是`.js`
hello('world')
</script>
// hello.mjs 或者扩展名可以是`.js`
export function hello(text) {
const div = document.createElement('div')
div.textContent = `Hello ${text}`
document.body.appendChild(div)
}
更多内容请参见:https://jakearchibald.com/2017/es-modules-in-browsers/
浏览器中的动态导入
动态导入允许脚本根据需要加载其他脚本:
<script type="module">
import('hello.mjs').then((module) => {
module.hello('world')
})
</script>
更多内容请参见:https://developers.google.com/web/updates/2017/11/dynamic-import
Node.js的require
旧的CJS模块风格仍然在Node.js中被广泛使用,即module.exports
/require
系统。
// mymodule.js
module.exports = {
hello: function () {
return 'Hello'
},
}
// server.js
const myModule = require('./mymodule')
let val = myModule.hello() // val是"Hello"
在浏览器中加载外部JavaScript的其他方式
AJAX加载
可以使用AJAX调用加载附加脚本,然后使用eval
运行它。这是最简单的方式,但由于JavaScript沙盒安全模型的限制,它仅限于当前域。使用eval
也会带来bug、黑客和安全问题。
Fetch加载
类似于动态导入,可以使用fetch
调用并使用Promise控制脚本依赖的执行顺序。可以使用Fetch Inject库:
fetchInject(['https://cdn.jsdelivr.net/momentjs/2.17.1/moment.min.js']).then(() => {
console.log(`Finish in less than ${moment().endOf('year').fromNow(true)}`)
})
jQuery加载
jQuery库提供了一行代码的加载功能:jQuery.getScript:
$.getScript('my_lovely_script.js', function () {
alert('Script loaded but not necessarily executed.')
})
动态脚本加载
可以将带有脚本URL的script
标签添加到HTML中。为了避免jQuery的开销,这是一种理想的解决方案。脚本甚至可以驻留在不同的服务器上。此外,浏览器将评估代码。<script>
标签可以注入网页的<head>
部分,或插入在关闭的</body>
标签之前。
样例如下:
function dynamicallyLoadScript(url) {
var script = document.createElement('script') // 创建一个script DOM节点
script.src = url // 将其src设置为提供的URL
document.head.appendChild(script) // 将它添加到页面head部分的末尾(可以将'head'改为'body'以将其添加到body部分的末尾)
}
此函数将一个新的<script>
标签添加到页面head
部分的末尾,其中src
属性设置为传递给函数的URL。这种和其他方法在JavaScript Madness: Dynamic Script Loading中有详细讨论和示例。
检测脚本执行完成
现代浏览器会异步加载文件以提升性能(这适用于jQuery方法和手动动态脚本加载方法)。因此,如果直接使用这些方法,你无法在请求加载脚本后的下一行使用新加载的代码,因为其可能仍在加载中。
例如:my_lovely_script.js
包含MySuperObject
:
var js = document.createElement("script");
js.type = "text/javascript";
js.src = jsFilePath;
document.body.appendChild(js);
var s = new MySuperObject();
Error : MySuperObject is undefined
然后刷新页面(F5),它却工作了!令人困惑...
所以该怎么解决这个问题呢?
你可以使用作者在上面提供的链接中提出的解决方案。简而言之,他使用事件在脚本加载时运行回调函数。将所有使用远程库的代码放在回调函数中。例如:
function loadScript(url, callback) {
var head = document.head
var script = document.createElement('script')
script.type = 'text/javascript'
script.src = url
script.onreadystatechange = callback
script.onload = callback
head.appendChild(script)
}
然后你可以在一个匿名函数中编写你希望在脚本加载后使用的代码:
var myPrettyCode = function () {
// 在这里做任何事
}
然后运行这些代码:
loadScript('my_lovely_script.js', myPrettyCode)
需要注意的是,脚本可能在DOM加载后执行,也可能在此之前,具体取决于浏览器以及你是否包含了script.async = false;
这一行代码。关于JavaScript加载的更多内容,可以参见这篇绝佳的文章,它全面讨论了JavaScript的加载问题。
代码合并/预处理
如本文开头所述,许多开发者在其项目中使用Parcel、Webpack或Babel等构建/转译工具,这使他们能够使用即将发布的JavaScript语法,提供对旧浏览器的兼容性,合并文件,压缩代码,执行代码分割等。