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 相关文章推荐
防止网站内容被拷贝的一些方法与优缺点好处与坏处分析
Nov 30 Javascript
跟着JQuery API学Jquery 之三 筛选
Apr 09 Javascript
点击button获取text内容并改变样式的js实现
Sep 09 Javascript
解决node-webkit 不支持html5播放mp4视频的方法
Mar 11 Javascript
JavaScript中constructor()方法的使用简介
Jun 05 Javascript
详解js中构造流程图的核心技术JsPlumb
Dec 08 Javascript
jQuery实现鼠标经过像翻页和描点链接效果
Aug 08 Javascript
JavaScript组合模式学习要点
Aug 26 Javascript
angularJS Provider、factory、service详解及实例代码
Sep 21 Javascript
浅谈jquery fullpage 插件增加头部和版权的方法
Mar 20 jQuery
vue实现压缩图片预览并上传功能(promise封装)
Jan 10 Javascript
如何使用Jquery动态生成二级选项列表
Feb 06 jQuery
如何根据业务封装自己的功能组件
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
用mysql_fetch_array()获取当前行数据的方法详解
2013/06/05 PHP
PHP单态模式简单用法示例
2016/11/16 PHP
php 二维数组快速排序算法的实现代码
2017/10/17 PHP
PHP代码加密的方法总结
2020/03/13 PHP
Javascript中定义方法的另类写法(批量定义js对象的方法)
2011/02/25 Javascript
JS实现在页面随时自定义背景颜色的方法
2015/02/27 Javascript
深入探究使JavaScript动画流畅的一些方法
2015/06/30 Javascript
用jquery快速解决IE输入框不能输入的问题
2016/10/04 Javascript
JS函数修改html的元素内容,及修改属性内容的方法
2016/10/28 Javascript
微信小程序 基础知识css样式media标签
2017/02/15 Javascript
通过js动态创建标签,并设置属性方法
2018/02/24 Javascript
js屏蔽退格键(backspace或者叫后退键与F5)
2019/02/10 Javascript
layui递归实现动态左侧菜单
2019/07/26 Javascript
vue实现配置全局访问路径头(axios)
2019/11/01 Javascript
javascript自定义右键菜单插件
2019/12/16 Javascript
使用preload预加载页面资源时注意事项
2020/02/03 Javascript
javascript自定义加载loading效果
2020/09/15 Javascript
[19:15]DK战队纪录片
2014/09/02 DOTA
[00:15]TI9地铁玩家打卡
2019/08/11 DOTA
python查询mysql中文乱码问题
2014/11/09 Python
python实现简单的计时器功能函数
2015/03/14 Python
Python实现的简单hangman游戏实例
2015/06/28 Python
Python命令行解析模块详解
2018/02/01 Python
python图书管理系统
2020/04/05 Python
基于DataFrame筛选数据与loc的用法详解
2018/05/18 Python
Python网络编程之使用TCP方式传输文件操作示例
2019/11/01 Python
Python3 shelve对象持久存储原理详解
2020/03/23 Python
python实现学生成绩测评系统
2020/06/22 Python
css3 矩阵的使用详解
2018/03/20 HTML / CSS
Mamaearth官方网站:印度母婴护理产品公司
2019/10/06 全球购物
全球性的众包图形设计市场:DesignCrowd
2021/02/02 全球购物
What is view? why do we have view?
2012/06/22 面试题
销售工作岗位职责
2013/12/24 职场文书
学习雷锋标语
2014/06/25 职场文书
交通事故一次性赔偿协议书范本
2014/11/02 职场文书
加班费申请报告
2015/05/15 职场文书