模块化
模块的必要条件
- 必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)
- 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。
这里的必要条件理解
- 有个封闭的函数,并且调用返回一个模块实例
- 返回的实例,至少要有个函数能够修改内部属性或状态
function CoolModule() {
var something = "cool";
var another = [1, 2, 3];
function doSomething() {
console.log( something );
}
function doAnother() {
console.log( another.join( " ! " ) );
}
return {
doSomething: doSomething,
doAnother: doAnother
};
}
var foo = CoolModule();
foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3
上面的例子
CoolModule 每次调用,都会生成一个新的函数。
简写,IIFE
var foo = (function CoolModule() {
var something = 'cool';
var another = [1,2,3];
function doSomething() {}
function doAnother() {}
return {
doSomething: doSomething,
doAnother: doAnother
}
})();
foo.doSomething();
foo.doAnother();
模块命名
var foo = (function CoolModule(id) {
function change() {
// 修改公共 API
publicAPI.identify = identify2;
}
function identify1() {
console.log( id );
}
function identify2() {
console.log( id.toUpperCase() );
}
var publicAPI = {
change: change,
identify: identify1
};
return publicAPI;
})( "foo module" );
foo.identify(); // foo module
foo.change();
foo.identify(); // FOO MODULE
通过在模块实例的内部保留对公共 API 对象的内部引用,可以从内部对模块实例进行修改,包括添加或删除方法和属性,以及修改它们的值。
上面的例子,一直在讲两个必要条件
- 封闭函数,返回实例
- 返回体能够修改内部属性
现代的模块机制
大多数模块依赖加载器/管理器本质上都是将这种模块定义封装在一个友好的 API。这里并不会研究某个具体的库,为了宏观了解会简单地介绍一些核心概念。
var MyModules = (function Manager() {
var modules = {};
function define(name, deps, impl) {
for (var i=0; i<deps.length; i++) {
deps[i] = modules[deps[i]];
}
modules[name] = impl.apply( impl, deps );
}
function get(name) {
return modules[name];
}
return {
define: define,
get: get
}
})()
上面有两部分组成,一个是定义,一个获取
定义时,这个模块需要的模块要作为参数,放进去。
这里我来复写一遍:
// 模块,定义,和获取
// 注意,定义时,要将该模块所需模块作为参数放入
var MyModules = (function() {
var modules = {};
function define(name, needModuleName, handle) {
for(var i=0; i<needModuleName.length, i++) {
needModuleName[i] = modules[needModuleName[i]];
}
modules[name] = handle.apply(handle, needModuleName);
}
function get(name) {
return modules[name];
}
return {
define: define,
get: get
}
})()
// 可以,写出来了,记住核心要点就行
这段代码的核心是 modules[name] = impl.apply(impl, deps)。为了模块的定义引入了包装函数(可以传入任何依赖),并且将返回值,也就是模块的 API,储存在一个根据名字来管理的模块列表中。
下面展示了如何使用它来定义模块:
MyModules.define('bar', [], function() {
function hello(who) {
return "Let me introduce:" + who;
}
return {
hello: hello
}
})
MyModules.define('foo', ['bar'], function(bar) {
var hungry = 'hippo';
function awesome() {
console.log(bar.hello(hungry).toUpperCase());
}
return {
awesome: awesome
}
})
var bar = MyModules.get('bar');
var foo = MyModules.get('foo');
console.log(bar.hello('hippo')); // Let me introduce: hippo
foo.awesome(); // LET ME INTRODUCE: HIPPO
未来的模块机制
ES6 中为模块增加了一级语法支持。但通过模块系统进行加载时,ES6 会将文件当作独立的模块来处理。每个模块都可以导入其他模块或特定的 API 成员,同样也可以导出自己的 API 成员。
基于函数的模块并不是一个能被稳定识别的模式(因为编译器无法识别,它们的 API 语义只有在运行时才会被考虑进来) 相比之下,ES6 模块 API 更加稳定(API 不会在运行时改变)。由于编辑器知道这一点,因此可以在编译期检查对导入模块的 API 成员的引用是否真实存在。如果 API 引用并不存在,编译器会在运行时抛出一个或多个“早期”错误,而不会像往常一样在运行期采用动态的解决方案。
export 和 import
小结
- 为创建内部作用域而调用了一个包装函数
- 包装函数的返回值必须至少包括一个对内部函数的引用