乐趣区

关于javascript:详解-JavaScript-中的模块Import和Export

作者:Tania Rascia

翻译:疯狂的技术宅

原文:https://www.taniarascia.com/j…

在互联网的洪荒时代,网站次要用 HTML 和 CSS 开发的。如果将 JavaScript 加载到页面中,通常是以小片段的模式提供成果和交互,个别会把所有的 JavaScript 代码全都写在一个文件中,并加载到一个 script 标签中。只管能够把 JavaScript 拆分为多个文件,然而所有的变量和函数依然会被增加到全局作用域中。

然而起初 JavaScript 在浏览器中施展着重要的作用,迫切需要应用第三方代码来实现常见工作,并且须要把代码合成为模块化的文件,防止净化全局命名空间。

ECMAScript 2015 标准在 JavaScript 语言中引入了 module,也有了 import 和 export 语句。在本文中,咱们一起来学习 JavaScript 模块,以及怎么用 importexport 来组织代码。

模块化编程

在 JavaScript 中呈现模块的概念之前,当咱们想要把本人的代码组织为多个块时,个别会创立多个文件,并且将它们链接为独自的脚本。上面先举例说明,首先创立一个 index.html 文件和两个 JavaScript 文件“functions.jsscript.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 能够看到所有后果:

对于只须要一些小脚本的网站,这不失为一种无效的组织代码的办法。然而这种办法存在一些问题:

  • 净化全局命名空间:你在脚本中创立的所有变量(sumdifference 等)当初都存在于 window 对象中。如果你打算在另一个文件中应用另一个名为 sum 的变量,会很难晓得在脚本的其它地位到底用的是哪一个值变量,因为它们用的都是雷同的 window.sum 变量。惟一能够使变量公有的办法是将其放在函数的作用域中。甚至在 DOM 中名为 xid 可能会和 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 中的模块应用importexport 关键字:

  • 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"。任何应用 importexport 的代码都必须应用这个属性:

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)一起配合应用,用来减少对浏览器的反对和附加性能,但它们也能够间接用在浏览器中。

接下来摸索更多应用 importexport 语法的形式。

命名导出

如前所述,应用 export 语法容许你别离导入按名称导出的值。以这个 function.js 的简化版本为例:

functions.js

export function sum() {}
export function difference() {}

这样容许你用花括号按名称导入 sumdifference

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() 函数的后果。

应用 * 语法能够将整个模块的内容导入到一个对象中。在这种状况下,sumdifference 将成为 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 中用关键字 importexport 来实现。


本文首发微信公众号:前端先锋

欢送扫描二维码关注公众号,每天都给你推送陈腐的前端技术文章


欢送持续浏览本专栏其它高赞文章:

  • 深刻了解 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 个计划及实现

  • 更多文章 …
退出移动版