前端开发实战基础——模块

文章目录

概要

模块化,就是将代码拆分成独立的块,各自在代码块中实现各自逻辑,同时自行决定引入外部代码,和决定对外暴露一些可以对外提供的特性功能

模块化包含下面特征:标识符、依赖、加载、入口等,下面具体说明一下

模块标识符

模块化标识符是所有模块通用的概念。模块系统可以通过一个标识符来代表一个模块,然后在需要调用的地方,通过这个标识符来引用对应的模块

标识符可以是一个字符串,在原生的实现的模块系统中可能是模块文件的实际路径。有些模块化系统支持明确声明模块的标识。不管哪种方式,完备的模块化系统一定不存在模块标识符冲突问题

模块依赖

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

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

模块加载

加载模块的概念是由模块依赖产生的。当一个外部模块被指定为依赖的时候,本地模块在准备执行时,外部模块已经完成初始化,等着被执行。

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

入口

相互依赖的模块必须指定一个入口,这个也是代码执行的起点。因为JavaScript是顺序执行的,并且是单线程,所有代码必须有执行的起点

在ES6之前,出现需要的模块化解决方案,现在具体说明一下

CommonJS

CommonJS 规范,用于Node.js 服务端,实现模块化代码组织,其语法不能直接在浏览器运行。

语法

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

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

下面代码展示

javascript 复制代码
//moduleA 文件
var moduleB = require("./moduleB");
module.exports = {
stuff: moduleB.doStuff;
}

//moduleC 引入moduleA 
var moduleA = require("./moduleA");
moduleA.stuff;

moduleA 通过使用模块定义相对路径指定自己对moduleB的依赖

在 moudleC 文件,通过require 引入 moduleA ,并给它起一个别名 moduleA。直接通过moduleA.stuff 便可以访问 moudleA 的staff.

moudle.exports 对象非常灵活,有多种使用方式。如果只想导出一个实体,可以直接module.exports 赋值

通过上面的语法,整个模块就导出一个字符串,可以通过下面的方式使用:

单例

模块加载是单例的,无论在一个模块require() 中被引用多少次模块只会被加载一次,模块第一次请求加载后会被缓存,再次请求模块,都只是取得模块的缓存,如下面的代码所示

AMD

CommonJS 以服务端为目标环境,能够同步地一次性所有依赖都加载到内存,无需考虑异步加载的问题。

异步加载模块定义,AMD( asynchronous Module Definition) 则以浏览器为执行环境,需要考虑网络延迟问题,因此实现了按需依次加载依赖,并在加载完成后,立即执行依赖模块

ADM模块实现的核心是用函数包装模块定义。这样防止声明全局变量,并允许加载器库控制何时加载

包装模块函数是全局的define 的参数,它是由AMD加载器库的实现定义的

语法

AMD 模块可以使用字符串指定自己的依赖,代码示例如下

javascript 复制代码
/ID为' moduleA'的模块定义。moduleA依赖moduleB
//moduleB会异步加载
define('moduleA',['moduleB'].function(moduleB) [
	return (
		stuff: moduleB.doStuff();
	);
});

AMD 也支持require 和 exports 对象,通过他们可以在AMD模块工厂函数内部定义CommonJS 风格的模块。

动态依赖也是通过这种方式支持的:

UMD

为了统一CommonJs 和 AMD 生态系统,通用模块定义(UMD,universal module definition)规范应运而生。

UMD 创建了两个系统都可以使用的模块代码,本质上UMD会检测依赖使用了哪种系统,然后进行适配,并把所有的逻辑包装在一个立即调用的函数表达式中。

核心语法

ES6模块化

ES6 引入了模块规范,使得浏览器支持原生的模块化加载。

下面对 ES6模块规范进行说明

模块标签及定义

浏览器中,我们可以通过在 script 标签中 加入 type="module" 的属性,告诉浏览器这一块代码应该作为哦模块执行,而不是作为传统的脚本执行。模块可以嵌入网页中,也可以作为外部文件引入。

javascript 复制代码
<script type="module">
// 模块代码
</script>
<script type="module src="path/to/myModule,js"></script>

与传统的脚本不同,所有模块都会想 script defer 加载的 脚本一样按顺序执行。 当解析到 script type="moudle" 标签后会立即下载模块文件,但执行会延迟到文档解析完成,因此不会阻塞文档解析

下面演示模块执行顺序

模块导出和导入

ES6模块导出和CommonJs非常相似,通过export 关键字导出模块,ES6支持两种导出,命名导出和默认导出,不同的导出方式的引入方式不同。

命名导出和导入

命名导出,可以定义一个变量,然后通过export 将变量导出,例子如下

或者下面的导出也是可以的

导入方式如下:

通过命名导出的,要使用 import 关键字 和{ }

javascript 复制代码
import {foo} from "./moudleA"
默认导出和导入

默认导出如下:

使用export default 导出foo

javascript 复制代码
const foo = foo'
export default foo

或者使用下面的方式也是可以的

javascript 复制代码
const foo = 'foo'
//等同于export default foo:
export {foo as default };

导入方式如下

javascript 复制代码
import foo from "./foo.js";
//或者使用 as
import {default as foo} from "./foo.js"

如果一个模块有多个命名空间

比如以下

javascript 复制代码
const foo="foo".bar = "bar",baz = "baz"
export {foo,bar,baz}

我们可以使用 * 一次性全部导入,然后通过as 指定一个别名,通过别名访问对应的命名空间。

javascript 复制代码
import * as Foo from "./foo.js"
console.log(Foo.foo)//"foo"
命名导出和默认导出混用

如果一个模块使用了命名空间和默认导出

如下面的示例

javascript 复制代码
export const foo = "foo"
export default const baz ="baz"

导出

javascript 复制代码
import baz,{foo} from "./foo.js"

模块行为

ES6借鉴了 CommonJS 和AMD 很多优秀的特性,下面简单列举一些

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

除此之外,ES6还增加自己的特性

  • ES6模块在严格模式执行
  • ES6不共享命名空间
  • 模块顶级this 是undefined
  • 模块定义的var 不会添加到window
  • ES6模块式异步加载和执行的

小结

  • CommonJS 用于Node 服务端模块化,采取module.exports 对象 + require 导出导入
  • AMD 异步加载模块化,可以用于浏览器解决脚本异步加载,通过全局的define 函数定义模块
  • UMD 实现了AMD 和CommonJs的集成,本质上也是检测并适配。
  • ES6 增加了模块化方案,采用export + import 导出导入,还可以支持默认导出
相关推荐
虾球xz12 分钟前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇18 分钟前
HTML常用表格与标签
前端·html
疯狂的沙粒21 分钟前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员37 分钟前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐39 分钟前
前端图像处理(一)
前端
程序猿阿伟1 小时前
《智能指针频繁创建销毁:程序性能的“隐形杀手”》
java·开发语言·前端
疯狂的沙粒1 小时前
对 TypeScript 中函数如何更好的理解及使用?与 JavaScript 函数有哪些区别?
前端·javascript·typescript
瑞雨溪1 小时前
AJAX的基本使用
前端·javascript·ajax
力透键背1 小时前
display: none和visibility: hidden的区别
开发语言·前端·javascript
程楠楠&M1 小时前
node.js第三方Express 框架
前端·javascript·node.js·express