DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> JavaScript入門知識 >> JavaScript基礎知識 >> JavaScript函數表達式
JavaScript函數表達式
編輯:JavaScript基礎知識     

函數表達式的基本語法形式

var functionName = function(arguments){
  //函數體
}

遞歸建議

我們通過例子來一步步說明,遞歸的最佳實現方式。下面是普通遞歸調用的例子:

// 階乘的遞歸函數
var factorial = function(num){
  if(num <= 1){
    return 1;
  }else {
    return num * factorial(num-1);
  }
}

console.log(factorial(3)); //6

我們來看一下這種情況:

// 賦給一個變量
var anotherFactorial = factorial;
factorial = null;

// 調用遞歸函數
console.log(anotherFactorial(3));

運行結果:

TypeError: factorial is not a function

這裡提示錯誤,說factorial不是一個函數,因為我們已經把factorial設置為null,而在執行 anotherFactorial(3) 時,是通過factorial(num-1) 來遞歸調用的,所以就報錯,因為已經把factorial設置為null。

解決的策略就是使遞歸調用的函數內部不要出現外部定義的函數名。

我們可以通過命名表達式來實現(注意括號的使用):

var factorial = (function f(num){
  if(num <= 1){
    return num;
  }else {
    return num * f(num-1);
  }
});

console.log(factorial(3)); //6

var anotherFactorial = factorial;
factorial = null;

console.log(anotherFactorial(3)); //6

理解匿名函數

匿名函數:function後面沒有跟著函數名的函數

我們常見的匿名函數有函數表達式:

var functionName = function(arguments){
  //函數體
}

當然匿名函數也可以不要賦值給變量:

(function(num){
  console.log(num+1);
})(3);

運行結果:

4

匿名函數也可以作為函數返回:

function person(){
  return function(){
    console.log("TabWeng");
  }
}
person()();

運行結果:

TabWeng

對於 person()(),有些人不太理解,其實不難理解,這裡解釋一下:

首先來看一下 person(),我們是不是得到一個返回的匿名函數 function(){console.log("TabWeng");},既然我們得到了一個函數,對於函數的調用,是不是給它的尾部加一個括號就可以了,所以就寫成person()()

理解閉包

閉包:有權訪問另一個函數作用域的函數

匿名函數是function後面沒有跟著函數名的函數,和閉包的定義不同,盡管這兩個稱呼經常說的是同一個函數,但是根據功能的不同,應該加以區分。

如果你理解作用域鏈,那麼閉包就非常好理解。通過閉包的這種特性,我們可以來實現JavaScript的很多模式,更加靈活的運用JavaScript。

如果要訪問一個函數的作用域,我們可以在函數裡面創建一個閉包,對於閉包而言,被訪問的函數處在閉包作用域鏈的第二層(從前端到終端的順序),而作用域鏈的指針指向的是整個活動對象。

有一個典型的例子不得不講:

function printNum(){

  var nums = [];
  for(var i = 0; i < 3; i++){
    nums[i] = function(){
      return i;
    }
  }

  for(var j = 0; j < nums.length; j++){
    console.log(nums[j]());
  }
}

printNum();

運行結果:

3
3
3

為什麼會得到這樣的結果,存在兩個疑問:

  1. 為什麼結果是3?不應該是2嗎?
  2. 為什麼結果都是3?

我們先來分析一下出現的原因:

首先針對問題2,在閉包裡面有return i;,而在閉包中,i是沒有定義的,根據作用域鏈,會向上一層作用域鏈尋找i,在上一層中我們發現了i,我們是通過尋找上一層的活動對象來找到i的,既然是活動對象,裡面的數值就是最終的值,所有此時的i已經是最終的值3了,我獲得的i就是最終的值3。因此打印出來每個結果都是3。

知道了問題2出現的原因,那麼問題1也就是自然明了,在for循環中,i加到等於3,通過i<3,使i沒有進入for循環的裡面,盡管 i==3 沒有進入for循環,但是閉包在獲取i的時候,獲取的就是i的最後一個值3。

通過立即執行函數來解決這個問題:

function printNum(){

  var nums = [];
  for(var i = 0; i < 3; i++){
    nums[i] = function(num){
      return num;
    }(i);
  }

  for(var j = 0; j < nums.length; j++){
    console.log(nums[j]);
  }
}

printNum();

運行結果:

0
1
2

性能優化

使用閉包時,因為閉包的作用域鏈會引用活動對象,使這個活動對象無法被回收(內存),而如果這個引用一直存在,那麼內存將一直無法得到釋放。通常的解決方法是解除這個引用。(解除引用的方法很多,主要是要有這個性能優化的思想,知道存在的問題)。

閉包的運用

(function(){
  //私有作用域
})();

看這塊代碼,之所以稱為私有,通過作用域鏈我們可以知道,外部不能訪問裡面。

而通過閉包可以實現提供公有方法而使外部能對私有變量進行訪問。

function Person(name){
  var name = name;
  var sayName = function(){
    console.log(name);
  };

  this.publicSayName = function(){
    console.log("我訪問了name這個屬性了:"+name);
    sayName();
  };
}

var p1 = new Person("TabWeng");
p1.publicSayName();

運行結果:

我訪問了name這個屬性了:TabWeng
TabWeng

這塊代碼也可以這樣寫:

function Person(name){
  var name = name;
  var sayName = function(){
    console.log(name);
  };

  return function(){
    console.log("我訪問了name這個屬性了:"+name);
    sayName();
  };
}

var p1 = new Person("TabWeng");
p1();

運行結果:

我訪問了name這個屬性了:TabWeng
TabWeng

通過返回的匿名函數獲得私有變量。

當然,也可以這樣寫:

function Person(name){
  var name = name;
  var sayName = function(){
    console.log(name);
  };

  return {
    printString:"我訪問了name這個屬性了:"+name,
    sayName:sayName,
    publicMethod:function(){
      sayName();
    }
  };
}

var p1 = new Person("TabWeng");
console.log(p1.printString);
p1.sayName();
p1.publicMethod();

運行結果:

我訪問了name這個屬性了:TabWeng
TabWeng
TabWeng

這就是模塊模式,返回的是一個對象,盡管是對象字面量的形式,也可以得到私有變量。

參考

  • 《JavaScript高級程序設計》
XML學習教程| jQuery入門知識| AJAX入門| Dreamweaver教程| Fireworks入門知識| SEO技巧| SEO優化集錦|
Copyright © DIV+CSS佈局教程網 All Rights Reserved