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 相关文章推荐
JS定时关闭窗口的实例
May 22 Javascript
jQuery ui 利用 datepicker插件实现开始日期(minDate)和结束日期(maxDate)
May 22 Javascript
Node.js程序中的本地文件操作用法小结
Mar 06 Javascript
Spring Boot+AngularJS+BootStrap实现进度条示例代码
Mar 02 Javascript
JavaScript实现无刷新上传预览图片功能
Aug 02 Javascript
JavaScript实现简单图片轮播效果
Aug 21 Javascript
js提取中文拼音首字母的封装工具类
Mar 12 Javascript
浅谈Webpack 是如何加载模块的
May 24 Javascript
微信小程序日历/日期选择插件使用方法详解
Dec 28 Javascript
vue+element UI实现树形表格带复选框的示例代码
Apr 16 Javascript
vue 中几种传值方法(3种)
Nov 12 Javascript
40行代码把Vue3的响应式集成进React做状态管理
May 20 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
php预定义常量
2006/12/25 PHP
php中让上传的文件大小在上传前就受限制的两种解决方法
2013/06/24 PHP
php curl post 时出现的问题解决
2014/01/30 PHP
CentOS下PHP安装Oracle扩展
2015/02/15 PHP
PHP实现通过Luhn算法校验信用卡卡号是否有效
2015/03/23 PHP
php 字符串中是否包含指定字符串的多种方法
2018/04/12 PHP
Ctrl+Enter提交内容信息
2006/06/26 Javascript
jQuery 1.0.4 - New Wave Javascript(js源文件)
2007/01/15 Javascript
qTip2 精致的基于jQuery提示信息插件
2012/02/17 Javascript
JS长整型精度问题实例分析
2015/01/13 Javascript
js实现选中页面文字将其分享到新浪微博
2015/11/05 Javascript
原生js实现键盘控制div移动且解决停顿问题
2016/12/05 Javascript
angularJS+requireJS实现controller及directive的按需加载示例
2017/02/20 Javascript
js读取json文件片段中的数据实例
2017/03/09 Javascript
Vue入门之数据绑定(小结)
2018/01/08 Javascript
vue中Axios的封装与API接口的管理详解
2018/08/09 Javascript
Vue Echarts实现可视化世界地图代码实例
2019/05/07 Javascript
javascript读取本地文件和目录方法详解
2020/08/06 Javascript
[00:14]护身甲盾
2019/03/06 DOTA
python实现绘制树枝简单示例
2014/07/24 Python
从零开始学Python第八周:详解网络编程基础(socket)
2016/12/14 Python
基于python select.select模块通信的实例讲解
2017/09/21 Python
Python中is和==的区别详解
2018/11/15 Python
500行python代码实现飞机大战
2020/04/24 Python
在tensorflow实现直接读取网络的参数(weight and bias)的值
2020/06/24 Python
详解如何解决H5开发使用wx.hideMenuItems无效果不生效
2021/01/20 HTML / CSS
Prototype如何为一个Ajax添加一个参数
2015/12/06 面试题
学生的自我鉴定范文
2013/10/24 职场文书
2014村务公开实施方案
2014/02/25 职场文书
《难忘的泼水节》教学反思
2014/02/27 职场文书
教育技术学专业职业规划书
2014/03/03 职场文书
2015教师见习期工作总结
2014/12/12 职场文书
应届毕业生自荐信
2015/03/04 职场文书
2015年教学管理工作总结
2015/05/20 职场文书
2016年九九重阳节活动总结
2016/04/01 职场文书
私人贷款担保书该怎么写呢?
2019/07/02 职场文书