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


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

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

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

知识点:

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

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

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

模块加载

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

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

入口

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

在这里插入图片描述

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

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';

工作者模块略

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值