前端模块化,相信这个词对于做前端的同学来说,绝对不陌生,你对前端模块化的了解有多少呢?在这里我们就来聊聊前端模块化。
一.前端为什么要模块化?
javascript这门语言被创造出来时,它只是基于浏览器的。但自1996年以来,已经有各种各样的JavaScript平台可以在浏览器之外进行编程,官方定义的API只能构建基于浏览器的应用程序。人们逐渐认识到了问题的严重性:
- JavaScript没有模块系统。要编写JavaScript脚本,必须以HTML格式管理,连接,注入或手动获取和评估它们。没有用于范围隔离或依赖关系管理的本机工具。
- JavaScript没有标准库。它有浏览器API,日期和数学,但没有文件系统API,更不用说IO流API或二进制数据的原始类型。
- JavaScript没有Web服务器或数据库之类的标准接口。
- JavaScript没有管理依赖关系并自动安装它们的软件包管理系统,除了JSAN(不要与JSON混淆),这不符合范围隔离。
二.CommonJS,AMD,CMD与ES6模块化
1.CommonJS
2009年,CommonJS出现了,CommonJS 通过定义处理许多常见应用程序需求的API来填补这一空白,最终提供与Python,Ruby和Java一样丰富的标准库。目的是应用程序开发人员能够使用CommonJS API编写应用程序,然后跨不同的JavaScript解释器和主机环境运行该应用程序。同时,美国程序员Ryan Dahl创造了node.js项目,将javascript语言用于服务器端编程,node.js是对commonJS规范的实现,这标志"Javascript模块化编程"正式诞生。关于nodejs和commonjs在阮一峰老师的这篇文章中有详细描述http://javascript.ruanyifeng.com/nodejs/module.html
原理
浏览器不兼容CommonJS的根本原因,在于缺少四个Node.js环境的变量。
- module
- exports
- require
- global
只要能够提供这四个变量,浏览器就能加载 CommonJS 模块。
下面是一个简单的示例。
var module = { exports: {} }; (function(module, exports) { exports.multiply = function (n) { return n * 1000 }; }(module, module.exports)) var f = module.exports.multiply; f(5) // 5000
上面代码向一个立即执行函数提供 module 和 exports 两个外部变量,模块就放在这个立即执行函数里面。模块的输出值放在 module.exports 之中,这样就实现了模块的加载。
Browserify 的实现
知道了原理,就能做出工具了。Browserify 是目前最常用的 CommonJS 格式转换的工具。
请看一个例子,main.js 模块加载 foo.js 模块。
// foo.js module.exports = function(x) { console.log(x); }; // main.js var foo = require("./foo"); foo("Hi");
使用下面的命令,就能将main.js转为浏览器可用的格式。
$ browserify main.js > compiled.js
Browserify到底做了什么?安装一下browser-unpack,就能看清楚了。
$ npm install browser-unpack -g
然后,将前面生成的compile.js解包。
$ browser-unpack < compiled.js [ { "id":1, "source":"module.exports = function(x) {\n console.log(x);\n};", "deps":{} }, { "id":2, "source":"var foo = require(\"./foo\");\nfoo(\"Hi\");", "deps":{"./foo":1}, "entry":true } ]
可以看到,browerify 将所有模块放入一个数组,id 属性是模块的编号,source 属性是模块的源码,deps 属性是模块的依赖。
因为 main.js 里面加载了 foo.js,所以 deps 属性就指定 ./foo 对应1号模块。执行的时候,浏览器遇到 require('./foo') 语句,就自动执行1号模块的 source 属性,并将执行后的 module.exports 属性值输出。
2.AMD
AMD就只有一个接口:define(id?,dependencies?,factory);
它要在声明模块的时候制定所有的依赖(dep),并且还要当做形参传到factory中,像这样:
define(['dep1','dep2'],function(dep1,dep2){...});
要是没什么依赖,就定义简单的模块,下面这样就可以啦
define(function(){
var exports = {};
exports.method = function(){...};
return exports;
});
这里有define,把东西包装起来,那Node实现中怎么没看到有define关键字呢,它也要把东西包装起来呀,其实吧,只是Node隐式包装了而已.....
这有AMD的WIKI中文版,讲了很多蛮详细的东西,用到的时候可以查看:AMD的WIKI中文版
3、CMD
大名远扬的玉伯写了seajs,就是遵循他提出的CMD规范,与AMD蛮相近的,不过用起来感觉更加方便些,最重要的是中文版,应有尽有:seajs官方doc
1 define(function(require,exports,module){...});
用过seajs吧,这个不陌生吧,对吧。
前面说AMD,说RequireJS实现了AMD,CMD看起来与AMD好像呀,那RequireJS与SeaJS像不像呢?
虽然CMD与AMD蛮像的,但区别还是挺明显的,官方非官方都有阐述和理解,我觉得吧,说的都挺好:
以上这些只是为了学习做的总结,有部分摘自大牛原话,本人只是为了学习方便做的笔记,后续会陆续加一些自己的理解。如有侵权,联系必删,致敬大牛!