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 蒙版进度条(结合图片)
Mar 10 Javascript
jquery ui dialog ie8出现滚动条的解决方法
Dec 06 Javascript
js数组Array sort方法使用深入分析
Feb 21 Javascript
JavaScript提高性能知识点汇总
Jan 15 Javascript
JS去除空格和换行的正则表达式(推荐)
Jun 14 Javascript
基于cssSlidy.js插件实现响应式手机图片轮播效果
Aug 30 Javascript
javascript 中iframe高度自适应(同域)实例详解
May 16 Javascript
基于vue.js组件实现分页效果
Dec 29 Javascript
nvm、nrm、npm 安装和使用详解(小结)
Jan 17 Javascript
微信小程序点击图片实现长按预览、保存、识别带参数二维码、转发等功能
Jul 20 Javascript
详解webpack-dev-middleware 源码解读
Mar 23 Javascript
如何在 Vue 中使用 JSX
Feb 14 Vue.js
如何根据业务封装自己的功能组件
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
超级简单的发送邮件程序
2006/10/09 PHP
PHP中使用Imagick实现各种图片效果实例
2015/01/21 PHP
微信支付PHP SDK之微信公众号支付代码详解
2015/12/09 PHP
PHP长连接实现与使用方法详解
2018/02/11 PHP
ASP.NET jQuery 实例12 通过使用jQuery validation插件简单实现用户注册页面验证功能
2012/02/03 Javascript
Uglifyjs(JS代码优化工具)入门 安装使用
2020/04/13 Javascript
通过jQuery源码学习javascript(一)
2012/12/27 Javascript
Raphael一个用于在网页中绘制矢量图形的Javascript库
2013/01/08 Javascript
JavaScript二维数组实现的省市联动菜单
2014/05/08 Javascript
Javascript表单验证要注意的事项
2014/09/29 Javascript
jQuery中last()方法用法实例
2015/01/06 Javascript
多个js毫秒倒计时同时进行效果
2016/01/05 Javascript
jsp 自动编译机制详细介绍
2016/12/01 Javascript
nodejs批量下载图片的实现方法
2017/05/19 NodeJs
Webpack执行命令参数详解
2017/06/17 Javascript
Angularjs的键盘事件的绑定
2017/07/27 Javascript
vue 2.0项目中如何引入element-ui详解
2017/09/06 Javascript
js的对象与函数详解
2019/01/21 Javascript
JavaScript HTML DOM元素 节点操作汇总
2019/07/29 Javascript
JavaScript实现单图片上传并预览功能
2019/09/30 Javascript
Vue + ts实现轮播插件的示例
2020/11/10 Javascript
python在Windows下安装setuptools(easy_install工具)步骤详解
2016/07/01 Python
搭建Python的Django框架环境并建立和运行第一个App的教程
2016/07/02 Python
Pyhton中单行和多行注释的使用方法及规范
2016/10/11 Python
python使用rsa非对称加密过程解析
2019/12/28 Python
利用jupyter网页版本进行python函数查询方式
2020/04/14 Python
Python实现清理微信僵尸粉功能示例【基于itchat模块】
2020/05/29 Python
Spring @Enable模块驱动原理及使用实例
2020/06/23 Python
html5 Canvas画图教程(1)—画图的基本常识
2013/01/09 HTML / CSS
html5 video全屏播放/自动播放的实现示例
2020/08/06 HTML / CSS
英国花园家具中心:Garden Furniture Centre
2017/08/24 全球购物
鉴定评语大全
2014/05/05 职场文书
超市开业庆典活动策划方案
2014/09/15 职场文书
整改报告怎么写
2014/11/06 职场文书
消费者理赔投诉书
2015/07/02 职场文书
Python 批量下载阴阳师网站壁纸
2021/05/19 Python