前端模块化

1 ES5时代

1.1 原生代码实现模块化

1) 对象写法

1
2
3
4
5
6
7
8
9
var module1 = new Object({
    _count : 0,
    m1 : function (){
      //...
    },
    m2 : function (){
      //...
    }
  });

2) 立刻执行函数(Immediately-Invoked Function Expression,IIFE) or 匿名闭包

1
2
3
4
5
6
7
8
9
10
11
12
13
var module1 = (function(){
    var _count = 0;
    var m1 = function(){
      //...
    };
    var m2 = function(){
      //...
    };
    return {
      m1 : m1,
      m2 : m2
    };
  })();

这样可以很好的保护私有变量,通过return来设置公开的方法。缺点也有: 动态添加方法的时候比较麻烦,且无法修改内部私有变量。
3) 放大模式 or 宽放大模式(Loose augmentation)
如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,这时就有必要采用”放大模式”(augmentation)

1
2
3
4
5
6
var module1 = (function (mod){
    mod.m3 = function () {
      //...
    };
    return mod;
  })(module1);

在浏览器环境中,模块的各个部分通常都是从网上获取的,有时无法知道哪个部分会先加载。如果采用上一节的写法,第一个执行的部分有可能加载一个不存在空对象,这时就要采用”宽放大模式”

1
2
3
4
var module1 = ( function (mod){
    //...
    return mod;
  })(window.module1 || {});

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var moduleTest = ( function (mod){

var value1=0, privateName='张三';

function privateAddTopic(data) {
// 这里是内部处理代码
console.log("内部函数");
console.log(data);
}
mod.name = privateName;
mod.init=function(){
this.f1();
};
mod.f1=function(){
console.log("f1--hello");
}; 
mod.f2=function(data){
privateAddTopic(data);
};   
  return mod;
})(window.moduleTest || {});

moduleTest.init();
moduleTest.f2("hello world");
console.log(moduleTest.name); //张三
moduleTest.name = '李四'; //修改模块中的属性
console.log(moduleTest.name); //李四

1.2 CommonJS/AMD/CMD

现代模块的基本思想实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

var myModules = (function Manager() {
var modules = {}; //定义的模块保存在这个对象里面

function define(name, deps, impl) {//impl是implement的简写,实现的方法
for (var i = 0; i < deps.length; i++) {
deps[i] = modules[deps[i]]; //从保存的对象中获取依赖的模块,注:依赖的模块,肯定已经被define()存放在modules对象中了。
}

modules[name] = impl.apply(impl, deps);//如果此时的模块引入别的模块deps,就将deps作为impl实现的方法的参数
}
/**
* [get 通过名字获得模块]
* @param {[type]} name [模块名]
* @return {[type]} [完整独立模块]
*/
function get(name) {
return modules[name];
}

return {
define: define,
get: get
};
})();

//测试
myModules.define("bar", [], function() {
function hello(who) {
return "hello " + who;
}
return {
hello: hello
};
});
myModules.define("foo", ["bar"], function(bar) {
var n = '张三';
function awe(who) {
console.log(bar.hello(n).toUpperCase());
}
return {
awe: awe
};

});

var bar = myModules.get("bar");
var foo = myModules.get("foo");

foo.awe();//HELLO 张三

1) CommonJS
node.js的模块系统,就是参照CommonJS规范实现的

1
2
var math = require('math');
math.add(2,3); // 5

第二行math.add(2, 3),在第一行require(‘math’)之后运行,因此必须等math.js加载完成。也就是说,如果加载时间很长,整个应用就会停在那里等。

这对服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于”假死”状态。这就催生AMD规范到来的背景。

2) AMD
AMD(Asynchronous Module Definition)是异步模块加载的意思,会预执行(依赖)模块。

1
require([module], callback);

1
2
3
require(['math'], function (math) {
  math.add(2, 3);
});

主要有两个Javascript库实现了AMD规范:require.jscurl.js

3) CMD
CMD(Custom Module Definition)通用模块加载,SeaJS遵循CMD规范,并行加载所有依赖的模块, 但不会立即执行模块,等到真正需要(require)的时候才开始解析(懒执行)。
引入SeaJS文件

1
<script type="text/javascript" src="../../common/jsext/sea-debug.js"></script>

SeaJS 的简单配置

1
2
3
4
5
6
7
8
seajs.config({
base: "../sea-modules/",
alias: {
"jquery": "jquery/jquery/1.10.1/jquery.js"
}
})
// 加载入口模块
seajs.use("../static/hello/src/main");//入口

定义模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 所有模块都通过 define 来定义
define(function(require, exports, module) {

// 通过 require 引入依赖
var $ = require('jquery');
var Spinning = require('./spinning');

// 通过 exports 对外提供接口
exports.doSomething = ...

// 或者通过 module.exports 提供整个接口
module.exports = ...

});

另外可以使用seajs-text加载html文件或者tpl片段,seajs-css加载css文件

1
2
3
4
5
6
7
8
9
10
11
12
13
<script src="path/to/sea.js"></script>
<script src="path/to/seajs-text.js"></script>

<script>
define("main", function(require) {

// You can require `.tpl` file directly
var tpl = require("./data.tpl")
//或者html
var html =require("./a.html");
$('.some_class').append(html);
})
</script>

seajs-css

1
2
3
4
5
6
7
8
9
10
<script src="path/to/sea.js"></script>
<script src="path/to/seajs-css.js"></script>

<script>

// seajs can load css file after loading css plugin.
seajs.use("path/to/some.css");
//很多时候可以使用require的方式
require("path/to/some.css");
</script>

2 ES6时代

1
2
3
4
5
6
//bar.js
function hello(who){
return "hello "+who;
}

export {hello};
1
2
3
4
5
6
7
8
9
//foo.js
import {hello} from "bar";

var name = "zs";
function awe(){
console.log(bar.hello(name).toUpperCase());
}

export {awe};
1
2
3
4
5
6
7
//baz.js
import {bar} from "bar";
import {foo} from "foo";

console.log(bar.hello('张三'));//hello 张三

foo.awe();//HELLO ZS

当然现在需要使用babel转成es5,并且要使用打包工具browserify webpack rollup 等才能直接在现在的浏览器上运行。

参考阅读:

热评文章