1 理解模块模式

将代码拆分成独立的块,然后再把这些块连接起来可以通过模块模式来实现。这种模式背后的思想很简单:把逻辑分块,各自封装,相互独立,每个块自行决定对外暴露什么,同时自行决定引入执行哪些外部代码。不同的实现和特性让这些基本的概念变得有点复杂,但这个基本的思想是所有 JavaScript模块系统的基础。

1.1 模块标识符

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

有的模块系统支持明确声明模块的标识,还有的模块系统会隐式地使用文件名作为模块标识符。不管怎样,完善的模块系统一定不会存在模块标识冲突的问题,且系统中的任何模块都应该能够无歧义地引用其他模块。

将模块标识符解析为实际模块的过程要根据模块系统对标识符的实现。原生浏览器模块标识符必须提供实际 JavaScript 文件的路径。除了文件路径,Node.js 还会搜索 node_modules 目录,用标识符去匹配包含 index.js 的目录。

1.2 模块依赖

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

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

1.3 模块加载

待补充 798

3 使用ES6之前的模块加载器

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

3.1 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();
};

moduleA 通过使用模块定义的相对路径来指定自己对 moduleB 的依赖。什么是“模块定义”,以及如何将字符串解析为模块,完全取决于模块系统的实现。比如在 Node.js 中,模块标识符可能指向文件,也可能指向包含 index.js 文件的目录。

请求模块会加载相应模块,而把模块赋值给变量也非常常见,但赋值给变量不是必需的。调用require()意味着模块会原封不动地加载进来:

console.log('moduleA');
require('./moduleA'); // "moduleA"

无论一个模块在 require()中被引用多少次,模块永远是单例。在下面的例子中,moduleA 只会被打印一次。这是因为无论请求多少次,moduleA 只会被加载一次。

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

模块第一次加载后会被缓存,后续加载会取得缓存的模块(如下代码所示)。模块加载顺序由依赖图决定。

console.log('moduleA');
require('./moduleA');
require('./moduleB'); // "moduleA"
require('./moduleA');

4 使用ES6模块

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

4.1 模块标签及定义

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

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

即使与常规 JavaScript 文件处理方式不同,JavaScript 模块文件也没有专门的内容类型。

待补充 808

4.4 模块导出

ES6 模块支持两种导出:命名导出和默认导出,不同的导出方式对应不同的导入方式。

export 关键字用于声明一个值为命名导出。导出语句必须在模块顶级,不能嵌套在某个块中:

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

导出值对模块内部 JavaScript 的执行没有直接影响,因此 export 语句与导出值的相对位置或者export 关键字在模块中出现的顺序没有限制。export 语句甚至可以出现在它要导出的值之前:

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

命名导出(named export)就好像模块是被导出值的容器。行内命名导出,顾名思义,可以在同一行执行变量声明。下面展示了一个声明变量同时又导出变量的例子。外部模块可以导入这个模块,而foo 将成为这个导入模块的一个属性:

export const foo = 'foo';

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

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

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

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

因为 ES6 命名导出可以将模块作为容器,所以可以在一个模块中声明多个命名导出。导出的值可以在导出语句中声明,也可以在导出之前声明:

export const foo = 'foo';
export const bar = 'bar';
export const baz = 'baz';

考虑到导出多个值是常见的操作,ES6 模块也支持对导出声明分组,可以同时为部分或全部导出值指定别名:

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

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

下面的例子定义了一个默认导出,外部模块可以导入这个模块,而这个模块本身就是 foo 的值:

const foo = 'foo';
export default foo;

另外,ES6 模块系统会识别作为别名提供的 default 关键字。此时,虽然对应的值是使用命名语法导出的,实际上则会成为默认导出:

const foo = 'foo'; // 等同于 export default foo;
export { foo as default };

因为命名导出和默认导出不会冲突,所以 ES6 支持在一个模块中同时定义这两种导出:

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

这两个 export 语句可以组合为一行:

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

ES6 规范对不同形式的 export 语句中可以使用什么不可以使用什么规定了限制。某些形式允许声明和赋值,某些形式只允许表达式,而某些形式则只允许简单标识符。注意,有的形式使用了分号,有的则没有:

// 命名行内导出
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;

4.5 模块导入

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

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

import 语句被提升到模块顶部。因此,与 export 关键字类似,import 语句与使用导入值的语句的相对位置并不重要。不过,还是推荐把导入语句放在模块顶部。

// 允许
import { foo } from './fooModule.js';
console.log(foo); // 'foo' // 允许,但应该避免
console.log(foo); // 'foo'
import { foo } from './fooModule.js';

模块标识符可以是相对于当前模块的相对路径,也可以是指向模块文件的绝对路径。它必须是纯字符串,不能是动态计算的结果。例如,不能是拼接的字符串。

如果在浏览器中通过标识符原生加载模块,则文件必须带有.js 扩展名,不然可能无法正确解析。不过,如果是通过构建工具或第三方模块加载器打包或解析的 ES6 模块,则可能不需要包含文件扩展名。

// 解析为/components/bar.js
import ... from './bar.js';// 解析为/bar.js
import ... from '../bar.js'; // 解析为/bar.js
import ... from '/bar.js';

不是必须通过导出的成员才能导入模块。如果不需要模块的特定导出,但仍想加载和执行模块以利用其副作用,可以只通过路径加载它:

import './foo.js';

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

import foo, * as Foo from './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

要指名导入,需要把标识符放在 import 子句中。使用 import 子句可以为导入的值指定别名:

import { foo, bar, baz as myBaz } from './foo.js'; console.log(foo); // foo
console.log(bar); // bar
console.log(myBaz); // baz

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

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

如果模块同时导出了命名导出和默认导出,则可以在 import 语句中同时取得它们。可以依次列出特定导出的标识符来取得,也可以使用*来取得:

import foo, { bar, baz } from './foo.js';
import { default as foo, bar, baz } from './foo.js';
import foo, * as Foo from './foo.js';

26 JavaScript模块相关推荐

  1. javascript模块_JavaScript模块第2部分:模块捆绑

    javascript模块 by Preethi Kasireddy 通过Preethi Kasireddy JavaScript模块第2部分:模块捆绑 (JavaScript Modules Part ...

  2. Javascript模块规范(CommonJS规范AMD规范)

    Javascript模块化编程(AMD&CommonJS) 前端模块化开发的价值:https://github.com/seajs/seajs/issues/547 模块的写法 查看 AMD规 ...

  3. 判断字符串 正则_(重学前端 - JavaScript(模块一)) 14、引用类型之 RegExp (正则)(详述)...

    上一篇文章介绍了 JavaScript 中的 Date 类型,从地理方面的原理知识开始入手,如果大家认真看过上一篇文章,相信 JavaScript 中的 Date 类型已经难不住大家了!!! 但是今天 ...

  4. 推荐好用的JavaScript模块

    2019独角兽企业重金招聘Python工程师标准>>> 译者按: 作者将自己常用的JavaScript模块分享给大家. 原文:? JavaScript Modules Worth U ...

  5. javascript模块_JavaScript中的模块

    javascript模块 JavaScript模块 (JavaScript Modules) One of the key features of programming fundamentals i ...

  6. 导出库的版本_了解 JavaScript 模块系统基础知识,搭建自己的库

    我想很多"前端工程师"都听过说过 "JavaScript 模块",那你们都知道如何处理它,以及它在日常工作中如何发挥作用吗? JS 模块系统到底是什么呢 随着 ...

  7. 田永强:优秀的JavaScript模块是怎样炼成的

    转自:http://blog.jobbole.com/26101/ 引言:如今的JavaScript已经是Web上最流行的语言,没有之一.从GitHub上的语言排行榜https://github.co ...

  8. JavaScript模块机制

    commonJS规范 模块引用 你可以通过require来引入你所需要的模块,这个方法接收模块标识,以此引入一个模块的API到当前上下文中. const fs = require('fs'); 模块定 ...

  9. 优秀的JavaScript模块是怎样炼成的

    引言:如今的JavaScript已经是Web上最流行的语言,没有之一.从Github上的语言排行榜https://github.com/languages上即可看出,也是如今最为活跃的开源社区.随着N ...

  10. 【转】优秀的JavaScript模块是怎样炼成的

    作者 田永强 发布于 2012年8月27日 原文[url]http://www.infoq.com/cn/articles/how-to-create-great-js-module[/url] 引言 ...

最新文章

  1. 安装Properties Editor插件,解决XXX.properties文件中文乱码的问题
  2. OpenWrite 赞助平台全流程说明
  3. 解决VS2008 开发Windows Mobile 项目生成速度慢的问题
  4. 2019牛客暑期多校训练营(第九场) E All men are brothers
  5. redis 数据操作命令汇总(string hash list set zset)
  6. javscript对cookie的操作,以及封装
  7. php explain type等级,mysql中explain分析sql详解
  8. Python中的分组函数(groupby、itertools)
  9. 我们奋斗着并将持续奋斗 ----暨清华D-Lab创新基地揭牌仪式
  10. HDU1570 A C【水题】
  11. Python语言基础
  12. 用python处理DEA模型--CCR
  13. oracle中的存储过程和存储函数的区别
  14. Flash cs3教程-传统数字动画
  15. react异常 Each child in a list should have a unique “key” prop
  16. 关于 Kubernetes中Pod健康检测和服务可用性检查的一些笔记(LivenessProbe+ReadinessProbe)
  17. average函数例子c语言数组,average函数(average函数使用例子)
  18. 小程序用哪个服务器好,小程序用什么服务器好
  19. 深入理解卷积网络的卷积
  20. 即将到来的量子计算时代,其商业应用价值在哪里?

热门文章

  1. linux以http方式搭建git服务器
  2. 解决PHP不打印任何东西浏览器输出字符串
  3. 详解 Flink 指标、监控与告警
  4. Android 中的Binder跨进程通信机制与AIDL
  5. html怎么设置动画保持,html – 如何使用CSS3为td列可见性设置动画
  6. python---用python实现冒泡排序
  7. c语言加密字母向右移两位,C语言二进制除法用左右移位来表示
  8. 徐州计算机专业技校,2021徐州所有的中专技校职高排名
  9. python将多重列表中的成绩按大小输出_Python编程从入门到实践-连载5(字 典)
  10. windows关于python虚拟机的设置以及安装使用virtualenv