您当前的位置:首页 > 电脑百科 > 程序开发 > 编程百科

WEB 前端模块化都有什么?

时间:2019-10-16 10:56:25  来源:  作者:

前言

说到前端模块化,你第一时间能想到的是什么?Webpack?ES6 Module?还有吗?我们一起来看一下下图。

WEB 前端模块化都有什么?

 

相信大伙儿对上图的单词都不陌生,可能用过、看过或者是只是听过。那你能不能用一张图梳理清楚上述所有词汇之间的关系呢?我们日常编写代码的时候,又和他们之间的谁谁谁有关系呢?一、千丝万缕

为了更贴合我们的日常开发场景(前后端分离),我们尝试先从不同平台的维度区分,作为本文的切入点。

1. 根据平台划分

平台 规范 特性 浏览器 AMD、CMD 存在网络瓶颈,使用异步加载 非浏览器 CommonJS 直接操作 IO,同步加载 可以看到我们非常暴力的以是不是浏览器作为划分标准。仔细分析一下,他们之间最大的差异在于其特性上,是否存在瓶颈。 例如说网络性能瓶颈,每个模块的请求都需要发起一次网络请求,并等待资源下载完成后再进行下一步操作,那整个用户体验是非常糟糕的。 根据该场景,我们简化一下,以同步加载和异步加载两个维度进行区分。

特性 规范 同步加载 CommonJS 异步加载 AMD、CMD 2. AMD、CMD 两大规范

先忽略 CommonJS,我们先介绍下,曾经一度盛行的 AMD、CMD 两大规范。

规范 约束条件 代表作 AMD 依赖前置 requirejs CMD 就近依赖 seajs AMD、CMD 提供了封装模块的方法,实现语法上相近,甚至于 requirejs 在后期也默默支持了 CMD 的写法。我们用一个例子,来讲清楚这两个规范之间最大的差异:依赖前置和就近依赖。

AMD:

// hello.js
define(function() {
 console.log('hello init');
 return {
 getMessage: function() {
 return 'hello';
 }
 };
});
// world.js
define(function() {
 console.log('world init');
});
// main
define(['./hello.js', './world.js'], function(hello) {
 return {
 sayHello: function() {
 console.log(hello.getMessage());
 }
 };
});
// 输出
// hello init
// world init
复制代码

CMD:

// hello.js
define(function(require, exports) {
 console.log('hello init');
 exports.getMessage = function() {
 return 'hello';
 };
});
// world.js
define(function(require, exports) {
 console.log('world init');
 exports.getMessage = function() {
 return 'world';
 };
});
// main
define(function(require) {
 var message;
 if (true) {
 message = require('./hello').getMessage();
 } else {
 message = require('./world').getMessage();
 }
});
// 输出
// hello init
复制代码

结论: CMD 的输出结果中,没有打印"world init"。但是,需要注意的是,CMD 没有打印"world init"并是不 world.js 文件没有加载。AMD 与 CMD 都是在页面初始化时加载完成所有模块,唯一的区别就是就近依赖是当模块被 require 时才会触发执行。

requirejs 和 seajs 的具体实现在这里就不展开阐述了,有兴趣的同学可以到官网了解一波,毕竟现在使用 requirejs 和 seajs 的应该很少了吧。

3. CommonJS

回到 CommonJS,写过 NodeJS 的同学对它肯定不会陌生。CommonJS 定义了,一个文件就是一个模块。在 node.js 的实现中,也给每个文件赋予了一个 module 对象,这个对象包括了描述当前模块的所有信息,我们尝试打印 module 对象。

// index.js
console.log(module);
// 输出
{
 id: '/Users/x/Documents/code/demo/index.js',
 exports: {},
 parent: { module }, // 调用该模块的模块,可以根据该属性查找调用链
 filename: '/Users/x/Documents/code/demo/index.js',
 loaded: false,
 children: [...],
 paths: [...]
}
复制代码

也就是说,在 CommonJS 里面,模块是用对象来表示。我们通过“循环加载”的例子进行来加深了解。

// a.js
exports.x = 'a1';
console.log('a.js ', require('./b.js').x);
exports.x = 'a2';
//b.js
exports.x = 'b1';
console.log('b.js ', require('./a.js').x);
exports.x = 'b2';
//main
console.log('index.js', require('./a.js').x);
// 输出
b.js a1
a.js b2
index.js a2
复制代码

我们的理论依据是模块对象,根据该依据我们进行如下分析。

1、 a.js准备加载,在内存中生成module对象moduleA
2、 a.js执行exports.x = 'a1'; 在moduleA的exports属性中添加x
3、 a.js执行console.log('a.js', require('./b.js').x); 检测到require关键字,开始加载b.js,a.js执行暂停
4、 b.js准备加载,在内存中生成module对象moduleB
5、 b.js执行exports.x = 'b1'; 在moduleB的exports属性中添加x
6、 b.js执行console.log('b.js', require('./a.js').x); 检测到require关键字,开始加载a.js,b.js执行暂停
7、 检测到内存中存在a.js的module对象moduleA,于是可以将第6步看成console.log('b.js', moduleA.x); 在第二步中moduleA.x赋值为a1,于是输出b.js, a1
8、 b.js继续执行,exports.x = 'b2',改写moduleBexports的x属性
9、 b.js执行完成,回到a.js,此时同理可以将第3步看成console.log('a.js', modulerB.x); 输出了a.js, b2
10、 a.js继续执行,改写exports.x = 'a2'
11、 输出index.js a2
复制代码

至此,“CommonJS 的模块,是一个对象。”这个概念大伙儿应该能理解吧?

回到这个例子,例子里面还出现了一个保留字 exports。其实 exports 是指向 module.exports 的一个引用。举个例子可以说明他们两个之间的关系。

const myFuns = { a: 1 };
let moduleExports = myFuns;
let myExports = moduleExports;
// moduleExports 重新指向
moduleExports = { b: 2 };
console.log(myExports);
// 输出 {a : 1}
// 也就是说在module.exports被重新复制时,exports与它的关系就gg了。解决方法就是重新指向
myExports = modulerExports;
console.log(myExports);
// 输出 { b: 2 }
复制代码

4. ES6 module

对 ES6 有所了解的同志们应该都清楚,web 前端模块化在 ES6 之前,并不是语言规范,不像是其他语言 JAVAphp 等存在命名空间或者包的概念。上文提及的 AMD、CMD、CommonJS 规范,都是为了基于规范实现的模块化,并非 JavaScript 语法上的支持。 我们先简单的看一个 ES6 模块化写法的例子:

// a.js
export const a = 1;
// b.js
export const b = 2;
// main
import { a } from './a.js';
import { b } from './b.js';
console.log(a, b);
//输出 1 2
复制代码

emmmm,没错,export 保留字看起来是不是和 CommonJS 的 exports 有点像?我们尝试 下从保留字对比 ES6 和 CommonJS。

保留字 CommonJS ES6 require 支持 支持 export / import 不支持 支持 exports / module.exports 支持 不支持 好吧,除了 require 两个都可以用之外,其他实际上还是有明显差别的。那么问题来了,既然 require 两个都可以用,那这两个在 require 使用上,有差异吗?

我们先对比下 ES6 module 和 CommonJS 之间的差异。

模块输出 加载方式 CommonJS 值拷贝 对象 ES6 引用(符号链接) 静态解析 又多了几个新颖的词汇,我们先通过例子来介绍一下值拷贝和引用的区别。

// 值拷贝 vs 引用
// CommonJS
let a = 1;
exports.a = a;
exports.add = () => {
 a++;
};
const { add, a } = require('./a.js');
add();
console.log(a); // 1
// ES6
export const a = 1;
export const add = () => {
 a++;
};
import { a, add } from './a.js';
add();
console.log(a); // 2
// 显而易见CommonJS和ES6之间,值拷贝和引用的区别吧。
复制代码

静态解析,什么是的静态解析呢?区别于 CommonJS 的模块实现,ES6 的模块并不是一个对象,而只是代码集合。也就是说,ES6 不需要和 CommonJS 一样,需要把整个文件加载进去,形成一个对象之后,才能知道自己有什么,而是在编写代码的过程中,代码是什么,它就是什么。

PS:

  1. 目前各个浏览器、node.js 端对 ES6 的模块化支持实际上并不友好,更多实践同志们有兴趣可以自己搞一波。
  2. 在 ES6 中使用 require 字样,静态解析的能力将会丢失!

5. UMD

模块化规范中还有一个 UMD 也不得不提及一下。什么是 UMD 呢?

UMD = AMD + CommonJS
复制代码

没错,UMD 就是这么简单。常用的场景就是当你封装的模块需要适配不同平台(浏览器、node.js),例如你写了一个基于 Date 对象二次封装的,对于时间的处理工具类,你想推广给负责前端页面开发的 A 同学和后台 Node.js 开发的 B 同学使用,你是不是就需要考虑你封装的模块,既能适配 Node.js 的 CommonJS 协议,也能适配前端同学使用的 AMD 协议?

二、工具时代

1. webpack

webpack 兴起之后,什么 AMD、CMD、CommonJS、UMD,似乎都变得不重要了。因为 webpack 的模块化能力真的强。

webpack 在定义模块上,可以支持 CommonJS、AMD 和 ES6 的模块声明方式,换句话说,就是你的模块如果是使用 CommonJS、AMD 或 ES6 的语法写的,webpack 都支持!我们看下例子:

//say-amd.js
define(function() {
 'use strict';
 return {
 sayHello: () => {
 console.log('say hello by AMD');
 }
 };
});
//say-commonjs.js
exports.sayHello = () => {
 console.log('say hello by commonjs');
};
//say-es6.js
export const sayHello = () => {
 console.log('say hello in es6');
};
//main
import { sayHello as sayInAMD } from './say-amd';
import { sayHello as sayInCommonJS } from './say-commonjs';
import { sayHello as sayInES6 } from './say-es6';
sayInAMD();
sayInCommonJS();
sayInES6();
复制代码

不仅如此,webpack 识别了你的模块之后,可以将其打包成 UMD、AMD 等等规范的模块重新输出。例如上文提及到的你需要把 Date 模块封装成 UMD 格式。只需要在 webpack 的 output 中添加 libraryTarget: 'UMD'即可。

2. more...

总结

回到开始我们提出的问题,我们尝试使用一张图汇总上文提及到的一溜模块化相关词汇。

WEB 前端模块化都有什么?

 



Tags:前端模块化   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
前言说到前端模块化,你第一时间能想到的是什么?Webpack?ES6 Module?还有吗?我们一起来看一下下图。 相信大伙儿对上图的单词都不陌生,可能用过、看过或者是只是听过。那你能不能用...【详细内容】
2019-10-16  Tags: 前端模块化  点击:(134)  评论:(0)  加入收藏
▌简易百科推荐
本文分为三个等级自顶向下地分析了glibc中内存分配与回收的过程。本文不过度关注细节,因此只是分别从arena层次、bin层次、chunk层次进行图解,而不涉及有关指针的具体操作。前...【详细内容】
2021-12-28  linux技术栈    Tags:glibc   点击:(3)  评论:(0)  加入收藏
摘 要 (OF作品展示)OF之前介绍了用python实现数据可视化、数据分析及一些小项目,但基本都是后端的知识。想要做一个好看的可视化大屏,我们还要学一些前端的知识(vue),网上有很多比...【详细内容】
2021-12-27  项目与数据管理    Tags:Vue   点击:(2)  评论:(0)  加入收藏
程序是如何被执行的  程序是如何被执行的?许多开发者可能也没法回答这个问题,大多数人更注重的是如何编写程序,却不会太注意编写好的程序是如何被运行,这并不是一个好...【详细内容】
2021-12-23  IT学习日记    Tags:程序   点击:(9)  评论:(0)  加入收藏
阅读收获✔️1. 了解单点登录实现原理✔️2. 掌握快速使用xxl-sso接入单点登录功能一、早期的多系统登录解决方案 单系统登录解决方案的核心是cookie,cookie携带会话id在浏览器...【详细内容】
2021-12-23  程序yuan    Tags:单点登录(   点击:(8)  评论:(0)  加入收藏
下载Eclipse RCP IDE如果你电脑上还没有安装Eclipse,那么请到这里下载对应版本的软件进行安装。具体的安装步骤就不在这赘述了。创建第一个标准Eclipse RCP应用(总共分为六步)1...【详细内容】
2021-12-22  阿福ChrisYuan    Tags:RCP应用   点击:(7)  评论:(0)  加入收藏
今天想简单聊一聊 Token 的 Value Capture,就是币的价值问题。首先说明啊,这个话题包含的内容非常之光,Token 的经济学设计也可以包含诸多问题,所以几乎不可能把这个问题说的清...【详细内容】
2021-12-21  唐少华TSH    Tags:Token   点击:(10)  评论:(0)  加入收藏
实现效果:假如有10条数据,分组展示,默认在当前页面展示4个,点击换一批,从第5个开始继续展示,到最后一组,再重新返回到第一组 data() { return { qList: [], //处理后...【详细内容】
2021-12-17  Mason程    Tags:VUE   点击:(14)  评论:(0)  加入收藏
什么是性能调优?(what) 为什么需要性能调优?(why) 什么时候需要性能调优?(when) 什么地方需要性能调优?(where) 什么时候来进行性能调优?(who) 怎么样进行性能调优?(How) 硬件配...【详细内容】
2021-12-16  软件测试小p    Tags:性能调优   点击:(20)  评论:(0)  加入收藏
Tasker 是一款适用于 Android 设备的高级自动化应用,它可以通过脚本让重复性的操作自动运行,提高效率。 不知道从哪里听说的抖音 app 会导致 OLED 屏幕烧屏。于是就现学现卖,自...【详细内容】
2021-12-15  ITBang    Tags:抖音防烧屏   点击:(25)  评论:(0)  加入收藏
11 月 23 日,Rust Moderation Team(审核团队)在 GitHub 上发布了辞职公告,即刻生效。根据公告,审核团队集体辞职是为了抗议 Rust 核心团队(Core team)在执行社区行为准则和标准上...【详细内容】
2021-12-15  InfoQ    Tags:Rust   点击:(25)  评论:(0)  加入收藏
相关文章
    无相关信息
最新更新
栏目热门
栏目头条