模块化产品是实现以大批量的效益进行单件生产目标的一种有效方法。产品模块化也是支持用户自行设计产品的一种有效方法。产品模块是具有独立功能和输入、输出的标准部件。这里的部件,一般包括分部件、组合件和零件等。模块化产品设计方法的原理是,在对一定范围内的不同功能或相同功能、不同性能、不同规格的产品进行功能分析的基础上,划分并设计出一系列功能模块,通过模块的选择和组合构成不同的顾客定制的产品,以满足市场的不同需求。这是相似性原理在产品功能和结构上的应用,是一种实现标准化与多样化的有机结合及多品种、小批量与效率的有效统一的标准化方法。
模块化结构体现了车族化、标准化的设计思想。按照研制合同,基型车为装甲输送车,变型车有五种:装甲指挥车(德、英、荷)、装甲修理车(也称战伤排除车,荷兰)、物资输送车(荷兰)、装甲救护车(英、荷)。今后,还可以进一步变型为步兵战车、装甲通信车、炮兵指挥车、炮兵观察车、雷达观察车、迫击炮车、工兵输送车、清障车等。
“拳击手”装甲输送车最多可容纳11名人员。设计中“以人为本”,强调乘坐舒适性,按人机工程学原理来设计,使乘员能在艰苦的作战环境下长时间坚持作战。
全密封的装甲结构,既为乘载人员提供了包括三防在内的全面防护,也便于安装大功率空调系统,适于在炎热地区长期作战。每个乘员座椅都配有安全带。优化设计的悬挂装置和减震系统,大大降低了车内噪声,实现“寂静”行驶。
液压控制的跳板式后部车门,使乘员能迅速上下车。车内的有效容积达14立方米,提供了宽敞、舒适的车内生活和战斗环境。
皮质座垫和靠背柔软舒适,座垫可折起,进一步增大载员室通道的空间。乘员的饮食和饮水装置也很考究。乘员的身高标准为1.56~2米,而不是像苏联/俄罗斯的军队那样,对乘载人员的身高有严格限制。
“拳击手”装甲车的动力装置为德国MTU公司的8缸增压柴油机,最大功率530千瓦,比T-54/55坦克的发动机功率还要高出44%。
发动机横向布置,通过一对锥齿轮与纵向布置的阿利逊公司制造的自动变速箱相连接。8个车轮采用独立悬挂装置,有轮胎气压中央调节系统,前四个车轮为转向轮,可通过复式转向制动器来减少转向半径。整个轮系结构如图所示。
由于发动机功率大,整个轮系设计合理,使得公路最大速度达到103千米/小时,最大行程达到1050千米。这使得它在战役部署阶段可实施长距离机动。“拳击手”最大爬坡度为31°,最大侧倾坡度为17°。
“拳击手”的武器站可灵活布置,装有特种设备和各种武器的任务舱(任务模块)可以在一个小时内更换完毕,非常方便。美国陆军“斯崔克”轮式装甲车
“斯崔克”轮式装甲车是美国陆军快速战斗旅的核心战斗平台。该车由加拿大通用汽车防务公司和美国通用动力公司联合为美国陆军制造,在机动性、兼容性、快速部署、生存能力和杀伤力方面都有出色表现,被称为2002年度“最新型的陆战武器”。
由于 Js起初定位的原因(刚开始没想到会应用在过于复杂的场景),所以它本身并没有提供模块系统,随着应用的复杂化,模块化成为了一个必须解决的问题。本着菲麦深入原理的原则,很有必要来揭开模块化的面纱
一、模块化需要解决的问题
要对一个东西进行深入的剖析,有必要带着目的去看。模块化所要解决的问题可以用一句话概括
在没有全局污染的情况下,更好的组织项目代码
举一个简单的栗子,我们现在有如下的代码:
function doSomething(){
const a= 10;
const b= 11;
const add= function(a+ b){
return a+ b
}
add(a+ b)
}
在现实的应用场景中,doSomething可能需要做很多很多的事情,add函数可能也更为复杂,并且可以复用,那么我们希望可以将 add函数独立到一个单独的文件中,于是:
// doSomething.js文件
const add= require('add.js');
const a= 10;
const b= 11;
add(a+ b);
// add.js文件
function add(a, b){
return a+ b;
}
module.exports= add;
这样做的目的显而易见,更好的组织项目代码,注意到两个文件中的 require和 module.exports,从现在的上帝视角来看,这出自 CommonJS规范(后文会有一个章节来专门讲规范)中的关键字,分别代表导入和导出,抛开规范而言,这其实是我们模块化之路上需要解决的问题。另外,虽然 add模块需要得到复用,但是我们并不希望在引入 add的时候造成全局污染
二、引入的模块如何运行
在上述的例子中,我们已经将代码拆分到了两个模块文件当中,在不造成全局污染的情况下,如何实现 require,才能使得例子中的代码做到正常运行呢?
先不考虑模块文件代码的载入过程,假设 require已经可以从模块文件中读取到代码字符串,那么 require可以这样实现
function require(path){
// lode方法读取 path对应的文件模块的代码字符串
// let code= load(path);
//不考虑 load的过程,直接获得模块 add代码字符串
let code='function add(a, b){return a+b}; module.exports= add';
//封装成闭包
code= `(function(module){$[code]})(context)`
//相当于 exports,用于导出对象
let context={};
//运行代码,使得结果影响到 context
const run= new Function('context', code);
run(context, code);
//返回导出的结果
return context.exports;
}
这有几个要点:
1)为了不造成全局污染,需要将代码字符串封装成闭包的形式,并且导出关键字 module.exports,module是与外界联系的唯一载体,需要作为闭包匿名函数的入参,与引用方传入的上下文 context进行关联
2)使用 new Function来执行代码字符串,估计大部分同学对 new Function是不熟悉的,因为一般情况下定义一个函数无需如此,要知道,用 Function类可以直接创建函数,语法如下:
var function_name= new function(arg1, arg2,..., argN, function_body)
在上面的形式中,每个 arg都是一个参数,最后一个参数是函数主体(要执行的代码)。这些参数必须是字符串。也就是说,可以使用它来执行字符串代码,类似于 eval,并且相比 eval,还可以通过参数的形式传入字符串代码中的某些变量的值
3)如果曾经你有疑惑过为什么规范的导出关键字只有 exports而我们实际使用过程中却要使用module.exports(写过 Node代码的应该不会陌生),那在这段代码中就可以找到答案了,如果只用 exports来接收 context,那么对 exports的重新赋值对 context不会有任何影响(参数的地址传递),不信将代码改成如下形式再跑一跑:
演示结果
三、代码载入方式
解决了代码的运行问题,还需要解决模块文件代码的载入问题,根据上述实例,我们的目标是将模块文件代码以字符串的形式载入
在 Node容器,所有的模块文件都在本地,只需要从本地磁盘读取模块文件载入字符串代码,再走上述的流程就可以了。事实证明,Node非内建、核心、c++模块的载入执行方式大体如此(虽然使用的不是 new Function,但也是一个类似的方法)
在 RN/Weex容器,要载入一个远程 bundle.js,可以通过 Native的能力请求一个远程的 js文件,再读取成字符串代码载入即可(按照这个逻辑,Node读取一个远程的 js模块好像也无不可,虽然大多数情况下我们不需要这么做)
在浏览器环境,所有的 Js模块都需要远程读取,尴尬的是,受限于浏览器提供的能力,并不能通过 ajax以文件流的形式将远程的 js文件直接读取为字符串代码。前提条件无法达成,上述运行策略便行不通,只能另辟蹊径
这就是为什么有了 CommonJs规范了,为什么还会出现 AMD/CMD规范的原因
那么浏览器上是怎么做的呢?在浏览器中通过 Js控制动态的载入一个远程的 Js模块文件,需要动态的插入一个<script>节点:
//摘抄自 require.js的一段代码
var node= config.xhtml?
document.createElementNS('','html:script'):
document.createElement('script');
node.type= config.scriptType||'text/javascript';
node.charset='utf-8';
node.async= true;
node.setAttribute('data-requirecontext', context.contextName);
node.setAttribute('data-requiremodule', moduleName);
node.addEventListener('load', context.onScriptLoad, false);
node.addEventListener('error', context.onScriptError, false);
要知道,设置了<script>标签的 src之后,代码一旦下载完成,就会立即执行,根本由不得你再封装成闭包,所以文件模块需要在定义之初就要做文章,这就是我们说熟知的 AMD/CMD规范中的 define,开篇的 add.js需要重新改写一下
// add.js文件
define('add',function(){
function add(a, b){
return a+ b;
}
return add;
})
而对于 define的实现,最重要的就是将 callback的执行结果注册到 context的一个模块数组中:
context.modules={}
function define(name, callback){
context.modules[name]= callback&& callback()
}
于是 require就可以从 context.modules中根据模块名载入模块了,是不是有了一种自己去写一个“requirejs”的冲动感
具体的 AMD实现当然还会复杂很多,还需要控制模块载入时序、模块依赖等等,但是了解了这其中的灵魂,想必去精读 require.js的源码也不是一件困难的事情
四、Webpack中的模块化
Webpack也可以配置异步模块,当配置为异步模块的时候,在浏览器环境同样的是基于动态插入<script>的方式载入远程模块。在大多数情况下,模块的载入方式都是类似于 Node的本地磁盘同步载入的方式
_忘记,Webpack除了有模块化的能力,还是一个在辅助完善开发工作流的工具,也就是说,Webpack的模块化是在开发阶段的完成的,使用 Webpack构筑的工作环境,在开发阶段虽然是独立的模块文件,但是在运行时,却是一个合并好的文件
所以 Webpack是一种在非运行时的模块化方案(基于 CommonJs),只有在配置了异步模块的时候对异步模块的加载才是运行时的(基于 AMD)
五、模块化规范
通用的问题在解决的过程中总会形成规范,上文已经多次提到 CommonJs、AMD、CMD,有必要花点篇幅来讲一讲规范
Js的模块化规范的萌发于将 Js扩展到后端的想法,要使得 Js具备类似于 Python、Ruby和 Java那样具备开发大型应用的基础能力,模块化规范是必不可少的。CommonJS规范的提出,为Js制定了一个美好愿景,希望 Js能在任何地方运行,包括但不限于:
服务器端 Js应用
命令行工具
桌面应用
混合应用
CommonJS对模块的定义并不复杂,主要分为模块引用、模块定义和模块标识
模块引用:使用 require方法来引入一个模块
模块定义:使用 exports导出模块对象
模块标识:给 require方法传入的参数,小驼峰命名的字符串、相对路径或者绝对路径
模块示意
CommonJs规范在 Node中大放异彩并且相互促进,但是在浏览器端,鉴于网络的原因,同步的方式加载模块显然不太实用,在经过一段争执之后,AMD规范最终在前端场景中胜出(全称 Asynchronous Module Definition,即“异步模块定义”)
什么是 AMD,为什么需要 AMD?在前述模块化实现的推演过程中,你应该能够找到答案
除此之外还有国内玉伯提出的 CMD规范,AMD和 CMD的差异主要是,前者需要在定义之初声明所有的依赖,后者可以在任意时机动态引入模块。CMD更接近于 CommonJS
两种规范都需要从远程网络中载入模块,不同之处在于,前者是预加载,后者是延迟加载
五、总结
如果有心,可以参照本文的推演,来实现一个“yourRequireJs”,没有什么比重复造轮子更能让知识沉淀~~
转载请注明:片头模版 » 模块化原理是什么(模块化设计原理有哪些)