今天看YDKJS时,明白了一个以前一直疑惑的东西,那就是JS中的模块开发,之前也使用过require.s进行模块化的开发,但是一直不能明白其中具体的原理,看了YDKJS的讲解后,真的是豁然开朗,在此记录其中一个实例的代码,并进行解析。
var myModules = (function foo(){
var modules = {};
function get(name){
return modules[name];
}
function define(name, deps, impl){
for(var i = 0; i < deps.length; i++){
deps[i] = modules[ deps[i] ];
}
modules[name] = impl.apply( impl, deps );
}
return {
get: get,
define: define
}
})();
myModules.define('foo',[],function(){
function hello(val){
console.log('hello',val);
}
return {
hello: hello
}
});
myModules.define('test',['foo'],function(foo){
function useHello(val){
var log = "I am from test" + val;
foo.hello( log );
}
return {
useHello: useHello
}
})
myModules.get('test').useHello('hello world');
说明:
-
IFFE
在整个myModules
定义的最外层,使用了立即执行函数表达式var myModules =(function foo(){...})()
,这样做的目的是为了形成一个闭包,使用myModules
保存函数表达式function foo(){...}
其中的词法作用域。 -
暴露公共接口
使用return {get: get, define: define }
,是为了将公共的接口get
和define
暴露出来,可以在myModules
上调用这些方法。同时因为这两个方法的内部都使用了变量modules
,所以modules
就会在内存中被保存起来,类似于myModules
的私有变量。 -
私有变量modules
变量modules
类似于myModules
的私有变量,不通过myModules
是无法访问到这个变量的,且其会一直保存在内存中。使用这个变量,结合define
方法,将所有定义的模块都挂载到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 ); }
name
: 定义的模块名称
deps
: 模块对应的依赖
impl
: 模块的实现先是使用循环根据依赖
deps
中的依赖模块名,从modules
中得到具体的挂载在modules
上的模块数组。
然后是下面这句很精髓的话:
modules[name] = impl.apply( impl, deps );
将deps
依赖模块数组作为参数传递给该模块的实现方法,并使用apply
调用当前模块的实现方法。那么此时我们可以发现,实际挂载到modules
上的并不是模块具体的实现方法,而是模块的返回值。但是模块的返回值是一个对象,这个对象中的值有可以引用了模块中定义的方法变量,如此一来,模块暴露出来的方法和变量中涉及的词法作用域中的变量就会被保存起来,而不会在内存中消失。其实还是应用了所谓的闭包。 -
模块定义的实现
我们来看看具体的模块定义和实现myModules.define('foo',[],function(){ function hello(val){ console.log('hello',val); } return { hello: hello } });
我们定义了
foo
模块,在foo
模块中,我们暴露了hello
方法,实际上此时myModules
内部的modules
是这样的modules = { foo:{ hello: hello } }
可以看到
modules
上挂载的foo
模块实际上是foo
模块定义时的返回值,但是这个返回值中引用的方法hello
此时会一直保存在内存中。 -
模块间调用
myModules.define('test',['foo'],function(foo){ function useHello(val){ var log = "I am from test" + val; foo.hello( log ); } return { useHello: useHello } }) myModules.get('test').useHello('hello world');
我们在模块
test
中调用了foo
模块。注意,在test模块的实现中,我们加入了参数foo
, 那么实际定义的过程如下impl.apply( impl, modules['foo'] );
等价于
impl.apply( impl, { hello: hello } );
所以,我们在
test
模块的实现中,可以直接使用变量foo
,就像真的在调用foo
模块一样,然后后我们再将test
模块中的返回值挂载到modules
上modules['test'] = impl.apply( impl, { hello: hello } );
以上是我个人对于js中的模块化的理解,如有错误,欢迎各位老铁指正。
参考资料:
[1]: https://github.com/Alizwell/You-Dont-Know-JS/blob/1ed-zh-CN/scope %26 closures/ch5.md