浅析前端工程化中的一部曲——模块化
在日益复杂和多元的 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&…...

graphsage解读
传统的图方法都是直推式(transductive)的,学习到的是结构固定的图模型,一旦有新的节点加入,便需要重新训练整个图网络,泛化性不强。GraphSAGE是归纳式(inductive)的,它学习一种映射:通过采样和聚合邻居节点…...

一文带你读懂Dockerfile
目录 一、概述 二、DockerFile构建过程解析 (一)Dockerfile内容基础知识 (二)Docker执行Dockerfile的大致流程 (三)总结 三、DockerFile常用保留字指令 四、案例 (一)自定义…...

用python实现对AES加密的视频数据流解密
密码学中的高级加密标准(Advanced Encryption Standard,AES),又称Rijndael加密法。 在做网络爬虫的时候,会遇到经过AES加密的数据,可以使用python来进行解密。 在做爬虫的时候,通常可以找到一个key,这个key是一个十六进制的一串字符,这传字符是解密的关键。所以对于…...

网络高可用方案
目录 1. 网络高可用 2. 高可用方案设计 2.1 方案一 堆叠 ha负载均衡模式 2.2 方案二 OSPF ha负载均衡模式 3. 高可用保障 1. 网络高可用 网络高可用,是指对于网络的核心部分或设备在设计上考虑冗余和备份,减少单点故障对整个网络的影响。其设计应…...

简单的认识 Vue(vue-cli安装、node安装、开发者工具)
Vue1、Vue 与其他框架的对比及特点1.1 Vue.js 是什么1.2 作者1.3 作用1.4 Vue 与其他框架的对比2、安装 Vue 的方法2.1 CDN 引入2.2 脚手架工具2.3 vue 开发者工具安装3、创建第一个实例4、理解 Vue 的 MVVM 模式5、数据双向绑定5.1 感受响应式实验总结1、Vue 与其他框架的对比…...

如何写一个 things3 client
Things3[1] 是一款苹果生态内的任务管理软件,是一家德国公司做的,非常好用。我前后尝试了众多任务管理软件,最终选定 things3,以后有机会会写文章介绍我是如何用 things3 来管理我的日常任务。本文主要介绍欧神写的 tli[2] 工具来…...

人工智能原理复习 | 命题逻辑和谓词演算
文章目录 一、前言二、命题逻辑三、谓词逻辑CSDN 叶庭云:https://yetingyun.blog.csdn.net/ 一、前言 数理逻辑思想的起源:莱布尼茨之梦。古典数理逻辑主要包括两部分:命题逻辑和谓词逻辑,命题逻辑又是谓词逻辑的一种简单情形。 逻辑研究的基本内容: 语法。语言部分:基…...

前端基础面试题:如何判断对象是否具有某属性?遍历数组的方法有哪些?
一、如何判断对象具有某属性? 如:let obj{name:zhangsan,age:21} 有以下方法 ( property 为属性名的变量,实际上是key,键名): 1. property in obj 效果如图: in 运算符 2. Reflect.has(obj, property)…...

Docker入门和安装教程
一、Docker入门简介 Docker 是一个基于GO语言开发的开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化。 容器是完全使用沙箱机制,相互之间不会…...
有了java基础,迅速学完Python并做了一份笔记-全套Python,建议收藏
面向过程Python简介Python和Java的解释方式对比Java:源代码 -> 编译成class -> Jvm解释运行Python:源代码 -> Python解释器解释运行我经常和身边的Java开发者开玩笑说:“Java真变态,别的语言都是要么直接编译要么直接解释…...