了解ES6模块

前言

在早期,JavaScript程序很小,用来你的 web 页面需要的地方提供一定交互,所以不需要多大的脚本。而后来,随着JavaScript程序越来越复杂,需要一种将 JavaScript 程序拆分为可按需导入的单独模块的机制,著名的 CommonJSAMD 诞生了。前者主要用于服务端,后者则是用于浏览器。但这些都是社区提供的模块加载方案,随着ES6的到来,JavaScript原生模块(ES Module)也正式在浏览器登场。

ES Module(简称ESM)在所有现代浏览器都支持,它依赖于 importexport 命令。

export 命令

export命令用于导出模块的功能import命令用于导入其他模块提供的功能

一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。

最简单的写法是把export放到要导出的项前面:

// user.js
export let name = 'Tom'
export let age = 10
export function jump() {
    
    
  console.log('jump')
}

上面的代码向外部导出了两个变量和一个函数,我们还可以用另一种写法:

// user.js
let name = 'Tom'
let age = 10
function jump() {
    
    
  console.log('jump');
}

export {
    
     name, age, jump }

将export写在最后,使用大括号指定要导出的功能。它和上一种方法是等价的,但是可以更直观的看到导出的内容。

在导出时,我们可以使用as关键字将变量重命名

// user.js
let name = 'Tom'
let age = 10
function jump() {
    
    
  console.log('jump');
}

export {
    
     name as n, age as a, jump as j }

此时外部就可以通过导入n来使用name的值。

import 命令

使用export导出模块后,就可以使用import导入这个模块。

// user.js
let name = 'Tom'
let age = 10
function jump() {
    
    
  console.log('jump');
}

export {
    
     name, age, jump }

// main.js
import {
    
     name, age, jump } from './user.js'

console.log(name);
console.log(age);
jump()

// Tom
// 10
// jump

导入时也可以使用as关键字重命名

// user.js
let name = 'Tom'
let age = 10
function jump() {
    
    
  console.log('jump');
}

export {
    
     name, age, jump }

// main.js
import {
    
     name as n, age as a, jump as j } from './user.js'

console.log(n);
console.log(a);
j()

除了上面将变量逐个导入的方法之外,我们还可以导入整个模块,使用以下语法:

// main.js
// 这将获取user.js中所有可用的导出
import * as user from './user.js'

console.log(user.name);
console.log(user.age);
user.jump()

注意,import命令输入的变量都是只读的,也就是说,不允许在加载模块的脚本里面,改写接口

// user.js
let name = 'Tom'
let obj = {
    
    }

export {
    
     name, obj }

// main.js
import {
    
     name, obj } from './user.js'

name = 'Jery' // 不合法 Uncaught TypeError: Assignment to constant variable
obj.prop = 'esm' // 合法

在HTML中导入模块

我们已经写好了简单的模块,接下来就是在html中引入main.js这个文件。

<script src="./js/main.js"></script>

此时浏览器是会报错的,它会提示你语法错误。
在这里插入图片描述

这是因为浏览器不知道这个文件是模块,需要在script标签添加type="module"

<script type="module" src="./js/user.js"></script>

此时浏览器已经可以正确识别ES6模块,就不会报错了。

ESM也允许内嵌在网页中,语法行为与加载外部脚本完全一致

<script type="module">
	import {
    
     name } from './js/user.js'
	console.log(name); // Tom
</script>

默认导出 与 命名导出

上面讲的导出功能都是由 named exports(命名导出) 组成 — 每个项目(无论是函数,常量等)在导出时都由其名称引用,并且该名称也用于在导入时引用它。

还有一种导出类型叫做 default export(默认导出),用户可以快速上手使用,不需要了解模块有哪些属性和方法。

下面用代码来对比一下:

// 命名导出
export function jump() {
    
    
  console.log('jump');
}

import {
    
     jump } from './user.js'

// 默认导出
export default function jump() {
    
    
  console.log('jump');
}

import jump from './user.js'

可以看到,命名导出对应的import语句需要使用大括号,默认导出是不需要使用大括号的。

export default命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,因此export default命令只能使用一次。所以,import命令后面才不用加大括号,因为只可能唯一对应export default命令。

本质上,export default 相当于导出一个叫default的变量,所以下面的代码是等价的

// user.js
function jump() {
    
    
  console.log('jump');
}

export default jump
// 相当于
// export { jump as default }

// main.js
import jump from './user.js'
//相当于
// import { default as jump } from './user.js'

如果想在一条import语句中,同时使用默认导入和命名导入,可以写成下面这样:

// user.js
let name = 'Tom'
let age = 10
function jump() {
    
    
  console.log('jump');
}

export {
    
     name, age }
export default jump

// main.js
import jump, {
    
     name, age } from './user.js'

export 和 import 的复合写法

如果在一个模块之中,先输入后输出同一个模块,import语句可以与export语句写在一起

// user.js
const name = 'Tom'

export {
    
     name }

// account.js
const username = 'Jack'

export {
    
     username }

// module.js
export {
    
     name } from './user.js'
export {
    
     username} from './account.js'
// 可以简单理解为
// import { name } from './user.js'
// export { name }
// import { username } from './account.js'
// export { username }

虽然exportimport语句可以结合在一起,写成一行,但是nameusername其实并没有被导入当前模块,只是相当于对外转发了这两个导出,在当前模块是不能使用的。

默认导出的复合写法如下:

export {
    
     default } from './user.js'

命名导出改为默认导出的复合写法如下:

export {
    
     name as default } from './user.js'

// 等同于
// import { name } from './user.js';
// export default name;

默认导出改为命名导出的复合写法如下:

export {
    
     default as name } from './user.js'

整个模块导出的复合写法如下:

export * as user from "./user.js"

// 等同于
// import * as user from "./user.js"
// export { user }

动态加载模块

importexport命令只能在模块的顶层,不能在代码块之中(比如,在if代码块之中,或在函数之中)。

// 报错
if (x === 1) {
    
    
  import user from './user.js';
}

上面代码中,import命令是在编译时处理,这时不会去分析或执行if语句,所以import语句放在if代码块之中毫无意义,因此会报语法错误。

ES2020提案 引入import()函数,支持动态加载模块。import()返回一个 Promise 对象,所以需要使用then()方法指定处理函数。

import('./user.js')
.then(module => {
    
    
  // Do something with the module
})
.catch(err => {
    
    
  // Error handling
})

为了代码的清晰,推荐使用await命令。

async function init() {
    
    
  const user = await import('./user.js')
}

猜你喜欢

转载自blog.csdn.net/sunddy_x/article/details/125332044