由于Js最初的定位(一开始没想到会用在太复杂的场景中),没有提供模块化的系统。随着应用的复杂化,模块化成为必须解决的问题。基于菲麦深化的原则,有必要揭开模块化的面纱
1.模块化需要解决的问题
要深入分析一件事,就要有目的地看待它。模块化要解决的问题可以用一句话概括
更好地组织项目代码,避免全球污染
对于一个简单的栗子,我们现在有以下代码:
函数dosomesing(){ const a=10;const b=11const add=function(a b){ return a b } add(a b)}在实际的应用场景中,做doSomething可能需要做很多事情,而add函数可能更复杂,可重用,所以我们希望add函数可以独立到一个单独的文件中,所以:
//dosome.js文件const add=require(' add . js ');const a=10const b=11添加(a)b;//add.js文件函数add (a,b){ return a b;} module.exports=add这样做的目的很明显,就是为了更好地组织项目代码,并注意两个文件中的require和module.exports。从现在上帝的角度来看,这来自CommonJS规范中的关键词(后面会有专门针对规范的章节),分别代表导入和导出。抛开规范不谈,这其实是模块化道路上需要解决的问题。此外,虽然add模块需要重用,但我们不希望在引入add时造成全局污染
二、如何运行引入的模块
在上面的例子中,我们已经将代码分成了两个模块文件。在不造成全局污染的情况下,我们如何实现require来使示例中的代码正常运行?
不管模块文件代码的加载过程如何,假设require已经可以从模块文件中读取代码字符串,那么require可以这样实现
函数require (path) {//lode方法读取path //let code=load(path)对应的文件模块的代码字符串;//获取模块添加代码字符串让code=' function add (a,b) {return a b}不考虑加载的过程;module . exports=add ';//封装在一个闭包代码中=`(function(module){ $[code]})(context)`//相当于导出,用于导出对象let context={ };//运行代码,使结果影响context const run=new function(' context ',code);运行(上下文、代码);//返回导出的结果返回context.exports}有几个要点:
1)为了不造成全局污染,需要将代码串封装成闭包的形式,并导出关键字module,exports module是与外界通信的唯一载体,需要作为闭包匿名函数的输入参数,并与引用者传入的上下文上下文关联
2)使用新的Function执行代码串,估计大部分同学都不熟悉新的Function,因为一般不需要定义函数。您应该知道,可以直接用function类创建函数,语法如下:
Var function _ name=新函数(arg1,arg2,argn,function _ body)在上面的形式中,每个arg都是一个参数,最后一个参数是函数体(要执行的代码)。这些参数必须是字符串。也就是说,它可以用来执行字符串代码,类似于eval,与eval相比,它还可以以参数的形式传入字符串代码中一些变量的值
3)如果你曾经好奇为什么规范的export关键字只有export,而我们实际上使用的是module.exports(那些写过Node代码的人应该不会陌生),那么你可以在这段代码中找到答案。如果您仅使用导出来接收上下文,则导出的重新分配不会对上下文(参数的地址传输)产生任何影响。如果您不相信,请将代码更改为以下形式并再次运行:
演示结果
三、代码加载方法
它解决了代码运行的问题,也需要解决模块文件代码加载的问题。根据上面的例子,我们的目标是以字符串的形式加载模块文件代码
在Node容器中,所有的模块文件都是本地的,所以只需要从本地磁盘读取模块文件并加载字符串代码,然后就可以按照上面的流程进行操作了。事实证明,Node非内置、核心和c模块的加载和执行方式大致相同(虽然没有使用新的Function,但也是类似的方法)
在RN/Weex容器中,如果要加载一个远程bundle.js,可以通过Native的能力请求一个远程js文件,然后读入字符串代码进行加载(按照这个逻辑,似乎Node也需要读取一个远程js模块,虽然大多数情况下我们并不需要这样做)
在浏览器环境中,所有的Js模块都需要远程读取。尴尬的是,由于浏览器提供的功能,远程js文件不能通过ajax以文件流的形式直接读取为字符串代码。如果前提条件达不到,上述操作策略就行不通,只能另辟蹊径
这就是为什么有CommonJs规范,也是为什么有AMD/CMD规范
那么它在浏览器上做什么呢?要通过浏览器中的Js控件动态加载远程Js模块文件,需要动态插入一个脚本节点:
//从require.js var node=config.xhtml中提取的一段代码?document . createelementns(' http://www . w3 . org/1999/XHTML ',' html : script '): document . createelement(' script ');node . type=config . script type | | ' text/JavaScript ';node . charset=' utf-8 ';node.async=truenode . setattribute(' data-require context ',context . context name);node . setAttribute(' data-require module ',ModuleName);node.addEventListener('load ',context.onScriptLoad,false);node.addEventListener('error ',context.onScriptError,false);要知道,一旦设置了脚本标签的src,代码一旦下载就会立即执行,根本不能封装成闭包。所以文件模块需要在定义之初就大惊小怪,这是我们在AMD/CMD规范中熟悉的,开篇的add.js需要重写。
//add.js文件define ('add ',function () {function add (a,b){ return a b;}返回add})对于define的实现,最重要的是将回调执行结果注册到上下文的模块数组中:
Context.modules={ } function define(name,callback){ context . modules[name]=callbackcallback()}因此要求可以根据模块名称从context . modules加载模块。你有没有一种冲动想自己写一篇“requirejs”?
当然具体的AMD实现会复杂很多,还需要控制模块加载时序、模块依赖等。但是了解了其中的精髓,深入阅读require.js的源代码就不难了
第四,网络包的模块化
Webpack也可以配置异步模块。当配置为异步模块时,远程模块也基于动态插入脚本在浏览器环境中加载。大多数情况下,模块的加载模式类似于Node本地磁盘的同步加载模式
忘了除了模块化之外,Webpack还是一个帮助改进开发工作流的工具。也就是说,Webpack的模块化是在开发阶段完成的,用Webpack构建的工作环境在开发阶段是一个独立的模块文件,但在运行时是一个合并的文件。
因此,Webpack是非运行时(基于CommonJs)的模块化解决方案,只有在配置了异步模块(基于AMD)的情况下,加载异步模块才是运行时
动词(verb的缩写)模块化规格
一般问题在解决的过程中总会形成规范。CommonJs、AMD、CMD上面已经提到很多次了,所以有必要花点时间讲讲规范
Js的模块化规范源于将Js扩展到后端的思想。为了使Js具备开发像Python、Ruby和Java这样的大规模应用程序的基本能力,模块化规范是必要的。CommonJs规范的提出为Js建立了美好的愿景,希望Js可以在任何地方运行,包括但不限于:
服务器端Js应用命令行工具桌面应用混合应用CommonJS定义模块并不复杂,主要分为模块引用、模块定义和模块标识
模块引用:使用require方法引入模块定义;使用导出导出模块对象模块标识:传递给require方法的参数、以驼峰命名的字符串、相对路径或绝对路径
模块示意图
CommonJs规范在Node中大放异彩,相互促进,但在浏览器端,由于网络的原因,同步加载模块显然不切实际。经过一段时间的争议,AMD规范终于在前端场景胜出(全称异步模块定义,“异步模块定义”)
什么是AMD,为什么需要AMD?在推导模块化实现的过程中,您应该能够找到答案
此外,国内还有宇博提出的CMD规范。AMD和CMD的主要区别在于,前者需要在定义之初就声明所有依赖关系,而后者可以随时动态引入模块。CMD更接近CommonJS
这两种规范都需要从远程网络加载模块。区别在于前者是预加载,后者是延迟加载
动词(verb的缩写)摘要
如果有心的话,可以参考本文的演绎,实现一个“yourRequireJs”。没有什么比反复制造轮子更能沉淀知识了~ ~
以上就是本文的全部内容。希望对大家的学习有帮助,支持我们。