ES6 -- 模块化(CommonJS、AMD、ES Module)

news2025/1/11 5:37:40

模块模式

将代码拆分成独立的块,然后再将这些块连接起来可以通过模块模式来实现。这种模式背后的思想很简单:把逻辑分块,各自封装,相互独立,每个块自行决定对外暴露什么,同时自行决定引入执行哪些外部代码。

模块标识符

模块系统本质上是键/值实体,其中每个模块都有个可用于引用它的标识符。这个标识符在模拟模块的系统中可能是字符串,在原生实现的模块系统中可能是模块文件的实际路径

模块依赖

每个模块都会与某一个唯一的标识符关联,该标识符可用于检索模块。这个标识符通常是 JavaScript文件的路径,但在某些模块系统中,这个标识符也可以是在模块本身内部声明的命名空间路径字符串。

模块加载

  1. 加载模块的概念派生自依赖契约。当一个外部模块被指定为依赖时,本地模块期望在执行它时,依赖已经准备好并已初始化。
  2. 在浏览器中,加载模块涉及几个步骤。加载模块涉及执行其中的代码,但必须是在所有依赖都加载并执行之后。如果浏览器没有收到依赖模块的代码,则必须发送请求并等待网络返回。收到模块代码之后,浏览器必须确定刚收到的模块是否也有依赖。然后递归地评估并加载所有依赖,直到所有依赖模块都加载完成。只有整个依赖图都加载完成,才可以执行入口模块。

入口

相互依赖的模块必须指定一个模块作为入口,这也是代码执行的起点。

动态依赖

if(loadCondition) {
    require('./moduleA');
}

在这个模块中,是否加载 moduleA 是运行时确定的。
动态依赖可以支持更复杂的依赖关系,但代价是增加了对模块进行静态分析的难度。

静态分析

分析工具会检查代码结构并在不实际执行代码的情况下推断其行为。

循环依赖

加载器回执行深度优先的依赖加载:

截屏2023-10-03 15.07.35.png

使用ES6之前对模块加载器

在ES6原生支持模块之前,使用模块的JavaScript代码本质上是希望使用默认没有的语言特性。因此,必须按照符合某种规范的模块语法来编写代码,另外还需要单独的模块工具把这些模块语法与JavaScript运行时连接起来。这里的模块语法和连接方式有不同的表现形式,通常需要在浏览器中额外加载库或者在构建时完成预处理。

CommonJS

CommonJS规范概述了同步声明依赖的模块定义。这个规范主要用于在服务器端实现模块化代码组织,但也可用于定义在浏览器中使用的模块依赖。CommonJS模块语法不能直接在浏览器中运行

注意 一般认为,Node.js的模块系统使用了CommonJS规范,实际上并不完全正确。Node.js使用了轻微修改版本的CommonJS,因为Node.js主要在服务器环境下使用,所以不需要考虑网络延迟问题。考虑到一致性,本节使用Node.js风格的模块定义语法。

CommonJS模块定义需要使用require()指定依赖,而使用exports对象定义自己的公共API。

var moduleB = require('./moduleB');
module.exports = {
    stuff: moduleB.doStuff();
}

无论一个模块在 require() 中被引用多少次,模块永远是单例

console.log('moduleA');
var a1 = require('./moduleA');
var a2 = require('./moduleA');

console.log(a1===a2); // true

模块第一次加载后会被缓存,后续加载会取得缓存的模块。模块的加载顺序由依赖图决定。
在 CommonJS 中,模块加载时模块系统执行的同步操作。因此 require()可以像下面这样以编程方式嵌入在模块中:

console.log('moduleA');
if (loadCondition) {
    require('./moduleA');
}

如果moduleA已经在前面某个地方加载过了,这个条件 require()就意味着只暴露moduleA这个命名空间而已。

异步模块定义

CommonJS以服务端为目标环境,能够一次性把所有模块都加载到内存,而异步模块定义(AMD Asynchronous Module Definition)的模块定义系统则以浏览器为目标执行环境,这需要考虑网络延迟的问题。AMD的一般策略是让模块声明自己的依赖,而运行在浏览器中的模块系统会按需获取依赖,并在依赖加载完成后立即执行依赖它们的模块。

AMD模块实现的核心是用函数包装模块定义。这样可以防止声明全局变量,并允许加载器库控制何时加载模块。包装函数也便于模块代码的移植,因为包装函数内部的所有模块代码使用的都是原生JavaScript结构。包装模块的函数是全局 define 的参数,它是由AMD加载器库的实现定义的

AMD模块可以使用字符串标识符指定自己的依赖,而AMD支持可选地为模块指定字符串标识符。

// ID 为‘moduleA’的模块定义。moduleA依赖moduleB,
// moduleB会异步加载
define('moduleA', ['moduleB'], function(moduleB) {
  return {
    stuff: moduleB.duStuff();
  }
})

AMD也支持require和exports对象,通过他们的可以在AMD模块工厂函数内部定义CommonJS风格的模块。这样可以像请求模块一样请求它们,但AMD加载器会将它们识别为原生AMD结构,而不是模块定义

define('moduleA', ['require'], function(require){
  if(condition) {
    var moduleB = require('moduleB');
  }
})

通用模块定义

为了统一CommonJS和AMD生态系统,通用模块定义(UMD,Universal Module Definition)规范应运而生。UMD可用于创建这两个系统都可以使用的模块代码。本质上,UMD定义的模块会在启动时检测要使用哪个模块系统,然后进行适当配置,并把所有逻辑包装在一个立即调用的函数表达式中。

下面是只包含一个依赖的UMD模块定义的示例:

  (function (root, factory) {
    if(typeof define === 'function' && define.amd) {
      //AMD。注册为匿名函数
      define(['moduleB'], factory);
    }else if (typeof module === 'object' && module.exports) {
      // Node。不支持严格CommonJS
      // 但可以在Node这样支持module.exports的类 CommonJS环境下使用
      module.exports = factory(require('moduleB'));
    }else {
      // 浏览器全局上下文(root是window)
      root.returnExports = factory(root.moduleB);
    }
  }(this, function (moduleB) {
    // 以某种方式使用moduleB
    // 将返回值作为模块的导出
    // 这个例子返回了一个对象
    // 但是模块也可以返回函数作为到处值
    return {};
  }));

模块加载器终将没落

随着ECMAScript 6模块规范得到越来越广泛的支持,上述展示的模式终会走向没落

使用 ES6 模块

ES6模块最大的一个改进就是引入了模块规范。这个规范全方位简化了之前出现的模块加载器,原生浏览器支持意味着加载器以及其他预处理都不再必要。从很多方面看,ES6模块系统是集AMD和CommonJS之大成者。

模块标签及定义

ECMAScript 6模块是作为一整块JavaScript代码而存在的。带有type="module"属性的

模块加载

完全支持 ECMAScript 6模块的浏览器可以从顶级模块加载整个依赖图,且是异步完成的。浏览器会解析入口模块,确定依赖,并发送对依赖模块的请求。这些文件通过网络返回之后,浏览器就会解析它们的内容,确定它们的依赖,如果这些二级依赖还没有加载,则会发送更多请求。这个异步递归加载过程会持续到整个应用程序的依赖图都解析完成。解析完依赖图,应用程序就可以正式加载模块了。
这个过程与AMD风格的模块加载非常相似。模块文件按需加载,且后续模块的请求会因为这个依赖模块的网络延迟而同步延迟。

模块行为

ECMAScript 6模块借用了CommonJS和AMD的很多优秀特性。如:

  • 模块代码只在加载后执行
  • 模块只能加载一次
  • 模块是单例
  • 模块可以定义公共接口,其他模块可以基于这个公共接口观察和交互。
  • 模块可以请求加载其他模块
    ES6模块系统也增加了一下新行为
  • ES6模块默认在严格模式下执行
  • ES6模块不共享全局命名空间
  • 模块顶级this的值是undefined(常规脚本中是window)
  • 模块中的 var 声明不会添加到 window 对象
  • ES6模块是异步加载和执行的

模块导出

ES6模块支持两种导出:命名导出和默认导出。

  1. export 关键字用于声明一个值为命名导出。导出语句必须在模块顶级,不能嵌套在某个块中
  2. 导出值对模块内部JavaScript的执行没有直接影响,因此 export 语句与导出值对相对位置或者 export 关键字在模块中出现的顺序没有限制。export语句甚至可以出现在它要导出的值之前:
    const foo = 'foo';
    export { foo };
    
    export const foo = 'foo';
    
    export { foo };
    const foo = 'foo';

命名导出就好像模块是被导出值的容器。

1. 行内命名导出
    export const foo = 'foo';
    const foo = 'foo';
    export { foo };
2. 导出时可以提供别名,别名必须在export子句的大括号语法中指定。
    const foo = 'foo';
    export { foo as myFoo };
3. 导出声明分组
    const foo = 'foo';
    const bar = 'bar';
    const baz = 'baz';
    export { foo, bar as myBar, baz};

默认导出就好像模块与被导出的值是一回事。默认导出使用 default 关键字将一个值声明为默认导出,每个模块只能有一个默认导出

export default foo
export default function() {}
export default function foo() {}
export default function*() {}
export default class {}

模块导入

模块可以通过使用import关键字使用其他模块导出的值。必须出现在模块的顶级

  • 模块标识符可以是相对于当前模块的相对路径,也可以是指向模块文件的绝对路径。它必须是纯字符串,不能说动态计算的结果。
  • 导入对模块来说是只读的,实际上相当于const声明的变量。在使用*执行批量导入时,赋值给别名的命名导出就好像使用Object.freeze()冻结过一样。直接修改导出的值时不可能的,但是可以修改导出对象的属性
  • 命名导出和默认导出的区别也反映在他们的导入上。命名导出可以使用*批量获取并赋值给保存导出集合的别名,而无需列出每个标识符:
const foo = 'foo', bar = 'bar', baz = 'baz'
export {foo, bar, baz}

    
import * as Foo form './foo.js'
console.log(Foo.foo); // foo    
  • 要指名导入,需要把标识符放在 import 子句中。
import { foo, bar, baz as myBaz } from './foo.js';    
  • 默认导出就好像整个模块就是导出值一样。可以使用 default 关键字并提供别名来导入。
import { default as foo } from './foo.js';
import foo from './foo.js'

模块转移导出

模块导入的值可以直接通过管道转移到导出。此时,也可以将默认导出转换为命名导出,或者相反。如果想把一个模块的所有命名导出集中在一块,可以像下面这样在bar.js中使用*导出:

export * from './foo.js'    

这样,foo.js中的所有命名导出都会出现在导入bar.js的模块中。如果foo.js有默认导出,则该语法会忽略它。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1092916.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

springboot 制造装备物联及生产管理ERP系统

springboot 制造装备物联及生产管理ERP系统 liu1113625581

什么是js?(初步了解)

Javascript简称js,js是一种脚本语言,是不需要进行编译的,也是浏览器中的一部分,经常用在web客户端脚本语言,主要是用来给html增加动态功能。 而现在,由于Node.js的出现,JavaScript以及不局限与浏览器,用户也可以在服务端进行代码的编写&#…

gpt4.0和3.5区别在哪里:什么样的人适合使用gpt4.0?

GPT-4和GPT-3.5相比,就像是智能手机的新一代和上一代。升级了,功能多了,但核心都是让你的工作和生活更便捷。 联网能力 GPT-3.5:与世隔绝,不联网。 GPT-4:5月17日后能上网浏览,信息获取能力加…

Zabbix7.0 LTS新功能

一、简介 LTS是长期支持。LTS版本支持5年。如果更喜欢稳定性,未涉及到最新的功能,可以选次新的LTS或者更低解决方案。而Zabbix6.4是最新的主要版本不属于LTS版本。 二、新功能 从以下几个方面介绍部分新功能: 性能提升:内存储存…

二十四、【参考素描三大面和五大调】

文章目录 三种色面(黑白灰)五种色调 这个可以参考素描对物体受光的理解:素描调子的基本规律与素描三大面五大调物体的明暗规律 三种色面(黑白灰) 如下图所示,我们可以看到光源是从亮面所对应的方向射过来的,所以我们去分析图形的时候,首先要…

SpringMVC之WEB-INF下页面跳转@ModelAttributeIDEA tomcat控制台中文乱码问题处理

WEB-INF下页面跳转 ModelAttribute来注解非请求处理方法 用途:预加载数据,会在每个RequestMapping方法执行之前调用。 特点:无需返回视图,返回类型void IDEA tomcat控制台中文乱码问题处理 复制此段代码:-Dfile.e…

Fooocus AI绘画多种风格阿弥陀佛像

ai绘画越来越简单了,fooocus就是最近比较多人议论的一个stable diffusion 变种,由control net 作者针对 标midJourney的一款同类开源绘画工具。作为工具开发来说,肯定是越简单,用户越多。当然,模型用的还是SD官方模型。…

【运算符+逻辑控制】

文章目录 1.类型转换2.类型提升3.如何求字符串长度 4.字符串类型String的神奇用法 4.运算符算术运算符关系运算符逻辑运算符 5.位运算符移位运算符条件运算符6.java的输入7. 判断是否为闰年8.switch 语句9.while循环计算1~100之间的和计算1~100之间的奇数的和计算1~100之间偶数…

如何在小程序的个人中心页面进行装修

个人中心页面是小程序中非常重要的页面之一,它是用户与小程序进行互动和管理的主要入口。为了提升用户体验和个性化定制,商家可以对个人中心页面进行装修,包括设置顶部的背景图片和标题图片、我的服务项目等等。 下图,是小程序个人…

顺序表之(条条有注释助你吃透顺序表以及基于顺序表实现的通讯录)

顺序表 顺序表是线性表的一种,而线性表指的是具有相同特性的一类数据结构的统称,这些相同特性即在逻辑结构(人为想象)上一定是线性的,在物理结构(内存存储中)上不一定是线性的 顺序表的底层结构…

Java应用的防破解方案

前言 自从Java诞生以来,其字节码容易被反编译的问题就为程序员所诟病。由此也诞生了不少Java混淆工具和加壳软件。 Java应用的打包 exe4jlaunch4jJSmooth: 已经过时了jpackage:JDK自带的打包工具Installer工具:Inno Setup、NSIS(https://s…

Redis 最流行的图形化界面下载及使用超详细教程(带安装包)! redis windows客户端下载

另一个完全不同的redis图形化界面教程链接(带安装包): https://blog.csdn.net/dream_ready/article/details/133817466 redis图形化界面的压缩包,直接解压缩即可,双击 Another Redis Desktop Manager.exe 即可打开 1、…

数据结构--》掌握数据结构中的查找算法

当你需要从大量数据中查找某个元素时,查找算法就变得非常重要。 无论你是初学者还是进阶者,本文将为你提供简单易懂、实用可行的知识点,帮助你更好地掌握查找在数据结构和算法中的重要性,进而提升算法解题的能力。接下来让我们开启…

IDEA中SpringBoot的启动类文件变成了一个J文件的解决方案

错误如下: 解决方案: 此时可以发现已经恢复成功了

使用Python进行钻石价格分析

钻石是最昂贵的宝石之一。钻石的质量通常以其重量(克拉)、净度、颜色和切工来评估。重量越大、净度越高、色彩纯净、切工精细的钻石价格也越高。其中,4C标准是衡量钻石质量的国际标准,即克拉(Carat)、净度&…

java中将数组转换成字符串

方法1&#xff1a;使用StringBuilder拼接 基本类型和引用类型兼容 int[] arr {1,2,4,6,9};StringBuilder sb new StringBuilder();for (int i 0; i < arr.length ; i) {if (i!arr.length-1){sb.append(arr[i]",");}else {sb.append(arr[i]);}}System.out.prin…

面向对象特征【封装性】

文章目录 OOP封装性内聚&#xff08;Cohesion&#xff09;耦合&#xff08;Coupling&#xff09; 封装性的优势最佳实践实际应用示例 OOP 面向对象编程&#xff08;OOP&#xff09;是计算机科学中的一个核心范例&#xff0c;它的其中一个重要特征是封装性。封装性有助于提高代码…

“之江创客”跨境电商赛区决赛暨浙南新电商发展论坛圆满落幕

9月26日&#xff0c;由商务部中国国际电子商务中心指导&#xff0c;浙江省商务厅等十个部门主办&#xff0c;浙江省电子商务促进中心、温州市商务局、苍南县人民政府承办的“之江创客”2023全球电子商务创业创新大赛跨境电商赛区决赛暨浙南新电商发展论坛在苍南圆满落幕。浙江省…

快递查询方法分享:如何批量查询并筛选超时快递?

快递查询是我们在日常生活中经常需要进行的一项任务。然而&#xff0c;当我们需要同时查询多个快递单号的时候&#xff0c;手动一个一个输入单号进行查询无疑是一项十分耗时的工作。为了解决这个问题&#xff0c;今天给大家介绍一款名为“固乔快递查询助手”的神器。 固乔快递查…

gRPC之gRPC Gateway

1、gRPC Gateway etcd3 API全面升级为gRPC后&#xff0c;同时要提供REST API服务&#xff0c;维护两个版本的服务显然不太合理&#xff0c;所以 grpc-gateway 诞生了。通过protobuf的自定义option实现了一个网关&#xff0c;服务端同时开启gRPC和HTTP服务&#xff0c; HTTP服…