第7章 函數表達式

//不要這么做

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)中使用,使用后即被銷毀。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 1、定義函數的兩種方式:1)函數聲明:存在函數聲明提升2)函數表達式:使用前必須先賦值;匿名函數(拉姆達函數) 2...
    94小輝閱讀 351評論 0 0
  • 定義函數的方式有兩種:函數聲明和函數表達式。 函數聲明的一個重要特征就是函數聲明提升,意思是在執(zhí)行代碼前會先讀取函...
    oWSQo閱讀 729評論 0 0
  • 常規(guī)方式定義函數 定義函數有兩種方式,第一種方式為常規(guī)聲明方式,該方式下函數可以先使用,后聲明,即"函數聲明提升"...
    勤勞的悄悄閱讀 301評論 0 0
  • 包管理 檢查.deb包內容 dpkg-deb --info arping_2.11-1_armhf.debdpkg...
    bluexiii閱讀 393評論 0 1
  • 錐子臉、帶美瞳、指甲貼亮片;隆起的胸部、水蛇腰以及黑絲緊繃的長腿。這是很多外圍女的標準樣貌。應該說,通過科技(整容...
    羊年小少閱讀 1,234評論 2 20

友情鏈接更多精彩內容