//不要這么做
if( condition ){
function sayHi(){
alert('Hello!');
}
} else {
function sayHi(){
alert('Yo!');
}
}
因為不同瀏覽器對上面代碼理解不同,ie不管condition是否為真,都會執(zhí)行alert('Yo!')
//可以這么做
var sayHi;
if( condition ){
sayHi = function(){
alert('Hello!');
}
} else {
sayHi = function(){
alert('Yo!');
}
}
7.1 遞歸
經典遞歸函數:
function factorial(num){
if( num <= 1 ){
return 1;
} else {
return num * factorial(num-1);
}
}
可是,遇到下面情況,會出問題:
var another = factorial;
factorial = null;
console.log(another(2)) //出錯
因為在調用another時候,factorial已經不是一個函數了。
可以使用 arguments.callee 解決問題:
注意:arguments.callee 是一個指向正在執(zhí)行的函數的指針。
function factorial(num){
if( num <= 1 ){
return 1;
} else {
return num * arguments.callee(num-1);
}
}
7.2 閉包
閉包:指有權訪問另一個函數作用域中的變量的函數。
創(chuàng)建閉包的常見方式,就是在一個函數內部創(chuàng)建另一個函數。
函數被調用時,都會發(fā)生些什么?如何創(chuàng)建作用域鏈,作用域鏈有什么作用?
來看這一段代碼:
function compare(value1, value2){
if( value1 < value2 ){
return -1;
} else if ( value1 > value2 ) {
return 1;
} else {
return 0;
}
}
var result = compare(5, 10);
以上代碼中,當調用了compare()時,會創(chuàng)建一個作用域鏈,其中,arguments、value1、value2處于作用域鏈的第一位,全局執(zhí)行環(huán)境的變量(包含result和compare)則處于第二位。
很顯然,作用域鏈本質上是一個指向變量對象的指針列表,它只引用但不實際包含變量對象。
再來看一個函數:
function createComparisonFunction(propertyName){
return function(object1, object2){
var value1 = object1[propertyName];
var value2 = object2[propertyName];
if( value1 < value2 ){
return -1;
} else if( value1 > value2 ){
return 1;
} else {
return 0;
}
}
}
var compare = createComparisonFunction('name');
var result = compare({ name: 'jack' }, { name: 'mack'} )
注意:在另一個函數內部定義的函數會將包含函數(即外部函數)的活動對象添加到自己的作用域鏈中。因此,
在createComparisonFunction() 函數內部定義的匿名函數的作用域鏈中,實際上將會包含外部函數createComparisonFunction()的活動對象。
其實,就是說,對于閉包,它的作用域鏈不僅僅是自己內部,還包括自己的外部。
接著說上面的函數,
在匿名函數從createComparisonFunction()中被返回后,它的作用域鏈被初始化為包含createComparisonFunction()函數的活動對象和全局變量對象。
這樣,匿名函數就可以訪問在createComparisonFunction()中定義的所有變量。
更為重要的是,createComparisonFunction()函數在執(zhí)行完畢后,其活動對象也不會被銷毀,因為匿名函數的作用域鏈仍然在引用這個活動對象。
換句話說,當createComparisonFunction() 函數返回后,其執(zhí)行環(huán)境的作用域鏈會被銷毀,但它的活動對象仍然會留在內存中,直到匿名函數被銷毀,
createComparisonFunction() 函數的活動對象才會被銷毀。
例如:
//創(chuàng)建函數
var compareNames = createComparisonFunction('name');
//調用函數
var result = compareNames({ name: 'jack' }, { name: 'mack'} );
//解除對匿名函數的引用(以便釋放內存)
compareNames = null;
就是說,創(chuàng)建一個匿名函數后,除非手動設置其為null來進行銷毀釋放內存,否則,這個匿名函數以及其引用的外部變量依然會存在于內存中。
建議:由于閉包會攜帶包含它的函數的作用域,因此會比其他函數占用更多的內存。過度使用閉包可能會導致內存占用過多,因此,我們建議讀者只在絕對必要時再考慮使用閉包。
7.2.1 閉包與變量
作用域鏈的這種配置機制引出了一個值得注意的副作用,即閉包只能取得包含函數中任何變量的最后一個值。別忘了,閉包所保存的是整個變量對象,而不是某個特殊的變量。
function create(){
var result = [];
for(var i = 0; i < 10; i++){
result[i] = function() {
return i;
}
}
return result
}
console.log(create())
這個函數會返回一個數組,但是每個函數都返回10。 因為每個函數的作用域鏈中都保存著 create() 函數的活動對象,所以它們引用的都是同一個變量 i。
怎么樣讓每個函數返回自己?我們可以創(chuàng)建另一個匿名函數強制讓閉包的行為符合預期。
function create(){
var result = [];
for(var i = 0; i < 10; i++){
result[i] = function(num) {
return function(){
return num;
}
}
console.log(result[i])
}
return result;
}
console.log(create())
7.2.2 關于this對象
我們知道,this對象是在運行時基于函數的執(zhí)行環(huán)境綁定的:在全局函數中,this等于window,而當函數被作為某個對象的方法調用時,this等于那個對象。
重點:不過,匿名函數的執(zhí)行環(huán)境具有全局性,因此其this對象通常指向window。
但是,有時候,由于編寫閉包的方式不同,這一點可能不會那么明顯
var name = 'the window';
var object = {
name: 'my object',
getNameFunc: function(){
return function() {
return this.name;
}
}
}
console.log(object.getNameFunc()()); ==> the window
如何讓閉包訪問object中的name呢?可以這么做:
var name = 'the window';
var object = {
name: 'my object',
getNameFunc: function(){
var that = this;
return function() {
return that.name;
}
}
}
console.log(object.getNameFunc()());
7.3 模仿塊級作用域
由于JS沒用塊級作用域,即意味著在塊語句內部定義的變量,全局環(huán)境中都可以訪問得到。
比如:
function outputNumbers(count){
for( var i = 0; i < count; i++ ){
alert(i)
}
alert(i) ==> 5
}
alert(i) ==> 報錯
outputNumbers(5);
由于沒有塊級作用域,在for循環(huán)之內的i變量,被定義在了其外部函數中。而在全局環(huán)境中查找不到i是因為js有函數作用域。
雖然js沒有塊級作用域,但是我們可以通過匿名函數來模仿塊級作用域。
function outputNumbers(count){
(function(){
for(var i = 0; i < count; i++){
alert(i);
}
})();
alert(i) ==> 報錯。i只能在for循環(huán)中訪問得到
}
在匿名函數中定義的任何變量,都會在執(zhí)行結束時被銷毀。因此,i只能在循環(huán)中使用,使用后即被銷毀。