閉包是作用域概念的擴展。通過閉包,函數可以訪問存在函數被創建的作用域中的變量。如果這顯得令人困惑,別擔心:閉包一般最適合通過例子來理解。
如同[作用域](http://js101.co/javascript-101/scope.html)部分所示,函數可以訪問變化的變量值。定義在循環中的函數也存在同樣的行為 - 即使在函數定義之后,它依然能觀察到變量的值發生了改變,導致每一個函數都引用了保存在變量中的最后值。
~~~
// Each function executed within the loop will reference
// the last value stored in i (5).
// This won't behave as we want it to - every 100 milliseconds, 5 will alert
for ( var i = 0; i < 5; i++ ) {
setTimeout(function() {
alert( i );
}, i * 100 );
}
~~~
閉包可以用來防止這種情況,通過給每一次迭代創建一個獨特的作用域 - 在其作用域內保存變量的每一個獨特值。
~~~
// Using a closure to create a new private scope
// fix: “close” the value of i inside createFunction, so it won't change
var createFunction = function( i ) {
return function() {
alert( i );
};
};
for ( var i = 0; i < 5; i++ ) {
setTimeout( createFunction( i ), i * 100 );
}
~~~
閉包也可以用來解決?`this`?關鍵字的問題,它是每個作用域的唯一值:
~~~
// Using a closure to access inner and outer object instances simultaneously.
var outerObj = {
myName: "outer",
outerFunction: function() {
// Provide a reference to outerObj through innerFunction's closure
var self = this;
var innerObj = {
myName: "inner",
innerFunction: function() {
console.log( self.myName, this.myName ); // "outer inner"
}
};
innerObj.innerFunction();
console.log( this.myName ); // "outer"
}
};
outerObj.outerFunction();
~~~
## Function.bind
當處理回調函數時,閉包也是特別有用的。但是,通常更好的做法是使用`Function.bind`,它可以避免任何作用域遍歷相關的過度開銷。
`Function.bind`?被用來創建一個新函數。當新函數被調用時,函數會在?`.bind()`方法中提供的?`this`?上下文中執行,并使用一系列?`.bind()`?方法中提供的參數與函數調用時提供的任何參數。
由于?`.bind()`?是在 ECMAScript 5 中添加的,它可能不會得到所有瀏覽器的支持,當決定是否使用它時,這是值得注意的一點。不過,我們可以使用 MDN 提供的[兼容代碼](https://developer.mozilla.org/zh-CN/JavaScript/Reference/Global_Objects/Function/bind)來使?`.bind()`?正常工作。
~~~
// Shim from MDN
if (!Function.prototype.bind) {
Function.prototype.bind = function( oThis ) {
if (typeof this !== "function") {
// closest thing possible to the ECMAScript 5 internal
// IsCallable function
throw new TypeError( "Function.prototype.bind - what is trying to be bound is not callable" );
}
var fSlice = Array.prototype.slice,
aArgs = fSlice.call( arguments, 1 ),
fToBind = this,
fNOP = function() {},
fBound = function() {
return fToBind.apply( this instanceof fNOP
? this
: oThis || window,
aArgs.concat( fSlice.call( arguments ) ) );
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}
~~~
`.bind()`?最簡單的用途之一是創建一個使用特定?`this`?值的函數,而無關該函數是如何調用的。一個開發者常常出現的錯誤是試圖從對象中提取一個方法,在隨后調用該方法時期望使用原始對象作為?`this`?值。這時可以通過創建一個函數綁定原始對象來解決類似問題,如下所示:
~~~
// Let's manipulate "this" with a basic example.
var user = "johnsmith";
var module = {
getUser: function() {
return this.user;
},
user: "janedoe"
};
// module.getUser() is called where "module" is "this"
// and "module.user" is returned.
// janedoe
module.getUser();
// let's now store a reference in the global version of "this"
var getUser = module.getUser;
// getUser() called, "this" is global, "user" is returned
// johnsmith
getUser();
// store a ref with "module" bound as "this"
var boundGetUser = getUser.bind( module );
// boundGetUser() called, "module" is "this" again, "module.user" returned.
// janedoe
boundGetUser();
~~~