1. 程式人生 > >《Javascript 高階程式設計(第三版)》筆記0xB 函式表示式

《Javascript 高階程式設計(第三版)》筆記0xB 函式表示式

 

目錄

遞迴

閉包

    閉包與變數

    關於this物件

    記憶體洩漏

模仿塊級作用域

私有變數

       特權方法(privileged method)

    靜態私有變數

    模組模式

    增強的模組模式


        建立一個函式並將它賦值給變數 functionName。這種情況下建立的函式叫做匿名函式(anonymous function),因為 function 關鍵字後面沒有識別符號。匿名函式有時候也叫拉姆達函式,name 屬性是空字串。

//不要這樣做!一般後面的會覆蓋前面的
if(condition){
  function sayHi(){
    alert("Hi!");
  }
} else {
  function sayHi(){
    alert("Yo!");
  }
}
//可以這樣做
var sayHi;
if(condition){
  sayHi = function(){
    alert("Hi!");
  };
} else {
  sayHi = function(){
    alert("Yo!");
  };
}

遞迴

        arguments.callee 是一個指向正在執行的函式的指標,因此可以用它來實現對函式的遞迴呼叫。

function factorial(num){
  if (num <= 1){
    return 1;
  } else {
    return num * arguments.callee(num-1);
  }
}
//嚴格模式下,不能通過指令碼訪問 arguments.callee,訪問這個屬性會導致錯誤。可以使用命名函式表示式來達成相同的結果。
var factorial = (function f(num){
  if (num <= 1){
    return 1;
  } else {
    return num * f(num-1);
  }
});

閉包

        閉包是指有權訪問另一個函式作用域中的變數的函式。

//建立閉包的常見方式,就是在一個函式內部建立另一個函式
function createComparisonFunction(propertyName) {
	return function(object1, object2){
		var value1 = object1[propertyName];//訪問了外部函式中的變數 propertyName
		var value2 = object2[propertyName];
		if (value1 < value2){
			return -1;
		} else if (value1 > value2){
			return 1;
		} else {
			return 0;
		}
	};
}
作用域鏈本質上是一個指向變數物件的指標列表,它只引用但不實際包含變數物件。
function compare(value1, value2){
  if (value1 < value2){
    return -1;
  } else if (value1 > value2){
    return 1;
  } else {
    return 0;
  }
}
var result = compare(5, 10);

        無論什麼時候在函式中訪問一個變數時,就會從作用域鏈中搜索具有相應名字的變數。一般來講,當函式執行完畢後,區域性活動物件就會被銷燬,記憶體中僅儲存全域性作用域(全域性執行環境的變數物件)。但是,閉包的情況又有所不同。

//建立函式
var compareNames = createComparisonFunction("name");
//呼叫函式
var result = compareNames({ name: "Nicholas" }, { name: "Greg" });
//解除對匿名函式的引用(以便釋放記憶體)
compareNames = null;

    閉包與變數

        作用域鏈的這種配置機制引出了一個值得注意的副作用,即閉包只能取得包含函式中任何變數的最後一個值。

/*表面上看,似乎每個函式都應該返自己的索引值,即位置0的函式返回0,位置1的函式返回1,以此類推。但實際上,每個函式都返回 10。
因為每個函式的作用域鏈中都儲存著createFunctions()函式的活動物件,所以它們引用的都是同一個變數i。 
當createFunctions()函式返回後,變數 i 的值是 10,此時每個函式都引用著儲存變數 i 的同一個變數物件,
所以在每個函式內部 i 的值都是 10。*/
function createFunctions(){
  var result = new Array();
  for (var i=0; i < 10; i++){
    result[i] = function(){
      return i;
    };
  }
  return result;
}
//通過建立另一個匿名函式強制讓閉包的行為符合預期
function createFunctions(){
  var result = new Array();
  for (var i=0; i < 10; i++){
    result[i] = function(num){
      return function(){
        return num;
      };
    }(i);
  }
  return result;
}

    關於this物件

var name = "The Window";
var object = {
  name : "My Object",
  getNameFunc : function(){
    return function(){
      return this.name;
    };
  }
};
alert(object.getNameFunc()()); //"The Window"(在非嚴格模式下)

        每個函式在被呼叫時都會自動取得兩個特殊變數: this 和 arguments。內部函式在搜尋這兩個變數時,只會搜尋到其活動物件為止,因此永遠不可能直接訪問外部函式中的這兩個變數。

        不過,把外部作用域中的 this 物件儲存在一個閉包能夠訪問到的變數裡,就可以讓閉包訪問該物件了。

var name = "The Window";
var object = {
  name : "My Object",
  getNameFunc : function(){
    var that = this;
    return function(){
      return that.name;
    };
  }
};
alert(object.getNameFunc()()); //"My Object"

    記憶體洩漏

         由於 IE9 之前的版本對 JScript 物件和 COM 物件使用不同的垃圾收集例程因此閉包在 IE 的這些版本中會導致一些特殊的問題。具體來說,如果閉包的作用域鏈中儲存著一個HTML 元素,那麼就意味著該元素將無法被銷燬。

function assignHandler(){
    var element = document.getElementById("someElement");
        element.onclick = function(){
        alert(element.id);
    };
}

模仿塊級作用域

        JavaScript 沒有塊級作用域的概念。這意味著在塊語句中定義的變數,實際上是在包含函式中而非語句中建立的。

function outputNumbers(count){
  for (var i=0; i < count; i++){
    alert(i);
  }
  alert(i); //計數
}

        在 Java、 C++等語言中,變數 i只會在 for 迴圈的語句塊中有定義,迴圈一旦結束,變數 i 就會被銷燬。可是在 JavaScrip 中,變數 i是定義在 ouputNumbers()的活動物件中的,因此從它有定義開始,就可以在函式內部隨處訪問它。

        JavaScript 從來不會告訴你是否多次聲明瞭同一個變數;遇到這種情況,它只會對後續的宣告視而不見(不過,它會執行後續宣告中的變數初始化)。匿名函式可以用來模仿塊級作用域並避免這個問題。

/*
*  (function(){
*    //這裡是塊級作用域
*  })();
*以上程式碼定義並立即呼叫了一個匿名函式。將函式宣告包含在一對圓括號中,表示它實際上是一個函式表示式。
*而緊隨其後的另一對圓括號會立即呼叫這個函式。
*/
function outputNumbers(count){
  (function () {
    for (var i=0; i < count; i++){
      alert(i);
    }
  })();
  alert(i); //導致一個錯誤!
}

(function(){
  var now = new Date();
  if (now.getMonth() == 0 && now.getDate() == 1){
    alert("Happy new year!");
  }
})();

私有變數

        嚴格來講,JavaScript 中沒有私有成員的概念;所有物件屬性都是公有的。不過,倒是有一個私有變數的概念。任何在函式中定義的變數,都可以認為是私有變數,因為不能在函式的外部訪問這些變數。私有變數包括函式的引數、區域性變數和在函式內部定義的其他函式。

function add(num1, num2){
  var sum = num1 + num2;
  return sum;
}
/*
*    在這個函式內部,有 3 個私有變數: num1、 num2 和 sum。在函式內部可以訪問這幾個變數,但在
*函式外部則不能訪問它們。如果在這個函式內部建立一個閉包,那麼閉包通過自己的作用域鏈也可以訪問這
*些變數。而利用這一點,就可以建立用於訪問私有變數的公有方法。
*/

       特權方法(privileged method)

//在建構函式中定義特權方法
function MyObject(){
  //私有變數和私有函式
  var privateVariable = 10;
  function privateFunction(){
    return false;
  }
  //特權方法
  this.publicMethod = function (){
    privateVariable++;
    return privateFunction();
  };
}

    靜態私有變數

//在私有作用域中定義私有變數或函式,同樣也可以建立特權方法
(function(){
	//私有變數和私有函式
	var privateVariable = 10;
	function privateFunction(){
		return false;
	}
	//建構函式
	MyObject = function(){
	};
	//公有/特權方法
	MyObject.prototype.publicMethod = function(){
		privateVariable++;
		return privateFunction();
	};
})();

    模組模式

//通過為單例新增私有變數和特權方法能夠使其得到增強
var singleton = function(){
	//私有變數和私有函式
	var privateVariable = 10;
	function privateFunction(){
		return false;
	}
	//特權/公有方法和屬性
	return {
		publicProperty: true,
		publicMethod : function(){
			privateVariable++;
			return privateFunction();
		}
	};
}();

    增強的模組模式

var singleton = function(){
	//私有變數和私有函式
	var privateVariable = 10;
	function privateFunction(){
		return false;
	}
	//建立物件
	var object = new CustomType();
	//新增特權/公有屬性和方法
	object.publicProperty = true;
	object.publicMethod = function(){
		privateVariable++;
		return privateFunction();
	};
	//返回這個物件
	return object;
}();