函數聲明定義函數編程
function functionName(arg0, arg1, arg2) { //函數體 } //只在 Firefox、Safari、Chrome 和 Opera 有效 alert(functionName.name); //"functionName"
函數聲明提高閉包
//在執行代碼以前會先讀取函數聲明 sayHi(); function sayHi(){ alert("Hi!"); }
函數表達式定義函數app
var functionName = function(arg0, arg1, arg2){ //函數體 };
匿名函數(拉姆達函數)函數
//沒有函數提高 sayHi(); //錯誤:函數還不存在 var sayHi = function(){ alert("Hi!"); }; //不要在if語句中使用函數聲明 if(condition){ function sayHi(){ alert("Hi!"); } } else { function sayHi(){ alert("Yo!"); } } //使用函數表達式定義函數 var sayHi; if(condition){ sayHi = function(){ alert("Hi!"); }; } else { sayHi = function(){ alert("Yo!"); }; }
函數做爲其餘函數的值返回this
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; } }; }
遞歸函數是在一個函數經過名字調用自身的狀況下構成的prototype
function factorial(num){ if (num <= 1){ return 1; } else { return num * factorial(num-1); } } var anotherFactorial = factorial; factorial = null; alert(anotherFactorial(4)); //出錯!
arguments.callee 是一個指向正在執行的函數的指針,所以能夠用它來實現對函數的遞歸調用指針
function factorial(num){ if (num <= 1){ return 1; } else { return num * arguments.callee(num-1); } }
概念:指有權訪問另外一個函數做用域中的變量的函數code
建立閉包的常見方式,就是在一個函數內部建立另外一個函數component
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; } }; }
原理:對象
閉包與變量
閉包只能取得包含函數中任何變量的最後一個值
//在每一個函數內部 i 的值都是 10 function createFunctions(){ var result = new Array(); for (var i=0; i < 10; i++){ result[i] = function(){ return i; }; } return result; } createFunctions();
建立另外一個匿名函數強制讓閉包的行爲符合預期:
//每一個函數都應該返本身的索引值 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對象
通常狀況:this 對象是在運行時基於函數的執行環境綁定的
匿名函數的this對象:
匿名函數的執行環境具備全局性,其 this 對象一般指向 window
var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ return function(){ return this.name; }; } }; alert(object.getNameFunc()()); //"The Window"(在非嚴格模式下)
解決辦法:把外部做用域中的 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"
this 的值可能會意外地改變的特殊狀況:
var name = "The Window"; var object = { name : "My Object", getName: function(){ return this.name; } }; object.getName(); //"My Object" (object.getName)(); //"My Object" (object.getName = object.getName)(); //"The Window",在非嚴格模式下
內存泄漏
閉包的做用域鏈中保存着一個HTML 元素,那麼就意味着該元素將沒法被銷燬
function assignHandler(){ var element = document.getElementById("someElement"); element.onclick = function(){ alert(element.id); }; }
解決辦法:
function assignHandler(){ var element = document.getElementById("someElement"); var id = element.id; element.onclick = function(){ alert(id); }; element = null; }
JavaScript 沒有塊級做用域:在塊語句中定義的變量,其實是在包含函數中而非語句中建立的
function outputNumbers(count){ for (var i=0; i < count; i++){ alert(i); } var i; //從新聲明變量,也不會改變它的值 alert(i); //計數 }
使用自調用函數產生塊級做用域(一般稱爲私有做用域)
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; }
特權方法:有權訪問私有變量和私有函數的公有方法
建立特權方法的方式之一:在構造函數中定義特權方法
基本模式:
function MyObject(){ //私有變量和私有函數 var privateVariable = 10; function privateFunction(){ return false; } //特權方法 this.publicMethod = function (){ privateVariable++; return privateFunction(); }; }
利用私有和特權成員,能夠隱藏那些不該該被直接修改的數據
function Person(name){ this.getName = function(){ return name; }; this.setName = function (value) { name = value; }; } var person = new Person("Nicholas"); alert(person.getName()); //"Nicholas" person.setName("Greg"); alert(person.getName()); //"Greg"
缺點:必須使用構造函數模式來達到這個目的,構造函數模式的缺點是針對每一個實例都會建立一樣一組新方法,而使用靜態私有變量來實現特權方法就能夠避免這個問題
建立特權方法的方式之二:靜態私有變量(見下)
靜態私有變量
基本模式
(function(){ //私有變量和私有函數 var privateVariable = 10; function privateFunction(){ return false; } //構造函數 MyObject = function(){}; //公有/特權方法 MyObject.prototype.publicMethod = function(){ privateVariable++; return privateFunction(); }; })();
與在構造函數中定義特權方法的主要區別:私有變量和函數是由實例共享的。因爲特權方法是在原型上定義的,所以全部實例都使用同一個函數。而這個特權方法,做爲一個閉包,老是保存着對包含做用域的引用。
(function(){ var name = ""; Person = function(value){ name = value; }; Person.prototype.getName = function(){ return name; }; Person.prototype.setName = function (value){ name = value; }; })(); var person1 = new Person("Nicholas"); alert(person1.getName()); //"Nicholas" person1.setName("Greg"); alert(person1.getName()); //"Greg" var person2 = new Person("Michael"); alert(person1.getName()); //"Michael" alert(person2.getName()); //"Michael"
優勢:以這種方式建立靜態私有變量會由於使用原型而增進代碼複用,但每一個實例都沒有本身的私有變
量。多查找做用域鏈中的一個層次,就會在必定程度上影響查找速度。而這正是使用閉包和私有變量的一個顯明的不足之處
模塊模式
單例:指的就是隻有一個實例的對象
var singleton = { name : value, method : function () { //這裏是方法的代碼 } };
模塊模式:爲單例建立私有變量和特權方法
var singleton = function(){ //私有變量和私有函數 var privateVariable = 10; function privateFunction(){ return false; } //特權/公有方法和屬性 return { publicProperty: true, publicMethod : function(){ privateVariable++; return privateFunction(); } }; }();
從本質上來說,這個對象字面量定義的是單例的公共接口。這種模式在須要對單例進行某些初始化,同時又須要維護其私有變量時是很是有用的
var application = function () { //私有變量和函數 var components = new Array(); //初始化 components.push(new BaseComponent()); //公共 return { getComponentCount: function () { return components.length; }, registerComponent: function (component) { if (typeof component == "object") { components.push(component); } } }; }();
若是必須建立一個對象並以某些數據對其進行初始化,同時還要公開一些可以訪問這些私有數據的方法,那麼就可使用模塊模式。以這種模式建立的每一個單例都是 Object 的實例,由於最終要經過一個對象字面量來表示它。事實上,這也沒有什麼;畢竟,單例一般都是做爲全局對象存在的,咱們不會將它傳遞給一個函數。所以,也就沒有什麼必要使用instanceof
操做符來檢查其對象類型了
加強的模塊模式
加強:在返回對象以前加入對其加強的代碼的,這種加強的模塊模式適合那些單例必須是某種類型的實例,同時還必須添加某些屬性和(或)方法對其加以加強的狀況
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; }();
或者這樣寫:
var application = function(){ //私有變量和函數 var components = new Array(); //初始化 components.push(new BaseComponent()); //建立 application 的一個局部副本 var app = new BaseComponent(); //公共接口 app.getComponentCount = function(){ return components.length; }; app.registerComponent = function(component){ if (typeof component == "object"){ components.push(component); } }; //返回這個副本 return app; }();
一、在 JavaScript 編程中,函數表達式是一種很是有用的技術。使用函數表達式能夠無須對函數命名,從而實現動態編程。匿名函數,也稱爲拉姆達函數,是一種使用 JavaScript 函數的強大方式。如下總結了函數表達式的特色:
二、當在函數內部定義了其餘函數時,就建立了閉包。閉包有權訪問包含函數內部的全部變量,原理以下:
三、使用閉包能夠在 JavaScript 中模仿塊級做用域(JavaScript 自己沒有塊級做用域的概念),要點以下:
四、閉包還能夠用於在對象中建立私有變量,相關概念和要點以下:
五、JavaScript 中的函數表達式和閉包都是極其有用的特性,利用它們能夠實現不少功能。不過,由於建立閉包必須維護額外的做用域,因此過分使用它們可能會佔用大量內存