javascript模块化开发
1、javascript模块的原始写法
模块就是实现特定功能的一组方法。
只要把不同的函数(以及记录状态的变量)简单地放在一起,就算是一个模块。
function m1(){
//...
}
function m2(){
//...
}
上面的函数m1()和m2(),组成一个模块。使用的时候,直接调用就行了。
这种做法的缺点很明显:"污染"了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接关系。
2、对象写法
为了解决上面的缺点,可以把模块写成一个对象,所有的模块成员都放到这个对象里面。
var module1 = new Object({
_count : 0,
m1 : function (){
//...
},
m2 : function (){
//...
}
});
上面的函数m1()和m2(),都封装在module1对象里。使用的时候,就是调用这个对象的属性。
module1.m1();
但是,这样的写法会暴露所有模块成员,内部状态可以被外部改写。比如,外部代码可以直接改变内部计数器的值。
module1._count = 5;
3、立即执行函数写法
使用"立即执行函数"(Immediately-Invoked Function Expression,IIFE),可以达到不暴露私有成员的目的。
var module1 = (function(){
var _count = 0;
var m1 = function(){
//...
};
var m2 = function(){
//...
};
return {
m1 : m1,
m2 : m2
};
})();
使用上面的写法,外部代码无法读取内部的_count变量。
console.info(module1._count); //undefined
module1就是Javascript模块的基本写法。下面,再对这种写法进行加工。
4、放大模式
如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,这时就有必要采用"放大模式"(augmentation)。
var module2 = (function (mod){
mod.m3 = function () {
//...
};
return mod;
})(module1);
上面的代码为module1模块添加了一个新方法m3(),然后返回新的module2模块。
5、使用AMD规范或者CMD规范的方式
如果是使用了require.js情况,使用define函数暴露模块;
如果是使用sea.js情况,使用exports暴露模块;
如果都没有,将模块绑定到全局变量window对象上。
以该插件需要引入jquery模块为例
(function(root,factory){}(this,function($){
}));
在需要引入jquery和该插件的html代码中,编写以下代码
<script src="jquery.js"></script>
<script src="该插件.js"></script>
由于jquery插件已经实现了模块化,即以一个对象作为模块对外暴露。该模块名既可以叫jQuery,也可以叫$,它们是等同的。
在主文件的这段html代码中,引入jquery文件<script src="jquery.js"></script>相当于声明了对象:
var jQuery=....;
或者var $=....;
随后引入该插件<script src="该插件.js"></script>,该插件的代码内部是一段立即执行函数
(function(root,factory){}(this,function($){
}));
这个立即执行函数表明,它以window对象作为第一个参数(在window执行作用域内执行这段代码),以一个函数作为第二个参数,
而 这个函数的参数正是前面引入jquery文件而造成声明的对象$。
我们再以一段代码具体看这个立即执行函数内部做了什么。
;(function (root, factory) {
'use strict'
if (typeof define === 'function' && define.amd) {
define('ajax', factory)
} else if (typeof exports === 'object') {
exports = module.exports = factory()
} else {
// @deprecated
root.Ajax = factory()
root.ajax = factory()
}
})(this, function () {
'use strict'
function Ajax (options) {/* code */}
return Ajax
})
如果我们了解AMD和CMD规范,了解使用require.js或者sea.js 的情况
// AMD 定义模块
define(['./model-a', './model-b'], 'name', function(model-a, model-b){
model-a.doSomething();
model-b.doSomething();
// factory code;
})
// CMD 定义模块
define(function (require, exports, module){
function factory(){};
module.exports = factory; // 导出需要公开的api
})
如果你用的是jQuery1.7版本以上的,可以看下jQuery源码的最后几行,你会发现类似下方的代码:
if ( typeof define === "function" && define.amd && define.amd.jQuery ) {
define( "jquery", [], function () { return jQuery; } );
}
我们再看看上面立即执行函数的那段代码,此时就很清晰了
if (typeof define === 'function' && define.amd) {
// 使用 require.js 的情况
define('ajax', factory)
} else if (typeof exports === 'object') {
// 使用 sea.js 的情况
exports = module.exports = factory()
} else {
// @deprecated
// 这是传入的 root === window
// 绑定到全局变量上
root.Ajax = factory()
root.ajax = factory()
}
既然CommonJs和AMD风格一样流行,似乎缺少一个统一的规范。所以人们产生了这样的需求,希望有支持两种风格的“通用”模式,于是通用模块规范(UMD)诞生了。
不得不承认,这个模式略难看,但是它兼容了AMD和CommonJS,同时还支持老式的“全局”变量规范:
以dateRangePicker.js(version: 2.1.30)为例,使用UMD规范引入依赖模块moment和jquery:
1、javascript模块的原始写法
模块就是实现特定功能的一组方法。
只要把不同的函数(以及记录状态的变量)简单地放在一起,就算是一个模块。
function m1(){
//...
}
function m2(){
//...
}
上面的函数m1()和m2(),组成一个模块。使用的时候,直接调用就行了。
这种做法的缺点很明显:"污染"了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接关系。
2、对象写法
为了解决上面的缺点,可以把模块写成一个对象,所有的模块成员都放到这个对象里面。
var module1 = new Object({
_count : 0,
m1 : function (){
//...
},
m2 : function (){
//...
}
});
上面的函数m1()和m2(),都封装在module1对象里。使用的时候,就是调用这个对象的属性。
module1.m1();
但是,这样的写法会暴露所有模块成员,内部状态可以被外部改写。比如,外部代码可以直接改变内部计数器的值。
module1._count = 5;
3、立即执行函数写法
使用"立即执行函数"(Immediately-Invoked Function Expression,IIFE),可以达到不暴露私有成员的目的。
var module1 = (function(){
var _count = 0;
var m1 = function(){
//...
};
var m2 = function(){
//...
};
return {
m1 : m1,
m2 : m2
};
})();
使用上面的写法,外部代码无法读取内部的_count变量。
console.info(module1._count); //undefined
module1就是Javascript模块的基本写法。下面,再对这种写法进行加工。
4、放大模式
如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,这时就有必要采用"放大模式"(augmentation)。
var module2 = (function (mod){
mod.m3 = function () {
//...
};
return mod;
})(module1);
上面的代码为module1模块添加了一个新方法m3(),然后返回新的module2模块。
5、使用AMD规范或者CMD规范的方式
如果是使用了require.js情况,使用define函数暴露模块;
如果是使用sea.js情况,使用exports暴露模块;
如果都没有,将模块绑定到全局变量window对象上。
以该插件需要引入jquery模块为例
(function(root,factory){}(this,function($){
}));
在需要引入jquery和该插件的html代码中,编写以下代码
<script src="jquery.js"></script>
<script src="该插件.js"></script>
由于jquery插件已经实现了模块化,即以一个对象作为模块对外暴露。该模块名既可以叫jQuery,也可以叫$,它们是等同的。
在主文件的这段html代码中,引入jquery文件<script src="jquery.js"></script>相当于声明了对象:
var jQuery=....;
或者var $=....;
随后引入该插件<script src="该插件.js"></script>,该插件的代码内部是一段立即执行函数
(function(root,factory){}(this,function($){
}));
这个立即执行函数表明,它以window对象作为第一个参数(在window执行作用域内执行这段代码),以一个函数作为第二个参数,
而 这个函数的参数正是前面引入jquery文件而造成声明的对象$。
我们再以一段代码具体看这个立即执行函数内部做了什么。
;(function (root, factory) {
'use strict'
if (typeof define === 'function' && define.amd) {
define('ajax', factory)
} else if (typeof exports === 'object') {
exports = module.exports = factory()
} else {
// @deprecated
root.Ajax = factory()
root.ajax = factory()
}
})(this, function () {
'use strict'
function Ajax (options) {/* code */}
return Ajax
})
如果我们了解AMD和CMD规范,了解使用require.js或者sea.js 的情况
// AMD 定义模块
define(['./model-a', './model-b'], 'name', function(model-a, model-b){
model-a.doSomething();
model-b.doSomething();
// factory code;
})
// CMD 定义模块
define(function (require, exports, module){
function factory(){};
module.exports = factory; // 导出需要公开的api
})
如果你用的是jQuery1.7版本以上的,可以看下jQuery源码的最后几行,你会发现类似下方的代码:
if ( typeof define === "function" && define.amd && define.amd.jQuery ) {
define( "jquery", [], function () { return jQuery; } );
}
我们再看看上面立即执行函数的那段代码,此时就很清晰了
if (typeof define === 'function' && define.amd) {
// 使用 require.js 的情况
define('ajax', factory)
} else if (typeof exports === 'object') {
// 使用 sea.js 的情况
exports = module.exports = factory()
} else {
// @deprecated
// 这是传入的 root === window
// 绑定到全局变量上
root.Ajax = factory()
root.ajax = factory()
}
参考文章:
====================================
AMD/CMD/CommonJs到底是什么?它们有什么区别?
知识点1:AMD/CMD/CommonJs是JS模块化开发的标准,目前对应的实现是RequireJs/SeaJs/nodeJs.
知识点2:CommonJs主要针对服务端,AMD/CMD主要针对浏览器端,所以最容易混淆的是AMD/CMD。(顺便提一下,针对服务器端和针对浏览器端有什么本质的区别呢?服务器端一般采用同步加载文件,也就是说需要某个模块,服务器端便停下来,等待它加载再执行。这里如果有其他后端语言,如java,经验的‘玩家’应该更容易理解。而浏览器端要保证效率,需要采用异步加载,这就需要一个预处理,提前将所需要的模块文件并行加载好。)
知识点3 : AMD/CMD区别,虽然都是并行加载js文件,但还是有所区别,AMD是预加载,在并行加载js文件同时,还会解析执行该模块(因为还需要执行,所以在加载某个模块前,这个模块的依赖模块需要先加载完成);而CMD是懒加载,虽然会一开始就并行加载js文件,但是不会执行,而是在需要的时候才执行。
知识点4:AMD/CMD的优缺点.一个的优点就是另一个的缺点, 可以对照浏览。
AMD优点:加载快速,尤其遇到多个大文件,因为并行解析,所以同一时间可以解析多个文件。
AMD缺点:并行加载,异步处理,加载顺序不一定,可能会造成一些困扰,甚至为程序埋下大坑。
CMD优点:因为只有在使用的时候才会解析执行js文件,因此,每个JS文件的执行顺序在代码中是有体现的,是可控的。
CMD缺点:执行等待时间会叠加。因为每个文件执行时是同步执行(串行执行),因此时间是所有文件解析执行时间之和,尤其在文件较多较大时,这种缺点尤为明显。
知识点5:如何使用?CommonJs的话,因为nodeJs就是它的实现,所以使用node就行,也不用引入其他包。AMD则是通过<script>标签引入RequireJs,具体语法还是去看官方文档或者百度一下吧。CMD则是引入SeaJs。
===========================
AMD
随着RequireJS成为最流行的实现方式,异步模块规范(AMD)在前端界已经被广泛认同。
下面是只依赖jquery的模块foo的代码:
// 文件名: foo.js
define(['jquery'], function ($) {
// 方法
function myFunc(){};
// 暴露公共方法
return myFunc;
});
还有稍微复杂点的例子,下面的代码依赖了多个组件并且暴露多个方法:
// 文件名: foo.js
define(['jquery', 'underscore'], function ($, _) {
// 方法
function a(){}; // 私有方法,因为没有被返回(见下面)
function b(){}; // 公共方法,因为被返回了
function c(){}; // 公共方法,因为被返回了
// 暴露公共方法
return {
b: b,
c: c
}
});
定义的第一个部分是一个依赖数组,第二个部分是回调函数,只有当依赖的组件可用时(像RequireJS这样的脚本加载器会负责这一部分,包括找到文件路径)回调函数才被执行。
注意,依赖组件和变量的顺序是一一对应的(例如,jquery->$, underscore->_)。
同时注意,我们可以用任意的变量名来表示依赖组件。假如我们把$改成$$,在函数体里面的所有对jQuery的引用都由$变成了$$。
还要注意,最重要的是你不能在回调函数外面引用变量$和_,因为它相对其它代码是独立的。这正是模块化的目的所在!
CommonJS
如果你用Node写过东西的话,你可能会熟悉CommonJS的风格(node使用的格式与之相差无几)。因为有Browserify,它也一直被前端界广泛认同。
就像前面的格式一样,下面是用CommonJS规范实现的foo模块的写法:
// 文件名: foo.js
// 依赖
var $ = require('jquery');
// 方法
function myFunc(){};
// 暴露公共方法(一个)
module.exports = myFunc;
还有更复杂的例子,下面的代码依赖了多个组件并且暴露多个方法:
// 文件名: foo.js
var $ = require('jquery');
var _ = require('underscore');
// methods
function a(){}; // 私有方法,因为它没在module.exports中 (见下面)
function b(){}; // 公共方法,因为它在module.exports中定义了
function c(){}; // 公共方法,因为它在module.exports中定义了
// 暴露公共方法
module.exports = {
b: b,
c: c
};
UMD: 通用模块规范
既然CommonJs和AMD风格一样流行,似乎缺少一个统一的规范。所以人们产生了这样的需求,希望有支持两种风格的“通用”模式,于是通用模块规范(UMD)诞生了。
不得不承认,这个模式略难看,但是它兼容了AMD和CommonJS,同时还支持老式的“全局”变量规范:
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// Node, CommonJS之类的
module.exports = factory(require('jquery'));
} else {
// 浏览器全局变量(root 即 window)
root.returnExports = factory(root.jQuery);
}
}(this, function ($) {
// 方法
function myFunc(){};
// 暴露公共方法
return myFunc;
}));
保持跟上面例子一样的模式,下面是更复杂的例子,它依赖了多个组件并且暴露多个方法:
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery', 'underscore'], factory);
} else if (typeof exports === 'object') {
// Node, CommonJS之类的
module.exports = factory(require('jquery'), require('underscore'));
} else {
// 浏览器全局变量(root 即 window)
root.returnExports = factory(root.jQuery, root._);
}
}(this, function ($, _) {
// 方法
function a(){}; // 私有方法,因为它没被返回 (见下面)
function b(){}; // 公共方法,因为被返回了
function c(){}; // 公共方法,因为被返回了
// 暴露公共方法
return {
b: b,
c: c
}
}));
更详尽的UMD代码模板,可以看github上的
umdJs项目
以dateRangePicker.js(version: 2.1.30)为例,使用UMD规范引入依赖模块moment和jquery:
// Follow the UMD template https://github.com/umdjs/umd/blob/master/templates/returnExportsGlobal.js
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Make globaly available as well
define(['moment', 'jquery'], function (moment, jquery) {
if (!jquery.fn) jquery.fn = {}; // webpack server rendering
return factory(moment, jquery);
});
} else if (typeof module === 'object' && module.exports) {
// Node / Browserify
//isomorphic issue
var jQuery = (typeof window != 'undefined') ? window.jQuery : undefined;
if (!jQuery) {
jQuery = require('jquery');
if (!jQuery.fn) jQuery.fn = {};
}
var moment = (typeof window != 'undefined' && typeof window.moment != 'undefined') ? window.moment : require('moment');
module.exports = factory(moment, jQuery);
} else {
// Browser globals
root.daterangepicker = factory(root.moment, root.jQuery);
}
}(this, function(moment, $) {
........
以moment.js(version : 2.5.1)为例,使用UMD规范导出模块moment:
(function (undefined) {
/************************************
Constants
************************************/
var moment,
VERSION = "1.0.0",
global = this,
// check for nodeJS
hasModule = (typeof module !== 'undefined' && module.exports && typeof require !== 'undefined');
// .................................................. 内容省略
/************************************
Exposing Moment
************************************/
function makeGlobal(deprecate) {
var warned = false, local_moment = moment;
/*global ender:false */
if (typeof ender !== 'undefined') {
return;
}
// here, `this` means `window` in the browser, or `global` on the server
// add `moment` as a global object via a string identifier,
// for Closure Compiler "advanced" mode
if (deprecate) {
global.moment = function () {
if (!warned && console && console.warn) {
warned = true;
console.warn(
"Accessing Moment through the global scope is " +
"deprecated, and will be removed in an upcoming " +
"release.");
}
return local_moment.apply(null, arguments);
};
extend(global.moment, local_moment);
} else {
global['moment'] = moment;
}
}
// CommonJS module is defined
if (hasModule) {
module.exports = moment;
makeGlobal(true);
} else if (typeof define === "function" && define.amd) {
define("moment", function (require, exports, module) {
if (module.config && module.config() && module.config().noGlobal !== true) {
// If user provided noGlobal, he is aware of global
makeGlobal(module.config().noGlobal === undefined);
}
return moment;
});
} else {
makeGlobal();
}
}).call(this);