js module大战


Posted in Javascript onApril 19, 2019

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是什么?在不知情的情况下,又出现了一个模块规范,这让笔者的头很大啊。

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

参考地址

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

requirejs:requirejs的官网对于AMD产生历史的解释。
前端模块化开发那点历史 :seajs的大佬对模块这一块的看法,梳理了笔者对于AMD的困惑
overflow上对于AMD和requirejs的一个解释

以上所述是小编给大家介绍的js module详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
javascript重写alert方法的实例代码
Mar 29 Javascript
angular.element方法汇总
Jan 07 Javascript
jQuery中end()方法用法实例
Jan 08 Javascript
JQuery EasyUI的使用
Feb 24 Javascript
select隐藏选中值对应的id,显示其它id的简单实现方法
Aug 25 Javascript
javascript输出AscII码扩展集中的字符方法
Dec 26 Javascript
BootStrap 动态表单效果
Jun 02 Javascript
vue.js实现单选框、复选框和下拉框示例
Jul 18 Javascript
微信小程序 配置顶部导航条标题颜色的实现方法
Sep 20 Javascript
使用 Javascript 实现浏览器推送提醒功能的示例
Nov 03 Javascript
微信小程序onLaunch异步,首页onLoad先执行?
Sep 20 Javascript
VUE 单页面使用 echart 窗口变化时的用法
Jul 30 Javascript
如何根据业务封装自己的功能组件
Apr 19 #Javascript
vue项目打包上传github并制作预览链接(pages)
Apr 19 #Javascript
vue组件之间的数据传递方法详解
Apr 19 #Javascript
详解keep-alive + vuex 让缓存的页面灵活起来
Apr 19 #Javascript
一个Java程序猿眼中的前后端分离以及Vue.js入门(推荐)
Apr 19 #Javascript
基于javascript的拖拽类封装详解
Apr 19 #Javascript
Bootstarp在pycharm中的安装及简单的使用方法
Apr 19 #Javascript
You might like
《斗罗大陆》六翼天使武魂最强,为什么老千家不是上三宗?
2020/03/02 国漫
新手配置 PHP 调试环境(IIS+PHP+MYSQL)
2007/01/10 PHP
php visitFile()遍历指定文件夹函数
2010/08/21 PHP
PHP闭包实例解析
2014/09/08 PHP
ThinkPHP框架整合微信支付之刷卡模式图文详解
2019/04/10 PHP
jQuery数组处理代码详解(含实例演示)
2012/02/03 Javascript
瀑布流布局并自动加载实现代码
2013/03/12 Javascript
火狐下input焦点无法重复获取问题的解决方法
2014/06/16 Javascript
JavaScript Promise 用法
2016/06/14 Javascript
如何解决手机浏览器页面点击不跳转浏览器双击放大网页
2016/07/01 Javascript
AngularJS框架中的双向数据绑定机制详解【减少需要重复的开发代码量】
2017/01/19 Javascript
w3c编程挑战_初级脚本算法实战篇
2017/06/23 Javascript
利用JS制作万年历的方法
2017/08/16 Javascript
移动前端图片压缩上传的实例
2017/12/06 Javascript
详解VUE里子组件如何获取父组件动态变化的值
2018/12/26 Javascript
Vue.extend 编程式插入组件的实现
2019/11/18 Javascript
Flexible.js可伸缩布局实现方法详解
2020/11/13 Javascript
在Python中使用异步Socket编程性能测试
2014/06/25 Python
python实现随机调用一个浏览器打开网页
2018/04/21 Python
Python中创建二维数组
2018/10/17 Python
如何使用python操作vmware
2019/07/27 Python
Python中zip()函数的简单用法举例
2019/09/02 Python
Python tkinter布局与按钮间距设置方式
2020/03/04 Python
Pycharm导入anaconda环境的教程图解
2020/07/31 Python
使用phonegap进行本地存储的实现方法
2017/03/31 HTML / CSS
历史专业个人求职信范文
2013/12/07 职场文书
洗煤厂厂长岗位职责
2014/01/03 职场文书
公司员工检讨书
2014/02/08 职场文书
学生检讨书范文
2014/10/30 职场文书
公务员检讨书
2014/11/01 职场文书
2015年化验员工作总结
2015/04/10 职场文书
运动会广播稿100字
2015/08/19 职场文书
MySQL数据迁移相关总结
2021/04/29 MySQL
变长双向rnn的正确使用姿势教学
2021/05/31 Python
HashMap实现保存两个key相同的数据
2021/06/30 Java/Android
PYTHON基于Pyecharts绘制常见的直角坐标系图表
2022/04/28 Python