Javascript Modularize

Closure

什么是闭包?

一个包含了自由变量的开发表达式,和该自由变量的约束环境组合之后,产生了一种封闭的状态。

举个例子,如下

1
2
3
4
5
6
7
8
9
10
11
12
function inc() {
var counter = 0;
return function() {
return counter++;
}
}

//invoke
var id = inc();
id(); //0
id(); //1
...

变量counter只在inc中可见,inc就是counter的约束环境,而内部的function就是开放的表达式,达到的封闭状态就是闭包。

Javascript Closure

1
2
3
4
5
6
7
(function(){
console.log(window);
}())
or
(function(){
console.log(window);
})()

注意两种闭包的写法: **()**一个在内,另一个在外,两者是等价的。
这种写法有两个作用

  1. 立即执行(Immediately-Invoked Function Expression, IIFE)
  2. 封装内部变量,使之对外不可见

NOTICE
对外不可见,不代表闭包内部不能访问全局变量。闭包内部是可以访问外部变量的,如上面的例子所示

JavaScript has a feature known as implied globals. Whenever a name is used, the interpreter walks the scope chain backwards looking for a var statement for that name. If none is found, that variable is assumed to be global. If it’s used in an assignment, the global is created if it doesn’t already exist. This means that using or creating global variables in an anonymous closure is easy. Unfortunately, this leads to hard-to-manage code, as it’s not obvious (to humans) which variables are global in a given file.

以上就是理由。这种做法不好的地方有三个:

  1. 代码不便管理,这是全局变量的弊病
  2. 依赖关系不明确
  3. 性能损失,因为需要向后查找变量声明

正确的做法是导入作用域

1
2
3
(function(window){
console.log(window);
})(window)

借助closure,我们可以简单实现JS的模块化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var module = (function(){
var counter = 0;
return {
inc: function() {
return counter++;
}
};
})();

//invoke
module.counter //undefined
module.inc(); //0
module.inc(); //1
...

Augmentation (mixin, trait …)

模块化之后,如何扩展这部分的功能?

Loose augmentation

1
2
3
4
5
6
7
var module = (function(self){

self.todo = function() {
console.log('cannot access counter');
}
return self;
})(module || {})

之所以称为宽松augmentation,是因为多个modules可以异步加载。如果变量没有声明,就会重新创建。与之对应的就是Tight augmentation

Tight augmentation

1
2
3
4
5
6
7
var module = (function(self){

self.todo = function() {
console.log('cannot access counter');
}
return self;
})(module)

这样就要求严格的加载顺序了。

更多模式参考 JavaScript Module Pattern: In-Depth

通用的JS规范共有两种:CommonJS和AMD

CommonJS

该标准中,有一个自由函数require,用于加载模块。

1
2
3
4
5
6
7
8
//math.js
exports.add = function() {
var sum = 0, i = 0, args = arguments, l = args.length;
while (i < l) {
sum += args[i++];
}
return sum;
};

这样就可以在其他模块中引用

1
2
3
4
5
//increment.js
var add = require('math').add;
exports.increment = function(val) {
return add(val,1);
};

NOTICE

任何require进来的模块,其变量都是私有的

举个例子

1
2
3
4
5
6
//program.js
var inc = require('increment').increment;
var a = 1;
inc(a); // 2
inc.add //undefined
module.id == "program";

这里的inc.add对于program.js是不可见的。

NodeJs的模块系统实现了改标准 Nodejs Module

AMD (Asynchronous Module Definition)

对于浏览器环境,由于js文件在服务端,异步加载使得CommonJS不实用了。

1
2
3
4
5
6
7
8
define("alpha", ["require", "exports", "beta"],
function (require, exports, beta) {
exports.verb = function() {
return beta.verb();
//Or:
return require("beta").verb();
}
});

这样,只有等到所有的模块加载成功后,才会调用回调函数。