今天看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
这篇博客探讨了JavaScript中的模块化开发,通过一个实例解释了如何使用立即执行函数表达式(IFE)创建闭包,以及如何通过`require`和`exports`暴露公共接口。文章详细阐述了`modules`变量的作用,它是模块私有的,用于存储和挂载模块。作者还分析了模块定义和实现的过程,展示了如何在模块间进行调用,并强调了闭包在保持词法作用域中的变量存活中的关键作用。
335

被折叠的 条评论
为什么被折叠?



