1.模块的写法
模块化编程平常都有这么几个过渡历程,以下形貌。
原始要领
function m1(){
//...
}
function m2(){
//...
}
上面的函数m1()和m2(),构成一个模块。运用的时刻,直接挪用就好了。
这类做法的瑕玷很显著:”污染”了全局变量,没法保证不与其他模块发作变量名争执,而且模块成员之间看不出直接关联。
对象写法
为了处理上面的瑕玷,可以把模块写成一个对象,一切的模块成员都放到这个对象内里。
var module1 = new Object({
_count : 0,
m1 : function (){
//...
},
m2 : function (){
//...
}
});
上面的函数m1()和m2(),都封装在module1对象里。运用的时刻,就是挪用这个对象的属性。
module1.m1();
然则,如许的写法会暴露一切模块成员,内部状况可以被外部改写。比方,外部代码可以直接转变内部计数器的值。
module1._count = 5;
马上实行函数写法
运用”马上实行函数”(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模块的基础写法。下面,再对这类写法举行加工。
放大情势
假如一个模块很大,必需分红几个部份,或许一个模块须要继续另一个模块,这时候就有必要采纳”放大情势”(augmentation)。
var module1 = (function (mod){
mod.m3 = function () {
//...
};
return mod;
})(module1);
上面的代码为module1模块添加了一个新要领m3(),然后返回新的module1模块。
宽放大情势(Loose augmentation)
在浏览器环境中,模块的各个部份一般都是从网上猎取的,偶然没法晓得哪一个部份会先加载。假如采纳上一节的写法,第一个实行的部份有可以加载一个不存在空对象,这时候就要采纳”宽放大情势”。
var module1 = ( function (mod){
//...
return mod;
})(window.module1 || {});
与”放大情势”比拟,"宽放大情势"就是”马上实行函数”的参数可以是空对象。
输入全局变量
独立性是模块的重要特性,模块内部最好不与顺序的其他部份直接交互。
为了在模块内部挪用全局变量,必需显式地将其他变量输入模块。
var module1 = (function ($, YAHOO) {
//...
})(jQuery, YAHOO);
上面的module1模块须要运用jQuery库和YUI库,就把这两个库(实际上是两个模块)看成参数输入module1。如许做除了保证模块的独立性,还使得模块之间的依靠关联变得显著。
2.AMD范例
2009年,美国顺序员Ryan Dahl制造了node.js项目,将javascript言语用于服务器端编程。
这标志”Javascript模块化编程”正式降生。因为老实说,在浏览器环境下,没有模块也不是迥殊大的题目,毕竟网页顺序的复杂性有限;然则在服务器端,肯定要有模块,与操作体系和其他运用顺序互动,不然基础没法编程。
node.js的模块体系,就是参照CommonJS范例完成的。在CommonJS中,有一个全局性要领require(),用于加载模块。假定有一个数学模块math.js,就可以像下面如许加载。
var math = require('math');
然后,就可以挪用模块供应的要领:
var math = require('math');
math.add(2,3); // 5
因为这个系列重要针对浏览器编程,不触及node.js,所以对CommonJS就不多做引见了。我们在这里只需晓得,require()用于加载模块就好了。
有了服务器端模块今后,很天然地,人人就想要客户端模块。而且最好二者可以兼容,一个模块不必修正,在服务器和浏览器都可以运转。
然则,因为一个严重的范围,使得CommonJS范例不适用于浏览器环境。照样上一节的代码,假如在浏览器中运转,会有一个很大的题目,你能看出来吗?
var math = require('math');
math.add(2, 3);
第二行math.add(2, 3),在第一行require(‘math’)以后运转,因而必需等math.js加载完成。也就是说,假如加载时候很长,全部运用就会停在那边等。
这对服务器端不是一个题目,因为一切的模块都存放在当地硬盘,可以同步加载完成,等待时候就是硬盘的读取时候。然则,关于浏览器,这倒是一个大题目,因为模块都放在服务器端,等待时候取决于网速的快慢,可以要等很长时候,浏览器处于”假死”状况。
因而,浏览器端的模块,不能采纳”同步加载”(synchronous),只能采纳”异步加载”(asynchronous)。这就是AMD范例降生的背景。
AMD是”Asynchronous Module Definition”的缩写,意义就是”异步模块定义”。它采纳异步体式格局加载模块,模块的加载不影响它背面语句的运转。一切依靠这个模块的语句,都定义在一个回调函数中,比及加载完成以后,这个回调函数才会运转。
AMD也采纳require()语句加载模块,然则不同于CommonJS,它请求两个参数:
require([module], callback);
第一个参数[module],是一个数组,内里的成员就是要加载的模块;第二个参数callback,则是加载胜利以后的回调函数。假如将前面的代码改写成AMD情势,就是下面如许:
require(['math'], function (math) {
math.add(2, 3);
});
math.add()与math模块加载不是同步的,浏览器不会发作假死。所以很显然,AMD比较合适浏览器环境。
3.require.js的加载
<script src="js/require.js"></script>
有人可以会想到,加载这个文件,也可以形成网页落空相应。处理办法有两个,一个是把它放在网页底部加载,另一个是写成下面如许:
<script src="js/require.js" defer async="true" ></script>
async属性表明这个文件须要异步加载,防止网页落空相应。IE不支撑这个属性,只支撑defer,所以把defer也写上。
加载require.js今后,下一步就要加载我们本身的代码了。假定我们本身的代码文件是main.js,也放在js目次下面。那末,只须要写成下面如许就好了:
<script src="js/require.js" data-main="js/main"></script>
data-main属性的作用是,指定网页顺序的主模块。在上例中,就是js目次下面的main.js,这个文件会第一个被require.js加载。因为require.js默许的文件后缀名是js,所以可以把main.js简写成main。
require.config()的设置
运用require.config()要领,我们可以对模块的加载行动举行自定义。require.config()就写在主模块(main.js)的头部。参数就是一个对象,这个对象的paths属性指定各个模块的加载途径。
require.config({
baseUrl: "js/lib",
paths: {
"jquery": "jquery.min",
"underscore": "underscore.min",
"backbone": "backbone.min"
}
});
AMD模块的写法
模块必需采纳特定的define()函数来定义。假如一个模块不依靠其他模块,那末可以直接定义在define()函数当中。
假定如今有一个math.js文件,它定义了一个math模块。那末,math.js就要如许写:
// math.js
define(function (){
var add = function (x,y){
return x+y;
};
return {
add: add
};
});
加载要领以下:
// main.js
require(['math'], function (math){
alert(math.add(1,1));
});
假如这个模块还依靠其他模块,那末define()函数的第一个参数,必需是一个数组,指明该模块的依靠性。
define(['myLib'], function(myLib){
function foo(){
myLib.doSomething();
}
return {
foo : foo
};
});
当require()函数加载上面这个模块的时刻,就会先加载myLib.js文件。
define()的完全定义:
define('sample3' ,['sample','sample1'],function (sample,sample1) {
var sample4 = require('sample4');
return function(){
alert(sample.name+':'+sample.sayhell());
}
});
关于define函数的name和require函数的依靠称号之间的关联
1)define(name,[] , callback); 这个name可以免却,默许是文件称号;固然也可以自定义,一旦我们定义了name,依据源代码我们可以发明define函数内部实在就是把这个name以及依靠模块、回调函数作为一个对象存储在全局的数组当中,也就是 defQueue.push([name,deps,callback]);那末这个name就是这个组件注册的的ID!
2)require([name , name2],callback); 体系起首会在全文检索path中是不是对应的途径,假如没有天然把他作为途径拼接在baseUrl上去异步加载这个js文件,加载时从源代码中可以看到 ,var data = getScriptData(evt);返回的 data.id 实在就是name,然后实行contex.completeLoad(node.id),其内部就很清晰了,把define中注册的name和这里获得的name举行比较假如相称就实行,所以原理就是:require 和 define 的 name 必需保证一致!
标签加载完成以后,猎取标签的唯一标识name
加载非范例的模块
举例来说,underscore和backbone这两个库,都没有采纳AMD范例编写。假如要加载它们的话,必需先定义它们的特性。
require.config({
shim: {
'underscore':{
exports: '_'
},
'backbone': {
deps: ['underscore', 'jquery'],
exports: 'Backbone'
}
}
});
require.config()接收一个设置对象,这个对象除了有前面说过的paths属性以外,另有一个shim属性,特地用来设置不兼容的模块。具体来说,每一个模块要定义(1)exports值(输出的变量名),表明这个模块外部挪用时的称号;(2)deps数组,表明该模块的依靠性。
比方,jQuery的插件可以如许定义:
shim: {
'jquery.scroll': {
deps: ['jquery'],
exports: 'jQuery.fn.scroll'
}
}
require.js插件
require.js还供应一系列插件,完成一些特定的功用。
domready插件,可以让回调函数在页面DOM构造加载完成后再运转。
require(['domready!'], function (doc){
// called once the DOM is ready
});
text和image插件,则是许可require.js加载文本和图片文件。
define([
'text!review.txt',
'image!cat.jpg'
],
function(review,cat){
console.log(review);
document.body.appendChild(cat);
}
);
相似的插件另有json和mdown,用于加载json文件和markdown文件。
参考地点:
Javascript模块化编程(一):模块的写法
Javascript模块化编程(二):AMD范例
Javascript模块化编程(三):require.js的用法
RequireJS define 细致引见