欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JavaScript高级程序设计第四版学习--第二十六章

程序员文章站 2022-07-07 18:58:27
...

title: JavaScript高级程序设计第四版学习–第二十六章
date: 2021-5-31 15:50:31
author: Xilong88
tags: JavaScript

本章内容
理解模块模式
凑合的模块系统
使用前ES6模块加载器
使用ES6模块
可能出现的面试题:
1.ES6模块的基本使用方法
2.判断export和import语句是否合法的程序题

凑合的模块系统 略
ES6之前的模块加载器 略

知识点:

1.理解模块模式
把逻辑分块,各自封装,相互独立,每个块自行决定对外暴露什么,同时自行决定引入执行哪些外部代码。

模块标识符
模块标识符是所有模块系统通用的概念。

模块依赖
模块系统的核心是管理依赖。指定依赖的模块与周围的环境会达成一种契约。本地模块向模块系统声明一组外部模块(依赖),这些外部模块对于当前模块正常运行是必需的。模块系统检视这些依赖,进而保证这
些外部模块能够被加载并在本地模块运行时初始化所有依赖。

模块加载

加载模块的概念派生自依赖契约。当一个外部模块被指定为依赖时,本地模块期望在执行它时,依赖已准备好并已初始化。

加载模块涉及执行其中的代码,但必须是在所有依赖都加载并执行之后。

入口

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

JavaScript高级程序设计第四版学习--第二十六章

模块加载是“阻塞的”,这意味着前置操作必须完成才能执行后续操作。每个模块在自己的代码到达浏览器之后完成加载,此时其依赖已经加载并初始化。

2.异步依赖

因为JavaScript可以异步执行,所以如果能按需加载就好了。换句话说,可以让JavaScript通知模块系统在必要时加载新模块,并在模块加载完成后提供回调。

动态依赖

有些模块系统要求开发者在模块开始列出所有依赖,而有些模块系统则允许开发者在程序结构中动态添加依赖。

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

在这个模块中,是否加载moduleA 是运行时确定的。加载moduleA 时可能是阻塞的,也可能导致执行,且只有模块加载后才会继续。无论怎样,模块内部的代码在moduleA 加载前都不能执行,因为moduleA 的存在是后续模块行为正确的关键。

3.循环依赖
模块之间相互依赖,但是指定了入口后,每个依赖都只被加载一次。

4.使用ES6模块

模块标签及定义

ECMAScript 6模块是作为一整块JavaScript代码而存在的。带有type=“module” 属性的<script> 标签会告诉浏览器相关代码应该作为模块执行,而不是作为传统的脚本执行。模块可以嵌入在网页中,也可以作为外部文件引入:

<script type="module">
  // 模块代码
</script>
<script type="module" src="path/to/myModule.js"></script>

与传统脚本不同,所有模块都会像<script defer> 加载的脚本一样按顺序执行。解析到<script type="module"> 标签后会立即下载模块文件,但执行会延迟到文档解析完成。无论对嵌入的模块代码,还是引入的外部模块文件,都是这样。

<script type="module"> 在页面中
出现的顺序就是它们执行的顺序。与<script defer> 一样,修改模块标签的位置,无论是在<head> 还是在<body> 中,只会影响文件什么时
候加载,而不会影响模块什么时候加载。

<!-- 第二个执行 -->
<script type="module"></script>
<!-- 第三个执行 -->
<script type="module"></script>
<!-- 第一个执行 -->
<script></script>
<!-- 第二个执行 -->
<script type="module" src="module.js"></script>
<!-- 第三个执行 -->
<script type="module" src="module.js"></script>
<!-- 第一个执行 -->

<script><script>

也可以给模块标签添加async 属性。这样影响就是双重的:不仅模块执行顺序不再与<script> 标签在页面中的顺序绑定,模块也不会等待文档完成解析才执行。不过,入口模块仍必须等待其依赖加载完成。

5.模块行为

模块代码只在加载后执行。
模块只能加载一次。
模块是单例。
模块可以定义公共接口,其他模块可以基于这个公共接口观察和交
互。
模块可以请求加载其他模块。
支持循环依赖。

ES6模块系统也增加了一些新行为。

ES6模块默认在严格模式下执行。
ES6模块不共享全局命名空间。
模块*this 的值是undefined (常规脚本中是window )。
模块中的var 声明不会添加到window 对象。
ES6模块是异步加载和执行的。

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

export 关键字

// 允许
export ...
// 不允许
if (condition) {
   export ...
}

export 语句甚至可以出现在它要导出的值之前:

// 允许
const foo = 'foo';
export { foo };
// 允许
export const foo = 'foo';
// 允许,但应该避免
export { foo };
const foo = 'foo';

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

export const foo = 'foo';

变量声明跟导出可以不在一行。可以在export 子句中执行声明并将标识符导出到模块的其他地方:

const foo = 'foo';
export { foo };

导出时也可以提供别名,别名必须在export 子句的大括号语法中指定。因此,声明值、导出值和为导出值提供别名不能在一行完成。在下面的例子中,导入这个模块的外部模块可以使用myFoo 访问导出的值:

const foo = 'foo';
export { foo as myFoo };
const foo = 'foo';
const bar = 'bar';
const baz = 'baz';
export { foo, bar as myBar, baz };

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

const foo = 'foo';
export default foo;
const foo = 'foo';
const bar = 'bar';
export { bar };
export default foo;
const foo = 'foo';
const bar = 'bar';
export { foo as default, bar };

例子:

// 命名行内导出
export const baz = 'baz';
export const foo = 'foo', bar = 'bar';
export function foo() {}
export function* foo() {}
export class Foo {}
// 命名子句导出
export { foo };
export { foo, bar };
export { foo as myFoo, bar };

// 默认导出
export default 'foo';
export default 123;
export default /[a-z]*/;
export default { foo: 'foo' };
export { foo, bar as default };
export default foo
export default function() {}
export default function foo() {}
export default function*() {}
export default class {}
// 会导致错误的不同形式:
// 行内默认导出中不能出现变量声明
export default const foo = 'bar';
// 只有标识符可以出现在export子句中
export { 123 as foo }
// 别名只能在export子句中出现
export const foo = 'foo' as myFoo;

7.模块导入

// 允许
import ...
// 不允许
if (condition) {
   import ...
}
// 允许
import { foo } from './fooModule.js';
console.log(foo); // 'foo'
// 允许,但应该避免
console.log(foo); // 'foo'
import { foo } from './fooModule.js';
// 解析为/components/bar.js
import ... from './bar.js';
// 解析为/bar.js
import ... from '../bar.js';
// 解析为/bar.js
import ... from '/bar.js';

导入对模块而言是只读的,实际上相当于const 声明的变量。在使用*执行批量导入时,赋值给别名的命名导出就好像使用Object.freeze() 冻结过一样。直接修改导出的值是不可能的,但可以修改导出对象的属性。同样,也不能给导出的集合添加或删除导出的属性。

要修改导出的值,必须使用有内部变量和属性访问权限的导出方法。

import foo, * as Foo './foo.js';
foo = 'foo';     // 错误
Foo.foo = 'foo'; // 错误
foo.bar = 'bar'; // 允许

命名导出和默认导出的区别也反映在它们的导入上。

命名导出可以使用* 批量获取并赋值给保存导出集合的别名,而无须列出每个标识符:

const foo = 'foo', bar = 'bar', baz = 'baz';
export { foo, bar, baz }
import * as Foo from './foo.js';
console.log(Foo.foo); // foo
console.log(Foo.bar); // bar
console.log(Foo.baz); // baz

默认导出就好像整个模块就是导出的值一样。可以使用default 关键字并提供别名来导入。也可以不使用大括号,此时指定的标识符就是默认导出的别名:

// 等效
import { default as foo } from './foo.js';
import foo from './foo.js';

8.模块转移导出

如果想把一个模块的所有命名导出集中在
一块,可以像下面这样在bar.js中使用* 导出:
foo.js

export const baz = 'origin:foo';

bar.js

export * from './foo.js';
export const baz = 'origin:bar';

main.js

import { baz } from './bar.js';
console.log(baz); // origin:bar

此外也可以明确列出要从外部模块转移本地导出的值。该语法支持使用别名:

export { foo, bar as myBar } from './foo.js';

类似地,外部模块的默认导出可以重用为当前模块的默认导出:

export { default } from './foo.js';

这样不会复制导出的值,只是把导入的引用传给了原始模块。在原始模块中,导入的值仍然是可用的,与修改导入相关的限制也适用于再次导出的导入。

在重新导出时,还可以在导入模块修改命名或默认导出的角色。比如,可以像下面这样将命名导出指定为默认导出:

export { foo as default } from './foo.js';

工作者模块略