1佃延、閉包
當函數(shù)可以記住并訪問所在的詞法作用域時,就產(chǎn)生了閉包脸甘,即使函數(shù)是在當前詞法作用域之外執(zhí)行。
function foo()
{
var a = 2;
function bar()
{
console.log(a);
}
return bar;
}
bar baz = foo();
baz(); //2,這就是閉包
基于詞法作用域的查找規(guī)則(RHS引用查詢)偏灿,函數(shù)bar()可以訪問外部foo()作用域中的變量a丹诀。然后我們將bar()函數(shù)本身當作一個值類型進行傳遞。在foo()執(zhí)行后翁垂,其返回值铆遭,實際上只是通過不同的標識符引用調(diào)用了內(nèi)部函數(shù)bar()。
因為bar()是在自己定義的詞法作用域以外的地方執(zhí)行沿猜,bar()擁有涵蓋foo()內(nèi)部作用域的閉包枚荣。所以foo()執(zhí)行后其內(nèi)部作用域不會被銷毀。
function wait(message)
{
setTimeout(function timer()
{
console.log(message);
}, 1000);
}
wait("hello, closure!");
將一個內(nèi)部函數(shù)(timer)傳遞給setTimeout()啼肩。timer具有涵蓋wait()作用域的閉包橄妆,因此還保有對變量message的引用。深入到引擎的內(nèi)部原理中祈坠,內(nèi)置的工具函數(shù)setTimeout()持有對一個參數(shù)的引用害碾。引擎會調(diào)用這個函數(shù)(timer),而詞法作用域在這個過程中保持完整——閉包赦拘。
2慌随、循環(huán)和閉包
for (var i = 1; i <= 5; i++)
{
setTimeout(function timer()
{
console.log(i);
}, i * 1000);
}
我們期待這段代碼行為是分別輸出數(shù)字1~5,每秒一次另绩,每次一個儒陨。但實際上花嘶,輸出的是以每秒一次的頻率輸出五次6。我們試圖假設循環(huán)中的每個迭代在運行時會給自己捕獲一個i的副本蹦漠。但根據(jù)作用域的工作原理椭员,實際情況是盡管循環(huán)中的五個函數(shù)是在各個迭代中分別定義的,但是它們都被封閉在一個共享的全局作用域中笛园,因此只有一個i隘击。
用IIFE解決:
for (var i = 1; i <= 5; i++)
{
(function(j)
{
setTimeout(function timer()
{
console.log(i);
}, i * 1000);
})(i);
}
用塊作用域解決:
使用IIFE在每次迭代時都創(chuàng)建一個新的作用域。也就說每次迭代需要一個塊作用域研铆。
for (let i = 1; i <= 5; i++)
{
setTimeout(function timer()
{
console.log(i);
}, i * 1000);
}
塊作用域本質(zhì)上是將一個塊轉換成一個可以被關閉的作用域埋同。
3、模塊
function CoolModule()
{
var something = "cool";
var another = [1, 2, 3];
function doSomething()
{
console.log(something);
}
function doAnother()
{
console.log(another.join("i"));
}
return
{
doSomething : doSomething,
doAnother : doAnother
};
}
var foo = CoolModule();
foo.doSomething(); //cool
foo.doAnother(); //1!2!3
首先棵红,CoolModule()只是一個函數(shù)凶赁,必須要通過它來創(chuàng)建一個模塊實例,其次逆甜,CoolModule()返回一個用對象字面量語法{key:value}來表示的對象虱肄。這個返回的對象中含有對內(nèi)部函數(shù)而不是內(nèi)部數(shù)據(jù)變量的引用。保持內(nèi)部數(shù)據(jù)變量是隱藏私有的狀態(tài)交煞∮搅可以將這個對象類型的返回值看作本質(zhì)上是模塊的公共API。
模塊模式需要具備兩個必要條件:
- 必須有外部的封閉函數(shù)素征,該函數(shù)必須至少被調(diào)用一次(每次調(diào)用都會創(chuàng)建一個新的模塊實例)
- 封閉函數(shù)必須返回至少一個內(nèi)部函數(shù)集嵌,這樣內(nèi)部函數(shù)才能在私有作用域中形成閉包,并且可以訪問或者修改私有的狀態(tài)御毅。
現(xiàn)代的模塊機制
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.apple(impl, deps);
}
function get(name){
return modules[name];
}
return{
define : define,
get : get
}
})();
這段代碼的核心是modulus[name] = impl.apply(impl,deps)根欧。為了模塊的定義引入了包裝函數(shù),并且將返回值存儲在一個根據(jù)名字來管理的模塊列表中亚享。
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
foo和bar模塊都是通過一個返回公共API的函數(shù)來定義的咽块。foo甚至接受bar的示例作為依賴參數(shù)绘面,并能相應地使用它欺税。