【ES6】阮一峰ES6学习之Module的加载实现

news/2024/7/10 22:50:03 标签: es6, 学习, javascript

Module的加载实现

  • 1. 浏览器加载
    • 传统方法
    • 加载规则
    • ES6 模块与 CommonJS 模块的差异
      • 1. CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
      • 2. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
      • 3. CommonJS 模块的 require() 是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。
  • 3. Node.js 的模块加载方法
    • CommonJS 模块加载 ES6 模块
    • ES6 模块加载 CommonJS 模块

1. 浏览器加载

传统方法

HTML 网页中,浏览器通过<script>标签加载 JavaScript 脚本。

javascript"><!-- 页面内嵌的脚本 -->
<script type="application/javascript">
  // module code
</script>

<!-- 外部脚本 -->
<script type="application/javascript" src="path/to/myModule.js">
</script>

默认情况下,浏览器是同步加载 JavaScript 脚本,也就是渲染引擎遇到<script>标签就会停下来,等到执行完脚本,再继续向下渲染。如果是外部脚本,还必须加入脚本下载的时间。如果脚本体积很大,下载和执行的时间就会很长,因此造成浏览器堵塞,用户会感觉到浏览器“卡死”了,没有任何响应。用户体验感很差。

所以浏览器允许脚本异步加载,下面就是两种异步加载的语法。

javascript"><script src="path/to/myModule.js" defer></script>
<script src="path/to/myModule.js" async></script>

deferasync属性,脚本就会异步加载。渲染引擎遇到这一行命令,就会开始下载外部脚本,但不会等它下载和执行,而是直接执行后面的命令。

deferasync的区别是

  • defer要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行;

  • async一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。

总结defer是“渲染完再执行”,async是“下载完就执行”。另外,如果有多个defer脚本,会按照它们在页面出现的顺序加载,而多个async脚本是不能保证加载顺序的。

加载规则

浏览器加载 ES6 模块,也使用<script>标签,但是要加入type="module"属性。

javascript"><script type="module" src="./foo.js"></script>

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

javascript"><script type="module">
  import utils from "./utils.js";

  // other code
</script>

注意事项:

  • 代码是在模块作用域之中运行,而不是在全局作用域运行。模块内部的顶层变量,外部不可见。
  • 模块脚本自动采用严格模式,不管有没有声明 use strict
  • 模块之中,可以使用import命令加载其他模块(.js后缀不可省略,需要提供绝对 URL 或相对URL),也可以使用export命令输出对外接口。
  • 模块之中,顶层的this关键字返回undefined,而不是指向window。也就是说,在模块顶层使用this关键字,是无意义的。
  • 同一个模块如果加载多次,将只执行一次。

ES6 模块与 CommonJS 模块的差异

1. CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。

javascript">// lib.js
var counter = 3;
function incCounter() {
  counter++;
}
module.exports = {
  counter: counter,
  incCounter: incCounter,
};
javascript">// main.js
var mod = require('./lib');

console.log(mod.counter);  // 3
mod.incCounter();
console.log(mod.counter); // 3
javascript">// lib.js
var counter = 3;
function incCounter() {
  counter++;
}
module.exports = {
  get counter() {
    return counter
  },
  incCounter: incCounter,
};

2. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

3. CommonJS 模块的 require() 是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。

3. Node.js 的模块加载方法

JavaScript 现在有两种模块。一种是 ES6 模块,简称 ESM;另一种是 CommonJS 模块,简称 CJS

CommonJS 模块是 Node.js 专用的,与 ES6 模块不兼容。语法上面,两者最明显的差异是,CommonJS 模块使用require()module.exportsES6 模块使用importexport

Node.js 要求 ES6 模块采用.mjs后缀文件名。也就是说,只要脚本文件里面使用import或者export命令,那么就必须采用.mjs后缀名。Node.js 遇到.mjs文件,就认为它是 ES6 模块,默认启用严格模式,不必在每个模块文件顶部指定"use strict"

如果不希望将后缀名改成.mjs,可以在项目的package.json文件中,指定type字段为module

javascript">{
   "type": "module"
}

一旦设置了以后,该项目的 JS 脚本,就被解释成 ES6 模块。

javascript"># 解释成 ES6 模块
$ node my-app.js

如果这时还要使用 CommonJS 模块,那么需要将 CommonJS 脚本的后缀名都改成.cjs。如果没有type字段,或者type字段为commonjs,则.js脚本会被解释成 CommonJS 模块。

总结.mjs文件总是以 ES6 模块加载,.cjs文件总是以 CommonJS 模块加载,.js文件的加载取决于package.json里面type字段的设置。

CommonJS 模块加载 ES6 模块

CommonJS 的require()命令不能加载 ES6 模块,会报错,只能使用import()这个方法加载。

javascript">// 在 CommonJS 模块中运行
(async () => {
  await import('./my-app.mjs');
})();

require()不支持 ES6 模块的一个原因是,它是同步加载,而 ES6 模块内部可以使用顶层await命令,导致无法被同步加载。

ES6 模块加载 CommonJS 模块

ES6 模块的import命令可以加载CommonJS模块,但是只能整体加载,不能只加载单一的输出项。

javascript">// 正确
import packageMain from 'commonjs-package';

// 报错
import { method } from 'commonjs-package';

这是因为 ES6 模块需要支持静态代码分析,而 CommonJS 模块的输出接口是module.exports,是一个对象,无法被静态分析,所以只能整体加载。

加载单一的输出项,可以写成下面这样。

javascript">import packageMain from 'commonjs-package';
const { method } = packageMain;

还有一种变通的加载方法,就是使用 Node.js 内置的module.createRequire()方法。

javascript">// cjs.cjs
module.exports = 'cjs';

// esm.mjs
import { createRequire } from 'module';

const require = createRequire(import.meta.url);

const cjs = require('./cjs.cjs');
cjs === 'cjs'; // true

上面代码中,ES6 模块通过module.createRequire()方法可以加载 CommonJS 模块。但是,这种写法等于将 ES6CommonJS 混在一起了,所以不建议使用。


http://www.niftyadmin.cn/n/17114.html

相关文章

黑白照片上色软件哪个好用?建议收藏这些软件

大家是否有在家里翻出一些以前的黑白老照片呢&#xff1f;这是由于以前那个时代摄像技术的限制&#xff0c;所以大部分老一辈的照片都是黑白色的。不知道大家是否有过这样的想法&#xff0c;想看看这些经典的黑白照片上色完的样子。其实我们只需要利用一些上色软件就可以了&…

stm32 adc dma

ADC采集电压&#xff0c;使用DMA传输到内存 一&#xff0c;ADC设置 1,Mode。 这里我们使用ADC的独立模式。 2&#xff0c;时钟分频 ADCCLK由 PCLK2分频得到&#xff0c;最大时钟频率36M。ADC 时钟太快&#xff0c;采样可能不够准确&#xff0c;误差大。 3&#xff0c;采样分…

R语言置信区间计算(confidence interval)、计算比例值对应的置信区间、为比例值构建95%执行区间、使用glue包把最终结果以标准格式输出

R语言置信区间计算(confidence interval)、计算比例值对应的置信区间、为比例值构建95%执行区间、使用glue包把最终结果以标准格式输出 目录

【JavaScript】if分支语句

文章目录一、if分支语句if分支语句的语法结构1、单分支2、双分支3、多分支if分支语句的应用总结if的特点一、if分支语句 if分支语句的语法结构 语法结构有三种(单分支&#xff0c;双分支&#xff0c;多分支) 重点内容1&#xff1a;必须熟悉布尔值真假情况(0,‘’,null,undefi…

ElasticSearch 调优

第一部分&#xff1a;调优索引速度 第二部分&#xff1a;调优搜索速度 第三部分&#xff1a;通用的一些建议 英文原文&#xff1a;https://www.elastic.co/guide/en/elasticsearch/reference/current/how-to.html ES发布时带有的默认值&#xff0c;可为es的开箱即用带来很好的…

大数据Kudu(六):Kudu Java Api操作

文章目录 ​​​​​​Kudu Java Api操作 一、​​​​​​​​​​​​​​添加Maven依赖

leetcode每天5题-Day56-动态规划2

目录1. 整数拆分2. 不同的二叉搜索树3.1. 整数拆分 343. 整数拆分-中等 讲解 动规 思路&#xff1a;拆分一个数 n 使之乘积最大&#xff0c;那么一定是拆分m个成近似相同的子数相乘才是最大的。 动规五部曲; ①确定dp数组&#xff08;dp table&#xff09;以及下标的含义 …

【C++】内存空间模型与名称空间(存储持续性、作用域、链接性、变量存储方式)

单独编译 头文件常包含的内容&#xff1a;函数原型、使用#define或const定义的符号常量、结构声明、类声明、模板声明、内联声明。 若头文件中文件名包含在尖括号中&#xff0c;则C编译器将在存储标准头文件的主机系统的文件系统中查找&#xff1b;若文件包含在双引号中&…