-
js模块化起源和现有的规范与框架
普通类 -
- 支持
- 批判
- 提问
- 解释
- 补充
- 删除
-
-
模块化
背景
Web应用的快速发展、后端逻辑的前端化,在客观上要求多人协作编写js代码,这样就带来了以下问题:
全局变量命名混乱冲突导致系统错误
代码集中少量文件导致开发效率降低
容易出现功能重复开发
类似于上述js的加载是早期的网页制作中最常见的,可这样做的缺点是加载时的文件越多网页等待响应的时间越长;而且如果js之间存在依赖关系,则必须严格按照依赖性最强的放置在最后的规则。依赖关系会严重影响代码的编写和维护的效率。。。
模块化框架对于按需加载、模块划分、代码复用、自动文档、单元测试、团队合作等都有很大好处。
模块就是实现特定功能的一组方法,理想情况下,开发者只需要实现核心的业务逻辑,其他都可以加载别人已经写好的模块。
表格 模块化
实现方式
优缺点
优点:代码简单啊
缺点:全是全局变量,容易冲突
优点:属性封装在对象中,看起来是减少了上述问题
缺点:会暴露所有模块成员,内部状态可以被外部改写(module1._count = 5)
立即执行的函数写法
优点:隐藏了私有变量,只返回需要暴露的内容
这是模块化基本的实现思路
放大模式,如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块时使用
优点:对基本方式的加强,可对特定的对象进行增强,如对左边的module1添加一个方法
宽放大模式(与"放大模式"相比,区别在于"立即执行函数"的参数可以是空对象)
优点:避免“放大模式”中因加载顺序导致的未定义的错误
-
规范
CommonJS
CommonJS是服务器端模块的规范,根据此规范,一个单独的文件就是一个模块。加载模块使用一个全局性的require方法,该方法读取一个文件并执行,最后返回文件内部的exports对象。(将需要实现的方法都添加到export对象上)
比如有如下一个简单的模块文件(lcell.js)
console.log("进入lcell.js 加载模块文件");
exports.message = "学习元平台(http://lcell.bnu.edu.cn)";
exports.say = function () {
console.log(message);
}
在需要使用此模块的地方使用require加载
var lcell = require('./lcell'); //.js后缀可省略
此时的变量lcell就对应模块中的exports对象,因此可以通过这个对象调用模块中提供的各种方法。
lcell.say();
有时,不需要exports返回一个对象,只需要它返回一个函数。这时,就要写成module.exports。
module.exports = function () {
console.log("返回一个方法");
}
CommonJS存在的问题在于如果目标模块很大,则需要全部等待加载完之后代码才可以继续执行,这样表面看起来是假死状态。
node.js采用了CommonJS的规范 (可自行了解。推荐一本书《node.js开发指南》)
AMD (Asynchronous Module Definition)
上述CommonJS规范加载模块是同步的,只有加载完成,才能执行后面的操作。由于Node.js主要用于服务器编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不用考虑非同步加载的方式,所以CommonJS规范比较适用。但是如果是浏览器环境,要从服务器端加载模块,这时就必须采用非同步模式,因此浏览器端一般采用AMD规范。
AMD规范则是异步加载模块,需指定回调函数。也采用require()语句加载模块,不同的是它是异步的,等各模块加载完成后调用callback回调函数。
使用方法:require([module], callback);
AMD规范使用define方法定义模块
define(id?, dependencies?, factory);
id: 模块标识,可以省略
dependencies: 所依赖的模块,可以省略
factory: 模块的实现,或者一个JavaScript对象
表格 AMD模块定义
类别
例子
定义无依赖的模块
define(function() {
return {
mix: function(source, target) {
}
};
});
定义有依赖的模块
define(['base'], function(base) {
return {
show: function() {
// todo with module base
}
}
});
定义数据对象模块
define({
users: [],
members: []
});
具名模块
define('index', ['data','base'], function(data, base) {
// 代码
});
包装模块(允许输出的模块兼容CommonJS规范)
define(function(require, exports, module) {
var base = require('base');
exports.show = function() {
// 代码
}
});
总之,AMD 规范是JavaScript开发的一次重要尝试,用简单、优雅的方式统一了JavaScript的模块定义和加载机制。
1实现AMD的库有RequireJS 、curl 、Dojo 、bdLoad、JSLocalnet 、Nodules 等
CMD
与AMD 规范相比,CMD 规范尽量保持简单,并与 CommonJS 和 Node.js 的 Modules 规范保持了很大的兼容性。经常使用的 API 只有 define, require, require.async, exports, module.exports 这五个。
在 CMD 规范中,一个模块就是一个文件。
define(factory);
表格 CMD模块定义
类别
例子
定义对象或字符串模块
define({ "foo": "bar" });
define('I am a template. My name is {{name}}.');
构造模块
define(function(require, exports, module) {
// 模块代码
});
//require 是一个方法,接受 模块标识 作为唯一参数,用来获取其他模块提供的接口。
//exports 是一个对象,用来向外提供模块接口
//module 是一个对象,上面存储了与当前模块相关联的一些属性和方法
定义有依赖的模块
//define(id?, deps?, factory)
define('hello', ['jquery'], function(require, exports, module) {
// 模块代码
});
一个空对象
//可用来判定当前页面是否有 CMD 模块加载器
if (typeof define === "function" && define.cmd) {
// 有 Sea.js 等 CMD 模块加载器存在
}
模块内部异步加载模块
define(function(require, exports, module) {
// 异步加载多个模块,在加载完成时,执行回调
require.async(['./c', './d'], function(c, d) {
c.doSomething();
d.doSomething();
});
});
exports向外提供接口
define(function(require, exports) {
// 对外提供 foo 属性
exports.foo = 'bar';
// 对外提供 doSomething 方法
exports.doSomething = function() {};
});
//或直接通过 return 直接提供接口
define(function(require) {
return {
foo: 'bar',
doSomething: function() {}
};
});
module.exports
define(function(require, exports, module) {
//下面的module不可省略
module.exports = {
foo: 'bar',
doSomething: function() {}
};
});
因为exports是module.exports的引用,如果省略module.而直接给exports重新赋值不会改变module.exports的值,赋值是无效的
-
模块加载器
ESL2
ESL是一个浏览器端、符合AMD的标准加载器,适合用于现代Web浏览器端应用的入口与模块管理。
在目标网页引入esl.js。然后对其进行配置(通过require.config( Object ),可以对esl进行配置
<script src="js/esl.js"></script>
<script type="text/javascript">
require.config( {
baseUrl: './src', //指定模块的根路径
paths:{
‘echarts’:'js/echarts', //为特殊模块指定查找路径
'echarts/chart/line': 'js/echarts',
lib: 'http://lib.com/libpath' // 引用外部模块可以用paths配置
},
packages: [ //功能集合的抽象,通过packages配置可以引入一个符合CommonJS规范的包。
{
name: 'er',
location: '../dep/er/3.0.2'
},
{
name: 'echarts',
location: '../dep/echarts/1.1.0',
main: 'echarts'
}
],
//以下是map配置项,允许用户对依赖的模块进行映射。
'some/newmodule': { //在some/newmodule模块中,require('foo')实际引用到的是foo2模块
'foo': 'foo2',
'foo/bar': 'foo1.2/bar3'
},
//config配置可以为模块传递需要动态变更的参数,比如功能开关。模块可以通过module.config()获得其自身配置
config: {
sidebar: {
displayMode: 'autohide'
}
},
waitSeconds: 5, //指定等待的时间,如果有模块未成功加载或初始化,将抛出异常错误信息
urlArgs: 'v=2.0.0' //在模块路径后添加参数字符串,更新此配置将导致相关模块缓存全部失效
} );
//用require函数加载模块并在回调函数中使用
require(
[
'echarts',
'echarts/chart/bar',
'echarts/chart/line'
],
function(ec) {
var myChart = ec.init(document.getElementById('main'));
//其他操作……
}
);
</script>
Require.js
在目标网页载入require.js,data-main属性指定网页程序的主模块,其中main.js省略后缀名。
<script src="js/require.js" data-main="js/main"></script>
主模块是整个js文件的入口代码。类似于C语言的main()函数。main.js使用AMD规范定义的的require()函数,定义主模块依赖的其他模块并设定回调函数。
require.config({
baseUrl: "js/lib", //指定模块的根路径
paths: { //为各模块指定查找路径
"jquery": "lib/jquery.min",
"underscore": "lib/underscore.min",
"backbone": "lib/backbone.min"
}
});
require(['jquery', 'underscore', 'backbone'], function (\\$, _, Backbone){
//操作各个模块
});
require.js加载的模块需要遵守AMD规范(见第4页),必须采用特定的define()函数来定义。
描述
定义方法
加载方式
无依赖模块
// math.js
define(function (){
var add = function (x,y){
return x+y;
};
return {
add: add
};
});
require(['math'], function (math){
alert(math.add(1,1));
});
依赖于其他模块
//foo.js
define(['myLib'], function(myLib){
function foo(){
myLib.doSomething();
}
return {
foo : foo
};
});
require(['foo'], function foo){
foo.foo();
});
//当加载foo模块的时候会先加载myLib模块
非标准的js文件
require.config({
shim: {
'underscore':{
exports: '_'
},
'backbone': {
deps: ['underscore', 'jquery'],
exports: 'Backbone'
}
}
});
// shim属性专门用来配置不兼容的模块。每个模块要定义exports值(输出的变量名),表明这个模块外部调用时的名称;deps数组,表明该模块的依赖性。
sea.jsSeaJS是一个遵循CMD规范的JavaScript模块加载框架,作者是淘宝前端工程师玉伯。
在目标页面引入sea.js
<script src="js/sea.js" ></script>
然后对seajs 进行简单配置:
seajs.config({
base: "../sea-modules/", // 设置路径
alias: { // 为模块设置别名
"jquery": "jquery/jquery/1.10.1/jquery.js"
}
})
// 加载入口模块。seajs.use用来在页面中加载一个或多个模块,此处加载主模块
seajs.use("../static/hello/src/main");
表格 seajs最常用的接口
最常用的接口
例子
seajs.config
seajs.config({
base: "../sea-modules/", // 设置路径
alias: { // 为模块设置别名
"jquery": "jquery/jquery/1.10.1/jquery.js"
}
})
seajs.use
// 支持加载多个模块,包含依赖关系,在加载完成时,执行回调
seajs.use(['./a', './b'], function(a, b) {
a.doSomething();
b.doSomething();
});
define
// 模块的定义
define(function(require, exports, module) {
// 模块代码
});
require
// 获取指定模块的接口,只接受字符串直接量作为参数
define(function(require) {
// 获取模块 a 的接口
var a = require('./a');
// 调用模块 a 的方法
a.doSomething();
});
require.async
define(function(require) {
// 异步加载多个模块,在加载完成时,执行回调
require.async(['./c', './d'], function(c, d) {
c.doSomething();
d.doSomething();
});
});
exports
// 对外提供接口。exports其实是module.exports的引用
define(function(require, exports) {
// 对外提供 foo 属性
exports.foo = 'bar';
// 对外提供 doSomething 方法
exports.doSomething = function() {};
});
module.exports
//完全重写module.exports属性来对外提供接口
define(function(require, exports, module) {
// 对外提供接口
module.exports = {
name: 'a',
doSomething: function() {};
};
});
-
比较
比较内容
RequireJS
SeaJS
ESL
遵循规范
AMD
CMD
AMD
策略
提前准备依赖
按需执行依赖
提前准备依赖
API
一个当多个用
严格区分,职责单一
定位
文件和模块加载器,特别为浏览器优化,同时也可运行在 Rhino 和 Node 环境中。
适用于浏览器端的JavaScript 模块加载器
仅支持浏览器端
shim支持
支持
支持
不支持
文件大小
81K
3K
49K
1 CMD模块定义规范https://github.com/seajs/seajs/issues/242
2 https://github.com/ecomfe/esl
-
在新页面中查看内容
-
下载源文件
-
-
- 标签:
- 文件
- 定义
- 39
- exports
- 加载
- function
- define
- js
- 模块化
- require
- 模块
- 规范
-
学习元评论 (0条)
聪明如你,不妨在这 发表你的看法与心得 ~