JS MODULE 大战

JS自己是一个多才多艺的语言,一个能够用本身编译本身的自由度极高的语言。正由于这份自由,出现了天花乱坠的规范与框架们,其中最基础的一块即是Module。javascript

来来来,baby们,作个小测试: CommonJS·AMD·CMD·UMD·ES6,这些模块规范,你们熟悉几个?html

注意注意:本文乃笔者主观写出的欢快脱线认知,也许和真正的模块造成的历史有所区别。前端

一切的根源

JS是一个自由度极高的语言,即便没有模块的概念。也能够经过IIFE,new一个对象来实现相似与模块的概念。也能够实现可复用,做用域独立,易维护。这样散装的,没法维护各个模块之间依赖。在一个JS文件中,模块一多,也许就是修罗场。java

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模块有原生的语法了”。node

真正的规范

对于众多规范中,只有CommonJS和ES6 import/export是真正的规范,其他的是利用JS现有的支持的方法模拟出的环境,以实现各自的规范。react

至于为何说CommonJS和ES6 import/export是真正的规范呢?由于只有原生支持他们的语法,才能实现他们的规范。webpack

对于CommonJS而言,运行环境中,必须有require和module.exports的支持,才能运行。这就是浏览器与CommonJS无缘的主要缘由。git

至于ES6,失去对import和export关键字的支持,便一切都是零。好比,nodejs就不支持import和export,明明nodejs支持其余的ES6语法,怎么就对import和export如此不友好,笔者认为nodejs是为了实现commonJS的规范,所以不能接受ES6的模块扰乱nodejs的模块规范github

因此说CommonJS和ES6的模块才是真正的规范。web

关于CommonJS和ES6模块,笔者曾经写过一篇关于他们的文章,这里很少作赘述,移步至读懂CommonJS的模块加载

有关浏览器实现CommonJS模块的原理

既然浏览器缺乏CommonJS的两个关键字致使,模块不成立,那么就建立一个模块环境。使用define这个方法,将函数内部模拟成CommonJS的环境,提供requiremodule.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是什么?在不知情的状况下,又出现了一个模块规范,这让笔者的头很大啊。

来自webpack的凝视

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大战就写到这边吧,你们都不晓得这些模块的规范可以存活多久,可是概念都很好。因此好好学习概念,之后就算有新的规范出来了,和老规范一对比,找出不一样点,加以分析,便可以轻松上手!

参考地址

不能忘记帮助笔者认知模块的文章们,谢谢大佬们:

首发掘金~转载请注明出处

相关文章
相关标签/搜索