(1)首先想到的是函数封装。
但是函数封装有问题,污染了全局变量,无法保证不与其他模块发生冲突。
而且模块之间没有什么关系。
(2)对象的形式
可以把模块成员封装在一个函数中。这样我们可以调用模块是,引入不同的文件,
然后调用相应的函数即可。这样避免了变量污染,只要保证模块名称唯一就可。
看似不错,但是有个问题,外部可以随意改动内部成员。就会有安全问题。
(3)立刻执行函数
通过立刻执行函数,可以达到隐藏细节,不暴露隐私的问题。(function())();
这个就是JS现在模块化的基础。CMD和AMD
首先是CommonJS
nodejs的引入,某种程度上,就标志了js模块化时代的来临。
定义模块:根据CommonJS的规范,一个单独的文件就是一个模块,每一个模块都是一个单独的
作用域,也就是说,该模块内部的变量,就是内部变量,外部无法获取,除非是global对象属性。
模块输出:模块有一个出口,module.exports对象,
加载模块:使用require方法,该方法,读取一个文件,并执行,
返回文件内部的module.exports对象。
例子: //模块定义 myModel.js var name = 'test'; function printName(){ console.log(name); } function printFullName(firstName){ console.log(firstName + name); } module.exports = { printName: printName, printFullName: printFullName } //加载模块 var nameModule = require('./myModel.js'); nameModule.printName();
上面的例子是同步实现的,require是同步的,模块系统同步读取模块文件内容,并执行。得到相应接口。
浏览器端,加载JavaScript最佳、最容易的方式是在document中插入script标签。
但脚本标签天生异步,传统CommonJS模块在浏览器环境中无法正常加载。
现有2种解决思路,
第一个是:服务器端的组件,对代码进行分析,然后随同http请求,返回给web页面
第二个是:写一个标准模板进行封装定义,然后再对模板进行解析和加载
OK,这样就引出了两种加载方式。AMD和CMD
AMD是"异步模块定义",但是原生JS明显不是很支持这个,就有了RequireJS。
RequireJS解决了2个问题:
首先是文件依赖关系,多个JS之间可能存在相互关系,可能要保证执行顺序,所以,将早于依赖它的文件加载到浏览器中。
其次是,js文件加载,会使浏览器会停止渲染,加载文件越多,页面失去响应时间越长,
例子: // 定义模块 Module.js define(['dependency'], function(){ var name = 'Byron'; function printName(){ console.log(name); } function add(x, y) { return x + y; } return { printName: printName, add : add }; }); // 加载模块 require(['Module'], function (my){ my.printName(); my.add(1, 2); });
my.add()与Module模块加载不是同步的,浏览器不会发生假死。所以很显然,AMD比较适合浏览器环境。
requireJS定义了一个函数 define,它是全局变量,用来定义模块
define([id], [dependencies], factory)
id: 可选参数,用来定义模块的标识,如果没有提供参数,脚本文件名,就去掉扩展名
dependencies:是一个当前模块以来的模块名称数组。
factory: 工厂方法,模块初始化要是真的函数或者对象,如果为函数,它应该被执行一次,
如果是对象,此对象应该为模块的输出值。
这里有问,需要学习一下,这里,如果采取同步加载的话,导致了一个问题,因为模块注定都是在服务器上等待被请求,
所以,客户端请求服务器,如果网络很慢,就要等待很久。
require()函数接受两个参数
第一个参数是一个数组,表示所依赖的模块
第二个参数是一个回调函数,当前面指定的模块都加载成功后,它将被调用。加载的模块会以参数形式传入该函数,
从而在回调函数内部就可以使用这些模块
require()函数在加载依赖的函数的时候是异步加载的,这样浏览器不会失去响应,它指定的回调函数,
只有前面的模块都加载成功后,才会运行,解决了依赖性的问题。
写一个完整的requireJS的例子。 (1)test.html文件 <!DOCTYPE html> <html> <head> <title>test</title> <script data-main="js/app.js" src="js/require.js"></script> </head> <body> </body> </html> (2)model.js文件 define(function (){ var add = function (x,y){ return x+y; }; return { add: add }; }); (3)app.js文件。 requirejs.config({ //默认情况下模块所在目录为js/lib baseUrl: 'js', //当模块id前缀为app时,他便由js/app加载模块文件 //这里设置的路径是相对与baseUrl的,不要包含.js paths: { app: 'model.js' } }); // 开始逻辑. requirejs(['model'], function (nameModule) { alert(nameModule.add(1, 2)); }); 执行以后,会打印3.
为什么要使用require.js,因为加载多个javascript文件,加载的时候,浏览器会停止渲染,
加载的文件越多,页面失去响应的时间就越长,其次,js之间存在相互关系,必须严格保证加载顺序,
依赖性大的模块一定要放在后面加载。当依赖关系复杂的时候,代码的可维护性变得困难。
require.js为了解决2个问题。
(1)实现js的异步加载,避免网页失去响应
(2)管理各个模块之间的依赖性,便于代码的编写和维护。
使用:
a。首先去下载require.js
b。引入require.js;
<script src="js/require.js" defer async="true" ></script>
async表明这个文件需要异步加载,避免网页失去响应。IE不支持async,而支持defer。
c.加载自己的代码
<script src="js/require.js" data-main="js/main"></script>
假定,代码是main.js,放到js目录下面。
data-main属性的作用是,指定网页程序的主模块。在上例中,就是js目录下面的main.js,
这个文件会第一个被require.js加载。由于require.js默认的文件后缀名是js,所以可以把main.js简写成main。
d,主模块的写法,main.js。这是整个网页的入口代码,类似于c语言的main函数。
这里遵循AMD规范,引入了A,B,C三个模块。而function回调函数中,对应着三个变量,就是模块所对应的。
但是,触发回调函数的基础是,模块异步加载成功以后。
// main.js
require(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){
// some code here
})
浏览器异步加载模块A,B,C。浏览器不会失去响应。
e. require.config有几种写法。
一:写绝对路径
require.config({
paths: {
"jquery": "lib/jquery.min",
"underscore": "lib/underscore.min",
"backbone": "lib/backbone.min"
}
});
二,写baseUrl,写基本路径。
require.config({
baseUrl: "js/lib",
paths: {
"jquery": "jquery.min",
"underscore": "underscore.min",
"backbone": "backbone.min"
}
});
三,如果模块在另一个主机上面,写个链接地址,比如CND
require.config({
paths: {
"jquery": "https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min"
}
});
f。采用AMD规范,就要写define方法
(1)如果定义的模块不依赖于其他模块,就直接define就好
比如:
<!-- math.js -->
define(function () {
var add = function (x, y) {
reuturn x + y;
};
retrun {
add:add
};
});
<!-- main.js -->这里直接调用就可以了,
require(['math'], function (math){
alert(math.add(1,1));
});
(2)如果依赖模块,就将依赖的模块加载。
define(['math'], function(math){
function foo(x, y, z){
return math.add(x, y) + z;
}
return {
foo : foo
};
});
当require()函数加载上面这个模块的时候,就会先加载math.js文件。
最后一个例子 (1)test.html <!DOCTYPE html> <html> <head> <title>test </title> <script data-main="js/app.js" src="js/require.js"></script> </head> <body> </body> </html> data-main="js/app.js" 设定了app,js是入口文件。 (2)model.js define(function (){ var add = function (x,y){ return x+y; }; alert(' function model add'); return { add: add }; }); (3)test.js;依赖于model模块 define(['model'], function(model){ function foo(x, y, z){ return model.add(x, y) + z; } function fun(x, y, z) { return model.add(x, y) - z; } return { foo : foo, fun : fun }; }); (4)app.js requirejs.config({ //默认情况下模块所在目录为js/lib baseUrl: 'js', //当模块id前缀为app时,他便由js/app加载模块文件 //这里设置的路径是相对与baseUrl的,不要包含.js paths: { 'model' : 'model', 'test' : "test" } });
// 开始逻辑.
requirejs(['model', 'test'], function (nameModule, test) {
alert(nameModule.add(1, 2));
alert(test.fun(1,2,3));
});
//加载了model和test模块。
// 开始逻辑.
requirejs(['test'], function ( test) {
alert(test.fun(1,2,3));
});
注意:这里的 model 模块,只加载了一次,虽然第二次调用test时好,依旧会调用model,
但是只加载一次。test模块,它依赖于model,但是依旧只调用了一次。
/*************************************************************/
之前加载的都是规范的AMD格式的模块,现在加载一些非规范的模块
比如underscore和backbone。都没有采用AMD的方式,如果加载的话,需要定义config
require.config({
shim: {
'underscore':{
exports: '_'
},
'backbone': {
deps: ['underscore', 'jquery'],
exports: 'Backbone'
}
}
});
这个对象除了有前面说过的paths属性之外,还有一个shim属性,专门用来配置不兼容的模块。
具体来说,每个模块要定义
(1)exports值(输出的变量名),表明这个模块外部调用时的名称;
(2)deps数组,表明该模块的依赖性。
requirejs插件。
https://github.com/jrburke/requirejs/wiki/Plugins