详解 JavaScript 中的模块、Import和Export

在互联网的洪荒时代,网站主要用 HTML和 CSS 开发的。若是将 JavaScript 加载到页面中,一般是以小片断的形式提供效果和交互,通常会把全部的 JavaScript 代码全都写在一个文件中,并加载到一个 script 标签中。尽管能够把 JavaScript 拆分为多个文件,可是全部的变量和函数仍然会被添加到全局做用域中。前端

可是后来 JavaScript 在浏览器中发挥着重要的做用,迫切须要使用第三方代码来完成常见任务,而且须要把代码分解为模块化的文件,避免污染全局命名空间。java

ECMAScript 2015 规范在 JavaScript 语言中引入了 module,也有了 import 和 export 语句。在本文中,咱们一块儿来学习 JavaScript 模块,以及怎样用 import 和 export 来组织代码。程序员

模块化编程

在 JavaScript 中出现模块的概念以前,当咱们想要把本身的代码组织为多个块时,通常会建立多个文件,而且将它们连接为单独的脚本。下面先举例说明,首先建立一个 index.html 文件和两个JavaScript文件“ functions.js 和 script.js。 面试

index.html 文件用来显示两个数字的和、差、乘积和商,并连接到 script 标签中的两个 JavaScript 文件。打开 index.html 并添加如下代码:chrome

index.htmlnpm

<!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 文件中包含将会在第二个脚本中用到的数学函数。打开文件并添加如下内容:segmentfault

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 来实现。

猜你喜欢

转载自blog.csdn.net/hmily43/article/details/124244751