浅析前端工程化中的一部曲——模块化
在日益复杂和多元的 Web 业务背景下,前端工程化经常会被提及。工程化的目的是高性能、稳定性、可用性、可维护性、高效协同,只要是以这几个角度为目标所做的操作,都可成为工程化的一部分。工程化是软件工程中的一种思想,当下的工程化可以分为四个方面:模块化、组件化、规范化、自动化。接下来,此文会浅析其中的一个部分——模块化。
一、什么是模块化
模块化是指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程,每个模块完成特定的子功能,所有的模块按某种方法组装起来,成为一个整体,从而完成整个系统所要求的的功能。
块的内部数据与实现是私有的,只是向外部暴露了一些接口和方法与外部其他模块通信。
二、为什么需要模块化
在早期的网页中,一些页面和样式都很简单,有极少交互行为,一个页面也不会依赖很多文件,所以逻辑代码会很少。但随着 Web 技术的发展,各种交互以及新技术使网页越来越丰富,逐渐地我们前端工程师的任务也越来越重,前端代码急速上涨、复杂度也在逐步升高,越来越多的业务逻辑和交互都放在 Web 层实现,代码一多,各种命名冲突、代码冗余、文件间依赖变大等等一系列问题都会浮现,而且也会导致后期难以维护。
在这些问题上,有很多语言已经有了很多实践经验,那就是模块化。因为小的、组织良好的代码远比庞大的代码更易理解和维护,于是前端也开启了模块化历程。
三、JS 模块化规范
CommonJS规范
2009年,美国程序员 Ryan Dahl 以 CommonJS 规范为基础创造了 node.js 项目,将 JS 语言用于服务器端编程,为前端奠基,此后,nodejs 就成为了 CommonJS 的代名词。
1. 概述
CommonJS 规范中规定每个文件就是一个独立的模块,有自己的作用域,模块的变量、函数、类都是私有的,对其他文件不可见。在服务器端,模块的加载是运行时同步加载的;在浏览器端,模块需要提前编译打包处理。
2. 特点
1)所有代码都运行在模块作用域。不会污染全局作用域。
2)模块可以多次加载,但只会在第一次加载时运行一次,然后运行结果就被缓存,以后再加载,就直接读取缓存结果,要想让模块再次运行,必须清除缓存。
3)模块加载的顺序,按照其在代码中出现的顺序。
3. 基本语法
外部想要调用,必须使用 module.exports 主动暴露,而在另一个文件中引用则需要使用 require(),如下:
// moduleA.js
var a = 1;
var b = 2;
var add = function() {return a + b
}module.exports.a = a;
module.exports.b = b;
module.exports.add = add;// 引用
var moduleA = require('./moduleA.js');console.log(moduleA.a)  // 1
console.log(moduleA.b)  // 2
console.log(moduleA.add(a,b))  // 3 
4. 加载机制
CommonJS 模块的加载机制是:输入的是被输出的值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值了。
5. 总结
CommonJS 是模块化的社区标准,而 Nodejs 就是 CommonJS 模块化规范的实现,它对模块的加载时同步的,也就是说,只有引入的模块加载完成,才会执行后面的操作,在 Node 服务端应用当中,模块一般存在本地,加载较快,同步问题不大,在浏览器中就不太合适了,如果一个很大的项目,所有的模块都要同步加载,那可想而知,体验感是极差的,所以还需要异步模块化方案。
AMD规范
AMD(异步模块定义)是专门为浏览器环境设计的,是非同步加载模块,允许指定回调函数。
1. 基本语法
define(id?: String, dependencies?: String[], factory: Function|Object)
- id:模块的名字,字符串类型,可选参数;
 - dependencies:依赖的模块列表,一个字符串数组,可选参数。每个依赖的模块的输出将作为参数一次传入 factory 中,如果没有指定 dependencies,那它的默认值是 ["require", "exports", "module"];
 - factory:包裹了模块的具体实现,可以为函数或对象,如果是函数,返回值就是模块的输出接口或者值;
 
使用 define() 定义暴露模块,引入模块需要使用 require(),如下:
// 定义没有依赖模块
define(function() {return 模块
})// 定义有依赖模块
define(['module1', 'module2'], function(m1, m2) {return 模块
})// 引入使用模块
require(['module1', 'module2'], function(m1, m2) {使用m1/m2
})
 
2. RequireJS
RequireJS 是一个遵守 AMD 规范的工具库,用于客户端的模块管理。它就是通过 define 方法将代码定义为模块,通过 require 方法,实现代码的模块加载,使用时需要下载和导入,也就是说在浏览器中想要使用 AMD 规范时先在页面中引入 require.js 就可以了。
3. 总结
AMD 模块定义的方法非常清晰,不会污染全局环境,能够清楚地显示依赖关系。AMD 模式可以用于浏览器环境,并且允许非同步加载模块,也可以根据需要动态加载模块,RequireJS 就是 AMD 的标准化实现。
CMD规范
CMD 是 SeaJS 在推广过程中对模块定义的规范化产出,而 CMD 规范以及 SeaJS 在国内曾经十分被推崇,原因不只是因为它足够简单方便,更是因为 SeaJS 的作者是阿里的玉伯大佬所写。
1. 概述
CMD 规范专门用于浏览器端,模块的加载是异步的,模块使用时才会加载执行。CMD 规范整合了CommonJS 和 AMD 规范的特点。在Sea.js 中,所有 JavaScript 模块都遵循 CMD 模块定义规范。
2. 基本语法
define(factory: Function|Object|String)
在 CMD 规范中,一个模块就是一个文件,define 是一个全局函数,用来定义模块。define 接收 factory 参数,它可以是一个函数,也可以是一个对象或字符串。
1)参数为函数时,如下:
// 定义没有依赖的模块
define(function(require, exports, module) {exports.xxx = value;module.exports = value;
})// 定义有依赖的模块
define(function(require, exports, module) {// 引入依赖模块(同步)var module1 = require('./module1')// 引入依赖模块(异步)require.async('./module2', function(m2) {...})// 引入依赖模块(条件)if(status) {var m3 = require('./module3')}// 暴露模块exports.xxx = value
})// 引入模块
define(function(require) {var m1 = require('./module1')var m2 = require('./module2')m1.show()m2.show()
}) 
- require :是一个方法,接收模块标识作为唯一参数,用来获取其他模块提供的接口;
 - exports:是一个对象,用来向外提供模块接口;
 - module:是一个对象,上面存储了与当前模块相关联的一些属性和方法;
 
2)参数为对象和字符串时,表示模块的接口就是该对象和字符串,如下:
// factory为JSON数据对象
define({"name": "bao"});// factory为字符串模板
define('my name is {{name}}'); 
3. 核心实现
对于 CMD 规范下的 SeaJS,同 AMD 规范下的 RequireJS 一样,都是浏览器端模块加载器,两者很相似,但又有明显不同,CMD 与 AMD 的区别是 AMD 推崇依赖前置,而 CMD 推崇依赖就近。
CMD 推崇尽可能的懒加载,也称为延迟加载,即在需要的时候才加载。对于依赖模块,AMD 是提前执行,CMD 是延迟加载,两者执行的方式不同,AMD 执行过程中会将所有依赖前置执行,也就是在自己的代码逻辑开始前全部执行,而 CMD 如果 require 引入了整个逻辑并未使用这个依赖或未执行到逻辑使用它的地方前是不会执行的。
ES6模块化
2015年6月,ESMAScript2015 也就是我们所说的 ES6 发布了,JS 终于在语言标准的层面上,实现了模块功能,设计思想是尽量的静态化,使得在编译时就能确定模块的依赖关系,以及其输入和输出的变量,不像 CommonJS、AMD 之类的需要在运行时才能确定,成为浏览器和服务器通用的模块解决方案。
所以说在 ES6 之前 JS 是没有官方的模块机制的,ES6 在语言标准的层面上,实现了模块化功能,而且实现的相当简单,旨在成为浏览器和服务器通用的模块化解决方案。
1. 基本语法
export 命令用于规定模块的对外接口,import 命令可以输入其他模块提供的功能。export 可以导出的是对象中包含多个属性、方法,提供的 export default 命令只能导出一个可以不具名的函数。
1)使用 export 导出,import 导入,如下:
// 定义模块module1
var count = 0;
var add = function(a, b) {return a + b;
}export {count, add};// 引用模块
import {count, add} from './module1';function test() {return add(1, count);
}
 
2)使用 import 命令导入时,需要知道所要加载的变量名或函数名,否则无法加载。export default 命令可以为模块默认输出,如下:
// 导出module2
export default function() {console.log('this is a msg');
}// 引入
import msgFun from './module2';msgFun(); // 'this is a msg' 
2. ES6 模块与 CommonJS 模块的差异
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用;
 - CommonJS 模块是运行时加载,ES6 模块是编译时输出接口;
 
第二个差异是因为 CommonJS 加载的是一个对象,该对象只有在脚本运行完才会生成;而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
ES6 模块运行机制与 CommonJS 运行机制不一样。js 引擎对脚本静态分析的时候,遇到模块加载指令后会生成一个只读引用,等到脚本真正执行的时候,才会通过引用模块中获取值,在引用到执行的过程中,模块中的值发生变化,导入到这里也会跟着发生变化。ES6 模块是动态引入的,并不会缓存值,模块里总是绑定其所在模块。
四、总结
对于 JS 模块化,上述方案都在解决几个同样的问题:
- 全局变量的污染
 - 命名冲突
 - 繁琐的文件依赖
 
不同的模块化手段都在致力于解决这些问题。前两个问题其实很好解决,使用闭包配合立即执行函数就可以实现,难点在于文件依赖关系的梳理以及加载。CommonJS 在服务端使用 fs 模块同步读取文件,而在浏览器中,不管是使用 AMD 规范的 RequireJS 还是 CMD 规范的 SeaJS,其实都是使用动态创建 script 标签方式加载,在依赖加载完毕之后再执行。
ESM 作为语言标准层面的模块化方案,不需要我们额外引入用于模块化的三方包,完全可以取代 CommonJS 和 AMD 等规范,成为浏览器和服务器通用的模块解决方案。抛开兼容问题,绝对是最好的选择,也是未来趋势,这点在 Vite 上就足以证明。
相关文章:
浅析前端工程化中的一部曲——模块化
在日益复杂和多元的 Web 业务背景下,前端工程化经常会被提及。工程化的目的是高性能、稳定性、可用性、可维护性、高效协同,只要是以这几个角度为目标所做的操作,都可成为工程化的一部分。工程化是软件工程中的一种思想,当下的工程…...
新版bing(集成ChatGPT)申请通过后在谷歌浏览器(Chrome)上的使用方法
大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,科大讯飞比赛第三名,CCF比赛第四名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…...
Time-distributed 的理解
前言 今天看到论文中用到 Time-distributed CNN,第一次见到 Time-distributed,不理解是什么含义,看到代码实现也很懵。不管什么网络结构,外面都能套一个TimeDistributed。看了几个博客,还是不明白,问了问C…...
matlab 计算矩阵的Moore-Penrose 伪逆
目录 一、Moore-Penrose 伪逆1、主要函数2、输入输出参数二、代码示例使用伪逆求解线性方程组一、Moore-Penrose 伪逆 Moore-Penrose 伪逆是一种矩阵,可在不存在逆矩阵的情况下作为逆矩阵的部分替代。此矩阵常被用于求解没有唯一解或有许多解的线性方程组。 对于任何矩阵…...
简历制作方面的经验与建议
专栏推荐:2023 数字IC设计秋招复盘——数十家公司笔试题、面试实录 专栏首页:2023 数字IC设计秋招复盘——数十家公司笔试题、面试实录 专栏内容: 笔试复盘篇 2023秋招过程中整理的笔试题,来源包括我自己求职笔试以及整理其他同学的笔试。包含华为、中兴、联发科、AMD、大…...
C语言--static、const、volatile关键字
Static static修饰局部变量改变了变量的生命周期,让静态局部变量出了作用域依然存在,到程序结束,生命周期才结束。 static 修饰局部变量 改变局部变量的生命周期,本质上是改变了局部变量的存储位置,让局部变量不再是…...
Rust学习入门--【18】Rust结构体
系列文章目录 Rust 语言是一种高效、可靠的通用高级语言,效率可以媲美 C / C 。本系列文件记录博主自学Rust的过程。欢迎大家一同学习。 Rust学习入门–【1】引言 Rust学习入门–【2】Rust 开发环境配置 Rust学习入门–【3】Cargo介绍 Rust学习入门–【4】Rust 输…...
LeetCode142 环形链表Ⅱ
题目: 给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评…...
JavaScript刷LeetCode拿offer-高频链表题
首先需要了解链表的概念 先把 next 记录下来 无论是插入,删除,还是翻转等等操作,先把 next 指针用临时变量保存起来,这可以解决 90% 重组链表中指向出错的问题, 如果不知道什么时候需要用到守卫,那就都用…...
linux系统编程2--网络编程
在linux系统编程中网络编程是使用socket(套接字),socket这个词可以表示很多概念:在TCP/IP协议中,“IP地址TCP或UDP端口号”唯一标识网络通讯中的一个进程,“IP地址端口号”就称为socket。在TCP协议中&#…...
Allegro如何重命名光绘操作指导
Allegro如何重命名光绘操作指导 在做PCB设计的时候,光绘设置是输出生产文件必要的流程,设置好光绘之后,如何对光绘重新命名,如下图 如何把L1改成TOP,L6改成BOTTOM,具体操作步骤如下 点击Manufacture选择Artwork...
[PMLR 2018] Hyperbolic entailment cones for learning hierarchical embeddings
Contents IntroductionEntailment Cones in the Poincar BallConvex cones in a complete Riemannian manifoldAngular cones in the Poincar ballfour intuitive propertiesClosed form expression of the optimal ψ \psi...
2023春季露营投影怎么选?轻薄投影极米Z6X Pro值得推荐
近年来,露营经济在多重因素的共同助推下快速发展,精致露营的攻略开始占据小红书、微博、朋友圈等各类社交平台,吸引着更多用户种草并加入到露营大军中,而露营经济的强势“破圈”给家用智能投影带来了更多的发展契机。凭借着小巧的…...
收藏,核心期刊的投稿、审稿、出刊流程详解
学术期刊论文(核心和普刊)的发表流程总的来说其实是一样的,整个流程包括:1写作-2选择刊物-3投稿-4审稿-5返修或拒稿-6录用-7出刊-8上网检索。 其中1和2其实顺序是可以调换的,可以选择好刊物再写作,根据刊物…...
JVM类加载子系统
1、类加载子系统在内存结构中所处的位置通过内存结构图,我们先知道类加载子系统所处的位置,做到心中有图。2、类加载器作用类加载器子系统负责从文件系统或者网络中加载Class文件,class文件在文件开头有特定的文件标识。ClassLoader只负责cla…...
摄像头的镜头的几个知识点
1、镜头的组成及镜片的固定方式 摄像头的镜头结构主要分为镜身,透镜,变焦环,对焦环,光圈叶片,部分还有防抖系统.其中最重要的就是透镜,也叫镜片。镜片的主要原料是光学玻璃,玻璃&…...
分布式-分布式存储笔记
读写分离 什么时候需要读写分离 互联网大部分业务场景都是读多写少的,读和写的请求对比可能差了不止一个数量级。为了不让数据库的读成为业务瓶颈,同时也为了保证写库的成功率,一般会采用读写分离的技术来保证。 读写分离的实现是把访问的压…...
第十三届蓝桥杯国赛 C++ C 组 Java A 组 C 组 Python C 组 E 题——斐波那契数组(三语言代码AC)
目录1.斐波那契数组1.题目描述2.输入格式3.输出格式4.样例输入5.样例输出6.数据范围7.原题链接2.解题思路3.Ac_code1.Java2.C3.Python1.斐波那契数组 1.题目描述 如果数组 A(a0,a1,⋯.an−1)A(a_0,a_1,⋯.a_{n-1})A(a0,a1,⋯.an−1)满足以下条件, 就说它是一个斐波那契…...
多因子模型(MFM)
多因子模型(Muiti-Factor M: MFM)因子投资基础CAPM (资本资产定价模型)APT套利定价理论截面数据 & 时间序列数据 & 面板数据定价误差 α\alphaαalpha 出现的原因线性多因子模型Fama-French三因子模型三因子的计算公式利用alpha大小进行购买股票…...
django项目实战一(django+bootstrap实现增删改查)
目录 一、创建django项目 二、修改默认配置 三、配置数据库连接 四、创建表结构 五、在app当中创建静态文件 六、页面实战-部门管理 1、实现一个部门列表页面 2、实现新增部门页面 3、实现删除部门 4、实现部门编辑功能 七、模版的继承 1、创建模板layout.html 1&…...
Python|GIF 解析与构建(5):手搓截屏和帧率控制
目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...
css实现圆环展示百分比,根据值动态展示所占比例
代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...
Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...
WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)
一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解,适合用作学习或写简历项目背景说明。 🧠 一、概念简介:Solidity 合约开发 Solidity 是一种专门为 以太坊(Ethereum)平台编写智能合约的高级编…...
JDK 17 新特性
#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持,不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的ÿ…...
分布式增量爬虫实现方案
之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面,避免重复抓取,以节省资源和时间。 在分布式环境下,增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路:将增量判…...
Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...
HDFS分布式存储 zookeeper
hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架,允许使用简单的变成模型跨计算机对大型集群进行分布式处理(1.海量的数据存储 2.海量数据的计算)Hadoop核心组件 hdfs(分布式文件存储系统)&a…...
招商蛇口 | 执笔CID,启幕低密生活新境
作为中国城市生长的力量,招商蛇口以“美好生活承载者”为使命,深耕全球111座城市,以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子,招商蛇口始终与城市发展同频共振,以建筑诠释对土地与生活的…...
