今天看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