【ES6知识】ESModule 模块化

2023-09-14 14:20:39

一、前言:模块化背景

JavaScript 程序本来很小——在早期,它们大多被用来执行独立的脚本任务,在你的 web 页面需要的地方提供一定交互,所以一般不需要多大的脚本。过了几年,我们现在有了运行大量 JavaScript 脚本的复杂程序,还有一些被用在其他环境(例如 Node.js)的需求。因此,近年来,有必要开始考虑提供一种将 JavaScript 程序拆分为可按需导入的单独模块的机制。

原生JavaScipt案例合集
JavaScript +DOM基础
JavaScript 基础到高级
Canvas游戏开发

1.1 后端模块化

Node.js 已经提供这个能力很长时间了,其是对 CommonJS 规范的实现。CommonJS 规范是为了解决 JavaScript 的作用域问题而定义的模块形式,可以使每个模块它自身的命名空间中执行。该规范的主要内容是,模块必须通过 module.exports 导出对外的变量或接口,通过 require() 来导入其他模块的输出到当前模块作用域中。

// moduleA.js
module.exports = function( value ){
    return value * 2;
}
// moduleB.js
var multiplyBy2 = require('./moduleA');
var result = multiplyBy2(4);

1.2 关于前端模块化

在 ES6 前, 实现模块化使用的是 RequireJS 或者 seaJS(分别是基于 AMD 规范的模块化库, 和基于 CMD 规范的模块化库)。

ES6 引入了模块化,其设计思想是在编译时就能确定模块的依赖关系,以及输入和输出的变量。ESModule被认为是大一统的模块化设计规范,有如下特点:

  • ES6 模块化自动开启严格模式,不管你是否在模块的头部加上 use strict;
  • 模块中可以导入和导出各种类型的变量,如函数、对象、字符串、数字、布尔值、类等
  • 每个模块都有自己的上下文,每一个模块内声明的变量都是局部变量,不会污染全局作用域
  • 每一个模块只加载一次(是单例的),若再去加载同目录下同文件,直接从内存中读取

最新的浏览器开始支持原生的模块功能了,浏览器能够最优化加载模块,使它比使用库更有效率:使用库通常需要做额外的客户端处理。

为了使模块可以在浏览器中正常地工作,你需要确保你的服务器能够正常地处理 Content-Type 头,其应该包含 JavaScript 的 MIME 类型 text/javascript。如果没有这么做,你可能会得到 一个严格 MIME 类型检查错误:“The server responded with a non-JavaScript MIME type(服务器返回了非 JavaScript MIME 类型)”,并且浏览器会拒绝执行相应的 JavaScript 代码。

二、模块的导出

使用 export 语句分别导出需要的内容,如下:

export const name = 'square';

export function draw(ctx, length, x, y, color) {
  ctx.fillStyle = color;
  ctx.fillRect(x, y, length, length);

  return {
    length: length,
    x: x,
    y: y,
    color: color
  };
}

当然你也可以在模块文件的末尾使用 export{} 一次性的导出所有需要导出的信息(逗号分隔),如下:

export { name, draw, reportArea, reportPerimeter };

三、模块的导入

如果想要在模块外面使用其它模块中的功能,必须先导入它们才能使用,如下:

import { name, draw, reportArea, reportPerimeter } from './modules/square.js';

导入后就能像定义在相同文件中的功能一样去使用它了,如下:

let myCanvas = create('myCanvas', document.body, 480, 320);
let reportList = createReportList(myCanvas.id);

let square1 = draw(myCanvas.ctx, 50, 50, 100, 'blue');
reportArea(square1.length, reportList);
reportPerimeter(square1.length, reportList);

四、应用模块到HTML中

与常规脚本引入方式相似,但又一些显著的差异。

首先,你需要把 type="module" 放到 <script> 标签中,来声明这个脚本是一个模块:

<script type="module" src="main.js"></script>
  • 你只能在模块内部使用 importexport 语句,且导入的功能只在当前模块内生效(无法全局中获取)。
  • 本地测试 – 如果你通过本地加载 HTML 文件 (比如一个 file:// 路径的文件), 你将会遇到 CORS 错误,因为 JavaScript 模块安全性需要。你需要通过一个服务器来测试。
  • 加载一个模块脚本时不需要使用 defer 属性 (see <script> attributes) 模块会自动延迟加载

五、export default 默认导出命令

到目前为止我们导出的功能都是由 named exports 组成 —- 每个项目(无论是函数,常量等)在导出时都由其名称引用,并且该名称也用于在导入时引用它。

还有一种导出类型叫做 default export —- 这样可以很容易地使模块提供默认功能,并且还可以帮助 JavaScript 模块与现有的 CommonJS 和 AMD 模块系统进行互操作。特点如下:

  • 在一个文件或模块中,export、import 可以有多个,export default 仅有一个
  • export default 中的 default 是对应的导出接口变量,后面不能再跟变量的声明语句
  • export default 方式导出的数据,在导入时不需要加 {}
  • export default 方式向外暴露的成员,可以使用任意变量来接收
var a = "My name is Tom!";
export default a; // 仅有一个
export default var c = "error"; 
// error,default 已经是对应的导出变量,不能跟着变量声明语句
 
import b from "./xxx.js"; // 不需要加{}, 使用任意变量接收

六、重命名导出与导入

在你的 importexport 语句的大括号中,可以使用 as 关键字跟一个新的名字,来改变你在顶级模块中将要使用的功能的标识名字。因此,例如,以下两者都会做同样的工作,尽管方式略有不同:

// inside module.js
export {
  function1 as newFunctionName,
  function2 as anotherNewFunctionName
};

// inside main.mjs
import { newFunctionName, anotherNewFunctionName } from '/modules/module.mjs';
// inside module.js
export { function1, function2 };

// inside main.mjs
import { function1 as newFunctionName,
         function2 as anotherNewFunctionName } from '/modules/module.mjs';

七、创建模块对象

上面的方法工作的挺好,但是有一点点混乱、亢长。一个更好的解决方是,导入每一个模块功能到一个模块功能对象上。可以使用以下语法形式:

import * as Module from '/modules/module.js';

这将获取 module.js 中所有可用的导出,并使它们可以作为对象模块的成员使用,从而有效地为其提供自己的命名空间。例如:

Module.function1()
Module.function2()

八、动态加载模块

浏览器中可用的 JavaScript 模块功能的最新部分是动态模块加载。这允许您仅在需要时动态加载模块,而不必预先加载所有模块。这有一些明显的性能优势。

这个新功能允许您将 import() 作为函数调用,将其作为参数传递给模块的路径。它返回一个 promise,它用一个模块对象来实现(参见创建模块对象),让你可以访问该对象的导出,例如

import('/modules/myModule.js')
  .then((module) => {
    // Do something with the module.
  });
更多推荐

Rust踩雷笔记(7)——两个链表题例子初识裸指针

目录leetcode234leetcode19leetcode234题目在这https://leetcode.cn/problems/palindrome-linked-list/,leetcode234的回文链表,思路很简单,就是fast和slow两个指针,fast一次移动两个、slow一次一个,最后slow指向的链

【Linux系统编程】通过系统调用获取进程标识符 及 创建子进程(fork)

文章目录1.通过系统调用获取进程标示符(PID)1.1进程id(PID)1.2父进程id(PPID)2.bash也是一个进程3.通过系统调用创建进程-fork初识3.1批量化注释3.2取消注释3.3fork创建子进程3.4fork的返回值3.5fork之后通常要用if进行分流3.6父子进程代码共享,数据写时拷贝(实现相

手机号码携号转网API接口,轻松实现用户号码流转

携号转网是指用户可以将自己的手机号码从原来的运营商转移到其他运营商,以更好的服务、更低的资费和更多的优惠来吸引用户。而手机号码携号转网API接口,则是让开发者可以方便地实现用户号码流转的工具,下面就来介绍一下如何使用手机号码携号转网API接口。一、API接口简介手机号码携号转网API接口是指一个提供手机号码携号转网服务

GitHub 曝出漏洞,或导致 4000 多个存储库遭受劫持攻击

TheHackerNews网站披露,安全研究员发现GitHub中存在一个新安全漏洞,该漏洞可能导致数千个存储库面临劫持攻击的风险。据悉,在2023年3月1日漏洞披露后,微软旗下的代码托管平台已于2023年9月1日解决了安全漏洞问题。Checkmarx安全研究员EladRapoport在与TheHackerNews分享的

mysql向数据库中添加数据

要向MySQL数据库中添加数据,您可以使用INSERTINTO语句。以下是一些基本步骤和示例代码来添加数据:连接到数据库:首先,您需要使用MySQL客户端或编程语言中的MySQL连接库连接到您的数据库。编写INSERT语句:创建一个INSERTINTO语句,指定要插入数据的表名和要插入的数据列以及其值。语法如下:INS

【学习笔记】Java 一对一培训(2.1)Java基础语法

【学习笔记】Java一对一培训(2.1)Java基础语法关键词:Java、SpringBoot、Idea、数据库、一对一、培训、教学本文主要内容含Java简介、Java基础语法、Java对象和类、Java基本数据类型、Java变量类型、Java修饰符计划2小时完成,请同学尽量提前准备。这部分主要讲述Java是什么、怎么

Spring Boot的运行原理

SpringBoot的运行原理SpringBoot是一个用于快速构建独立、可独立运行的Spring应用程序的框架。它通过自动配置和约定优于配置的原则,简化了Spring应用程序的开发过程。下面将详细介绍SpringBoot的运行原理,并附上一些代码解释。1.主要组件SpringBoot的核心组件包括自动配置(Auto-

基于SpringBoot的图书商城系统

基于SpringBoot+Vue的网上书城系统、图书商城、网上书店系统,前后端分离开发语言:Java数据库:MySQL技术:SpringBoot、Vue、MybaitsPlus、ELementUI工具:IDEA/Ecilpse、Navicat、Maven【主要功能】角色:管理员、用户管理员:用户管理、图书类型管理、图书

TCP详解之流量控制

TCP详解之流量控制发送方不能无脑的发数据给接收方,要考虑接收方处理能力。如果一直无脑的发数据给对方,但对方处理不过来,那么就会导致触发重发机制,从而导致网络流量的无端的浪费。为了解决这种现象发生,TCP提供一种机制可以让「发送方」根据「接收方」的实际接收能力控制发送的数据量,这就是所谓的流量控制。下面举个栗子,为了简

百度之星(夏日漫步)

夏日夜晚,小度看着庭院中长长的走廊,萌发出想要在上面散步的欲望,小度注意到月光透过树荫落在地砖上,并且由于树荫的遮蔽度不通,所以月光的亮度不同,为了直观地看到每个格子的亮度,小度用了一些自然数来表示它们的亮度。亮度越高则数字越大,亮度相同的数字相同。走廊是只有一行地砖的直走廊。上面一共有n个格子,每个格子都被小度给予了

如何给API签名

前言有时候为了保护API,需要用到API签名,使用API签名的好处:让API只能被特定的人访问防止别人抓包拿到请求参数,通过篡改参数发起新的请求客户端过程给API调用者分配一个app_id和app_secret,app_secret调用者和服务端各保存一份,不对外泄露,app_id需要在调用API时作为参数传递。除了a

热文推荐