作者:Tania Rascia
翻译:疯狂的技术宅
原文:https://www.taniarascia.com/j…
在互联网的洪荒时代,网站次要用 HTML 和 CSS 开发的。如果将 JavaScript 加载到页面中,通常是以小片段的模式提供成果和交互,个别会把所有的 JavaScript 代码全都写在一个文件中,并加载到一个 script
标签中。只管能够把 JavaScript 拆分为多个文件,然而所有的变量和函数依然会被增加到全局作用域中。
然而起初 JavaScript 在浏览器中施展着重要的作用,迫切需要应用第三方代码来实现常见工作,并且须要把代码合成为模块化的文件,防止净化全局命名空间。
ECMAScript 2015 标准在 JavaScript 语言中引入了 module,也有了 import 和 export 语句。在本文中,咱们一起来学习 JavaScript 模块,以及怎么用 import
和 export
来组织代码。
模块化编程
在 JavaScript 中呈现模块的概念之前,当咱们想要把本人的代码组织为多个块时,个别会创立多个文件,并且将它们链接为独自的脚本。上面先举例说明,首先创立一个 index.html
文件和两个 JavaScript 文件“functions.js
和 script.js
。
index.html
文件用来显示两个数字的和、差、乘积和商,并链接到 script
标签中的两个 JavaScript 文件。关上 index.html
并增加以下代码:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>JavaScript Modules</title>
</head>
<body>
<h1>Answers</h1>
<h2><strong id="x"></strong> and <strong id="y"></strong></h2>
<h3>Addition</h3>
<p id="addition"></p>
<h3>Subtraction</h3>
<p id="subtraction"></p>
<h3>Multiplication</h3>
<p id="multiplication"></p>
<h3>Division</h3>
<p id="division"></p>
<script src="functions.js"></script>
<script src="script.js"></script>
</body>
</html>
这个页面很简略,就不具体阐明了。
functions.js
文件中蕴含将会在第二个脚本中用到的数学函数。关上文件并增加以下内容:
functions.js
function sum(x, y) {return x + y}
function difference(x, y) {return x - y}
function product(x, y) {return x * y}
function quotient(x, y) {return x / y}
最初,script.js
文件用来确定 x 和 y 的值,以及调用后面那些函数并显示后果:
script.js
const x = 10
const y = 5
document.getElementById('x').textContent = x
document.getElementById('y').textContent = y
document.getElementById('addition').textContent = sum(x, y)
document.getElementById('subtraction').textContent = difference(x, y)
document.getElementById('multiplication').textContent = product(x, y)
document.getElementById('division').textContent = quotient(x, y)
保留之后在浏览器中关上 index.html
能够看到所有后果:
对于只须要一些小脚本的网站,这不失为一种无效的组织代码的办法。然而这种办法存在一些问题:
- 净化全局命名空间:你在脚本中创立的所有变量(
sum
、difference
等)当初都存在于window
对象中。如果你打算在另一个文件中应用另一个名为sum
的变量,会很难晓得在脚本的其它地位到底用的是哪一个值变量,因为它们用的都是雷同的window.sum
变量。惟一能够使变量公有的办法是将其放在函数的作用域中。甚至在 DOM 中名为x
的id
可能会和var x
存在抵触。 - 依赖治理:必须从上到下顺次加载脚本来确保能够应用正确的变量。将脚本别离保留存为不同文件会产生拆散的错觉,但实质上与放在页面中的单个
<script>
中雷同。
在 ES6 把原生模块增加到 JavaScript 语言之前,社区已经尝试着提供了几种解决方案。第一个解决方案是用原生 JavaScript 编写的,例如将所有代码都写在 objects 或立刻调用的函数表达式(IIFE)中,并将它们放在全局命名空间中的单个对象上。这是对多脚本办法的一种改良,然而依然存在将至多一个对象放入全局命名空间的问题,并没有使在第三方之间统一地共享代码的问题变得更加容易。
之后又呈现了一些模块解决方案:CommonJS 是一种在 Node.js 实现的同步办法,异步模块定义(AMD)是一种异步办法,还有反对后面两种款式的通用办法——通用模块定义(UMD)。
这些解决方案的呈现使咱们能够更轻松地以 包的模式共享和重用代码,也就是能够散发和共享的模块,例如 npm。然而因为存在许多解决方案,并且都不是 JavaScript 原生的,所以须要依附 Babel、Webpack 或 Browserify 之类的工具能力在浏览器中应用。
因为多文件办法存在许多问题,并且解决方案很简单,所以开发人员对把模块化开发的办法引入 JavaScript 语言十分感兴趣。于是 ECMAScript 2015 开始反对 JavaScript module。
module 是一组代码,用来提供其余模块所应用的性能,并能应用其余模块的性能。export 模块提供代码,import 模块应用其余代码。模块之所以有用,是因为它们容许咱们重用代码,它们提供了许多可用的稳固、统一的接口,并且不会净化全局命名空间。
模块(有时称为 ES 模块)当初能够在原生 JavaScript 中应用,在本文中,咱们一起来摸索怎么在代码中应用及实现。
原生 JavaScript 模块
JavaScript 中的模块应用import
和 export
关键字:
import
:用于读取从另一个模块导出的代码。export
:用于向其余模块提供代码。
接下来把后面的的 functions.js
文件更新为模块并导出函数。在每个函数的后面增加 export
。
functions.js
export function sum(x, y) {return x + y}
export function difference(x, y) {return x - y}
export function product(x, y) {return x * y}
export function quotient(x, y) {return x / y}
在 script.js
中用 import
从后面的 functions.js
模块中检索代码。
留神:
import
必须始终位于文件的顶部,而后再写其余代码,并且还必须包含相对路径(在这个例子里为./
)。
把 script.js
中的代码改成上面的样子:
script.js
import {sum, difference, product, quotient} from './functions.js'
const x = 10
const y = 5
document.getElementById('x').textContent = x
document.getElementById('y').textContent = y
document.getElementById('addition').textContent = sum(x, y)
document.getElementById('subtraction').textContent = difference(x, y)
document.getElementById('multiplication').textContent = product(x, y)
document.getElementById('division').textContent = quotient(x, y)
留神:要通过在花括号中命名单个函数来导入。
为了确保代码作为模块导入,而不是作为惯例脚本加载,要在 index.html
中的 script
标签中增加type="module"
。任何应用 import
或 export
的代码都必须应用这个属性:
index.html
<script
type="module" src="functions.js">
</script>
<script
type="module" src="script.js">
</script>
因为受限于 CORS 策略,必须在服务器环境中应用模块,否则会呈现上面的谬误:
Access to script at 'file:///Users/your_file_path/script.js' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, chrome-untrusted, https.
模块与惯例脚本不一样的中央:
- 模块不会向全局(
window
)作用域增加任何内容。 - 模块始终处于严格模式。
- 在同一文件中把同一模块加载两次不会出问题,因为模块仅执行一次
- 模块须要服务器环境。
模块依然常常与打包程序(如 Webpack)一起配合应用,用来减少对浏览器的反对和附加性能,但它们也能够间接用在浏览器中。
接下来摸索更多应用 import
和 export
语法的形式。
命名导出
如前所述,应用 export
语法容许你别离导入按名称导出的值。以这个 function.js
的简化版本为例:
functions.js
export function sum() {}
export function difference() {}
这样容许你用花括号按名称导入 sum
和 difference
:
script.js
import {sum, difference} from './functions.js'
也能够用别名来重命名该函数。这样能够防止在同一模块中产生命名抵触。在这个例子中,sum
将重命名为 add
,而 difference
将重命名为 subtract
。
script.js
import {
sum as add,
difference as subtract
} from './functions.js'
add(1, 2) // 3
在这里调用 add()
将产生 sum()
函数的后果。
应用 *
语法能够将整个模块的内容导入到一个对象中。在这种状况下,sum
和 difference
将成为 mathFunctions
对象上的办法。
script.js
import * as mathFunctions from './functions.js'
mathFunctions.sum(1, 2) // 3
mathFunctions.difference(10, 3) // 7
原始值、函数表达式和定义、异步函数、类和实例化的类都能够导出,只有它们有标识符就行:
// 原始值
export const number = 100
export const string = 'string'
export const undef = undefined
export const empty = null
export const obj = {name: 'Homer'}
export const array = ['Bart', 'Lisa', 'Maggie']
// 函数表达式
export const sum = (x, y) => x + y
// 函数定义
export function difference(x, y) {return x - y}
// 匿名函数
export async function getBooks() {}
// 类
export class Book {constructor(name, author) {
this.name = name
this.author = author
}
}
// 实例化类
export const book = new Book('Lord of the Rings', 'J. R. R. Tolkein')
所有这些导出都能够胜利被导入。接下来要探讨的另一种导出类型称为默认导出。
默认导出
在后面的例子中咱们导出了多个命名的导出,并别离或作为一个对象导入了每个导出,将每个导出作为对象上的办法。模块也能够用关键字 default
蕴含默认导出。默认导出不应用大括号导入,而是间接导入到命名标识符中。
以 functions.js
文件为例:
functions.js
export default function sum(x, y) {return x + y}
在 script.js
文件中,能够用以下命令将默认函数导入为 sum
:
script.js
import sum from './functions.js'
sum(1, 2) // 3
不过这样做很危险,因为在导入过程中对默认导出的命名没有做任何限度。在这个例子中,默认函数被导入为 difference
,只管它实际上是 sum
函数:
script.js
import difference from './functions.js'
difference(1, 2) // 3
所以个别首选应用命名导出。与命名导出不同,默认导出不须要标识符——原始值自身或匿名函数都能够用作默认导出。以下是用作默认导出的对象的示例:
functions.js
export default {
name: 'Lord of the Rings',
author: 'J. R. R. Tolkein',
}
能够用以下命令将其作为 book
导入:
functions.js
import book from './functions.js'
同样,上面的例子演示了如何将匿名箭头函数导出为默认导出:
functions.js
export default () => 'This function is anonymous'
能够这样导入:
script.js
import anonymousFunction from './functions.js'
命名导出和默认导出能够彼此并用,例如在这个模块中,导出两个命名值和一个默认值:
functions.js
export const length = 10
export const width = 5
export default function perimeter(x, y) {return 2 * (x + y)
}
能够用以下命令导入这些变量和默认函数:
script.js
import calculatePerimeter, {length, width} from './functions.js'
calculatePerimeter(length, width) // 30
当初默认值和命名值都可用于脚本了。
总结
模块化编程设计容许咱们把代码分成单个组件,这有助于代码重用,同时还能够爱护全局命名空间。一个模块接口能够在原生 JavaScript 中用关键字 import
和 export
来实现。
本文首发微信公众号:前端先锋
欢送扫描二维码关注公众号,每天都给你推送陈腐的前端技术文章
欢送持续浏览本专栏其它高赞文章:
- 深刻了解 Shadow DOM v1
- 一步步教你用 WebVR 实现虚拟现实游戏
- 13 个帮你进步开发效率的古代 CSS 框架
- 疾速上手 BootstrapVue
- JavaScript 引擎是如何工作的?从调用栈到 Promise 你须要晓得的所有
- WebSocket 实战:在 Node 和 React 之间进行实时通信
- 对于 Git 的 20 个面试题
- 深刻解析 Node.js 的 console.log
- Node.js 到底是什么?
- 30 分钟用 Node.js 构建一个 API 服务器
- Javascript 的对象拷贝
- 程序员 30 岁前月薪达不到 30K,该何去何从
- 14 个最好的 JavaScript 数据可视化库
- 8 个给前端的顶级 VS Code 扩大插件
- Node.js 多线程齐全指南
- 把 HTML 转成 PDF 的 4 个计划及实现
- 更多文章 …