js module大战
js本身是一个多才多艺的语言,一个可以用自己编译自己的*度极高的语言。正因为这份*,出现了天花乱坠的规范与框架们,其中最基础的一块便是module。
来来来,baby们,做个小测试: commonjs·amd·cmd·umd·es6,这些模块规范,大家熟悉几个?
注意注意:本文乃笔者主观写出的欢快脱线认知,也许和真正的模块形成的历史有所区别。
一切的根源
js是一个*度极高的语言,即使没有模块的概念。也可以通过iife,new一个对象来实现类似与模块的概念。也可以实现可复用,作用域独立,易维护。这样散装的,无法维护各个模块之间依赖。在一个js文件中,模块一多,也许就是修罗场。
module的诞生
于是js module,一个令人又爱又恨的名词诞生了。js本身设计上就没有模块的概念,之后为了让js变成一个功能强大的语言,业界大佬们各显神通,定了一个名为commonjs的规范,实现了一个名为模块的东西。可惜大多浏览器并不支持,只能用于nodejs,于是commonjs开始分裂,变异了一个名为amd规范的模块,可以用于浏览器端,而由于amd与commonjs规范相去甚远,于是amd自立门户,并且推出了requirejs这个框架,用于实现并推广amd规范。正因为amd与commonjs如此不同,且用于不同的环境,为了能够兼容两个平台,umd应运而生,不过笔者认为仅仅是一个polyfill,以兼容两个平台。此时,commonjs的拥护者认为,浏览端也可以实现commonjs的规范,于是稍作改动,形成了cmd规范,并且推出了seajs这个框架。正在amd与cmd打得火热的时候,ecmascript6给js本身定了一个模块加载的功能,es6表示“你们也别争了,js模块有原生的语法了”。
真正的规范
对于众多规范中,只有commonjs和es6 import/export是真正的规范,其余的是利用js现有的支持的方法模拟出的环境,以实现各自的规范。
至于为什么说commonjs和es6 import/export是真正的规范呢?因为只有原生支持他们的语法,才能实现他们的规范。
对于commonjs而言,运行环境中,必须有require和module.exports的支持,才能运行。这就是浏览器与commonjs无缘的主要原因。
至于es6,失去对import和export关键字的支持,便一切都是零。比如,nodejs就不支持import和export,明明nodejs支持其他的es6语法,怎么就对import和export如此不友好,笔者认为nodejs是为了实现commonjs的规范,因此不能接受es6的模块扰乱nodejs的模块规范。
所以说commonjs和es6的模块才是真正的规范。
关于commonjs和es6模块,笔者曾经写过一篇关于他们的文章,这里不多做赘述,移步至读懂commonjs的模块加载。
有关浏览器实现commonjs模块的原理
既然浏览器缺少commonjs的两个关键字导致,模块不成立,那么就创建一个模块环境。使用define这个方法,将函数内部模拟成commonjs的环境,提供require和module.export的方法。无论是seajs还是requirejs都是通过define模拟环境的办法,实现module的。
自立门户的amd
笔者之前正在diy台式机,挑选显卡的时候,在a卡和n卡之间犹豫了一下,之后果断选a卡,因为a卡便宜一点。这里的a卡指的是amd,那么和此处js的amd有社么关系吗?没有任何关系!只是因为js模块的amd这个缩写和人家美国的amd公司的名字一致而已,这只是一个美丽的巧合。
amd的全称是asynchronous module definition,中文名是异步模块定义,不同于commonjs的按需加载,也就是require了之后才加栽,amd是将所有的潜在需要用到的包都加载运行了,也就是传说中的高配,至于是否用得到就不再amd的考虑范围之内了。requirejs就是amd的代表:
来自amd的暴击:
define("module1",function(require) { 'use strict'; console.log("cccc") }); define("module2",function(require) { 'use strict'; console.log("aaaa") if(false){ console.log(require("module1")) } console.log("bbbb") }); require(["module2"])
此时打印cccc,aaaa,bbbb,由此可见amd是将所有的模块,在模块执行之前,就全部加载完毕了,所以amd还有一种写法是将所有的依赖模块写头部。
define("module1",function(require) { 'use strict'; console.log("cccc") }); define("module2",[module1],function(module1) { 'use strict'; console.log("aaaa") if(false){ console.log(require("module1")) } console.log("bbbb") });
浏览一下requirejs的源码:
requirejs有两种获取依赖的方法,一种是配置,一种是利用正则匹配出所有的require的内容,然后加入依赖。当调用当前模块的时候,就先检查依赖的模块是否运行了。
cjsrequireregexp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,
已经定义完成的模块,会被缓存在一个对象之中,以模块的名字为唯一健值,之后若再次调用此缓存的模块,则无需再次执行。
执行之后缓存结果
defined[id] = exports;
二次执行,先检查是否已存在,若存在怎不重复执行。
function callgetmodule(args) { //skip modules already defined. if (!hasprop(defined, args[0])) { getmodule(makemodulemap(args[0], null, true)).init(args[1], args[2]); } }
若是远程依赖,则创建一个script,加载远程资源,并将script加入头部。
req.createnode = function (config, modulename, url) { var node = config.xhtml ? document.createelementns('http://www.w3.org/1999/xhtml', 'html:script') : document.createelement('script'); node.type = config.scripttype || 'text/javascript'; node.charset = 'utf-8'; node.async = true; return node; };
那么umd是个什么样的存在
第一次接触到umd,是在webpack的打包之中,想要生成一个library,有好多个选项,commonjs,amd,umd。当时一下子有点懵,umd是什么?在不知情的情况下,又出现了一个模块规范,这让笔者的头很大啊。
output: { path: path.join(__dirname), filename: 'index.js', librarytarget: "umd",//此处是希望打包的插件类型 library: "swiper", }
看一眼打包后的效果:
!function(root,callback){ "object"==typeof exports&&"object"==typeof module?//判断是不是nodejs环境 module.exports=callback(require("react"),require("prop-types")) : "function"==typeof define&&define.amd?//判断是不是requirejs的amd环境 define("swiper",["react","prop-types"],callback) :"object"==typeof exports?//相当于连接到module.exports.swiper exports.swiper=callback(require("react"),require("prop-types")) : root.swiper=callback(root.react,root.proptypes)//全局变量 }(window,callback)
这样一个polyfill,瞬间就兼容了commonjs,amd和全局的一个模块。这就是umd,比起规范,不如说它是一个兼容,polyfill,支持多个模块规范。
where is cmd?
眼尖的小伙伴应该发现了,cmd不知去向,webpack的打包中也没有cmd模块的一个选项。cmd其实就是按照commonjs规范,然后进行改造,从而使之支持浏览器端的一种规范。
主要说说他和amd的主要区别吧:
require关键字引入内容的执行顺序。amd是一个依赖提前加载的概念,而cmd是同步执行,遇到require之后再执行当前的一个模块。
define("c",function(require, exports, module) { console.log("bbb") }); define("b",function(require, exports, module) { console.log("aaa") require("c") console.log("ccc") }); seajs.use("b")
这样打印的就是 aaa,bbb,ccc。按照代码出现的顺序执行。
当然这个是同步代码的区别,至于异步代码,cmd和amd都是通过script,append到head加载,存入模块对象之中,然后根据id调用。不过cmd有一点不同,加了一个小小的优化:
if (!data.debug) { head.removechild(node) }
当代码加载完毕之后,并且缓存在模块之中之后,便在head之中删除了这个script。
后记
借用鲁迅的一句话“世上本没有路,走的人多了也就成了路”。js modudle的规范也是如此,用的人多了也就是默认的解决方案了。
js module大战就写到这边吧,大家都不晓得这些模块的规范能够存活多久,但是概念都很好。所以好好学习概念,以后就算有新的规范出来了,和老规范一对比,找出不同点,加以分析,便能够轻松上手!
参考地址
不能忘记帮助笔者认知模块的文章们,谢谢大佬们:
:requirejs的官网对于amd产生历史的解释。
:seajs的大佬对模块这一块的看法,梳理了笔者对于amd的困惑
overflow上对于amd和requirejs的一个解释
以上所述是小编给大家介绍的js module详解整合,希望对大家有所帮助
上一篇: 整理C# 二进制,十进制,十六进制 互转
下一篇: iOS Moya实现OAuth请求的方法