面向對象的JavaScript-小結
Javascript中的類
類是對象的模板,用於創建共享一系列屬性和方法的類似對象。
使用new關鍵字調用函數,可以創建對象實例。
1 function Accommodation(){}; 2 3 var house = new Accommodation(); 4 5 var apartment = new Accommodation();
同一模板的對象實例之間互無關聯,這些對象實例是完全獨立的變量,只不過共享同一個模板結構而已。
類的擴充:
1.找出對象的構造器
通過new關鍵字創建的對象的實例,有一個額外的屬性:constructor,該屬性指向創建該對象時所使用的JavaScript構造函數。
1 house.constructor === Accommodation;//true 2 3 apartment.constructor === Accommodation;//true
關鍵字instanceof也可以檢查對象是否是某個構造函數的實例。
house instanceof Accommodation; apartment instanceof Accommmodation;
2.通過原型添加屬性和方法
Javascript中的每個函數(構造器)都有一個叫prototype的屬性,這個屬性指向一個對象,我們用關鍵字new來創建一個“類”的對象實例時,實例中包含的屬性和方法都來自prototype所指向的這個對象。
//定義構造函數 function Accommodation(){} //為這個類添加屬性 Accommodation.prototype.floors = 0; Accommodation.prototype.rooms = 0; Accommodation.prototype.sharedEntrance = false; //為這個類添加方法 Accommodation.prototype.lock = function(){}; Accommodation.prototype.unlock = function(){}; //創建對象實例 var house = new Accommodation();var apartment = new Accommodation(); //讀取對象實例的屬性 console.log(house.floors);//0 console.log(apartment.rooms);//0 //修改對象屬性的值 house.floors = 2; apartment.sharedEntrance = true; //調用對象實例的方法 house.unlock(); apartment.lock();
prototype屬性本身是一個對象,這個對象被關聯在扮演“類”這個角色的函數身上,因此,還可以用對象直接量標記法為構造函數添加屬性和方法。
//定義構造函數 function Accommodation(){} //為這個類添加屬性和方法 Accommodation.prototype = { floors: 0, rooms: 0, sharedEntrance: false, lock: function(){}, unlock: function(){} }
prototype這個關鍵字有一個強大的特性:允許在對象實例已經被創建後繼續添加屬性和方法,新增屬性和方法會自動添加到所有對象實例中,不管是已創建的還是將要創建的。
3.通過作用域添加屬性和方法
javascript函數體內定義的變量和函數,作用域都限於函數體內,在該函數體外無法訪問這些變量和函數——對這些函數和變量來說,包裹他們的外層函數提供了一個沙箱般的編程環境,或者說一個閉包。
4.上下文和this關鍵字
javascript中this關鍵字代表的是一個函數的上下文環境,這個上下文環境大多數情況下指的是函數運行時封裝這個函數的那個對象。
//在所有函數之外,this表示的是全局window對象 console.log(this === window);//true //因為doSomething函數在對象外部被調用,this指向的是瀏覽器中的window對象 function doSomething(){ console.log(this === window); //true } doSomething(); var house = { floors: 2, isLocked = false, lock: function(){ console.log(this === house);//true 因為this關鍵字代表的是包含這個方法的那個對象 //也可以把this看做house對象的替身,可以使用點標記法 this.isLocked = true; }, } house.lock(); console.log(house.isLocked);//true
對象中的嵌套函數其上下文環境是全局的window對象,而非包含它的那個對象。但我們可以在this指向包含這個函數的對象時,將this的值保存在一個變量中,在用到該對象時用這個變量代替。
var apartment = { islocked: false, lock: function(){ var that = this; //設置isLocked屬性 this.isLocked = true; function doSomething(){ console.log(this === apartment);//false console.log(this === window);//false console.log(that === apartment);//false //通過that變量來修改apartment對象的isLocked屬性 that.isLocked = false; } doSomething(); }, } apartment.lock(); console.log(apartment.isLocked);//false
在使用new關鍵字創建對象時,this指向的值和一般情況下又有區別,這種情況下this指向的是通過構造函數所創建的那個對象實例。也因為這個特性,得以在構造函數中通過this來設置所有對象實例的屬性和方法,而非像之前那樣使用prototype關鍵字。
//定義一個新的構造函數來表示一種住宅 function Accommodation(){ //this關鍵字指向的是通過這個“類”創建的對象實例 this.floors = 0; this.rooms = 0; this.sharedEntrance = false; this.isLocked = false; this.lock = function(){ //函數中的this一般指向包含函數的那個對象,本例中的this指向的是創建的對象實例,因為這個函數是通過這個被創建的對象實例來調用的 this.isLocked = true; }; this.unlock = function(){ this.isLocked = false; } } //通過構造函數來創建對象實例 var house = new Accommodation(); var apartment = new Accommodation(); //讀取和修改屬性值,調用方法等操作都和普通對象一樣 console.log(house.floors); house.floors = 2; apartment.lock();
開發者一般會結合使用prototype和this關鍵字來定義對象實例的屬性和方法,其中前者用來定義方法,後者用來定義屬性。每次通過構造器創建一個新的對象實例,構造函數都會被執行一次。之所以結合使用這兩個關鍵字,是為了避免每次初始化一個對象實例時都要執行那些對方法進行初始化的代碼。通過prototype關鍵字定義的方法只需定義一次,然後就可以為所有通過這個構造函數創建的對象所用,這使得對象的創建變得更加高效;
開發者們喜歡在構造函數中使用this關鍵字來設置屬性的另一個原因是可以給構造函數傳遞參數,這樣我們就能在調用構造函數時通過傳遞參數來對某些屬性進行初始化了。
我們也可以向構造函數傳遞一個對象直接量作為唯一的參數,這個對象直接量包含了進行屬性設置所需的所有初始值。
5.方法的鏈式調用
要實現鏈式調用,只需在“類”中的每個方法最後通過this關鍵字返回對象實例的引用即可。
6.繼承
傳統編程語言的一項關鍵功能就是可以創建一些新的類來繼承或者擴展某個父類的屬性和方法,這些新的類和該父類都有某種類似的邏輯關聯,這些新的類被稱為子類。
Javascript中也可以實現這種繼承,不過是通過Javascript的原型鏈來實現,被稱為原型繼承。
//定義一個有兩個方法的類 function Accommodation(){} Accommodation.prototype.lock = function(){} Accommodation.prototype.unlock = function(){} //定義一個構造函數,它將成為我們的子類 function House(defaults) { defaults = defaults || {}; //將本類所有實例的floors屬性初始化為2 this.floors = 2; this.rooms = defaults.rooms ||7; } //將House類的原型設為Accommodation“類”的一個實例,使用關鍵字new來調用Accommodation的構造函數,這樣就能創建並返回一個包含所有屬性和方法的對象。這個對象被傳遞給House"類"的原型,這樣House"類"就得以繼承Accommodation 的所有內容 House.prototype = new Accommodation(); //對象實例的constructor屬性指向創建該對象的那個構造函數,然而由於House繼承了Accommodation的所有內容,constructor值也被復制了,所以我們現在需要重設constructor值,使其指向新的子類。如果沒有這一步,通過House"類"創建的對象就會報告說他們是通過Accommodation“類”創建的。 House.prototype.constructor = House;
封裝:
當通過繼承對已有的類進行改變或特殊化時,父類的所有屬性和方法對子類都是可用的,在子類中不需要額外聲明或定義任何東西就能使用父類的屬性和方法,這種特性被稱為封裝。子類之需要定義那些在父類基礎上新增的屬性和方法即可;
多態:
在構造一個新的子類來繼承並擴展一個“類”的時候,你可能需要將某個方法替換為一個同名的新方法,新方法和原方法功能類似,但對子類做出了針對性的改變,這就是多態,Javascript中實現多態,只需重寫一個函數並給它一個和原方法形同的方法即可;
call和apply
arguments對象
公有,私有以及受保護的屬性和方法
在構造函數中通過var定義的變量其作用域局限於該構造函數內——在prototype上定義的方法無法訪問這個變量,因為這些方法有自己的作用域。要想通過公有的方法來訪問私有的變量,需要創建一個同時包含兩個作用域的新作用域,為此,我們可以創建一個自我執行的函數,稱為閉包。該函數完全包含了類的定義,包括所有私有變量以及原型方法。
javascript有一個非強制性的但很有用的編程慣例,就是對所有私有變量或函數名加一個下劃線(_)作為前綴,以標識他們是私有的,這有助於你及項目組的其他成員更好的理解每個類的作者的意圖。
// 我們將類定義包在一個自我執行的函數裏,這個函數返回我們所創建的類並將其保存在一個變量中,一邊在後面的代碼中使用 var Accommodation = (function(){ //定義類的構造函數,因為處於一個新的函數內,我們也切換到了一個新的作用域中,所以可以使用與保存函數返回值得那個變量相同的名字 function Accommodation(){ //此處定義的變量都是私有的,這些變量在當前作用域之外不可用,可以通過變量名添加下劃線前綴來標識這一點 var _isLocked = false, _isAlarmed = false, _alarmMessage = "Alarm activated!"; //僅在當前作用域中定義的函數(未在構造函數上定義的)也是私有的 function _alarm(){ _isAlarmed = true; alert(_alarmMessage); } function _disableAlarm(){ _isAlarmed = false; } //所有定義在原型上的方法都是公有的,當我們在此處創建愛你的類在閉包結束處被返回後,就可以在當前作用域之外訪問這些方法了 Accommodation.prototype.lock =function(){ _isLocked = true; _alarm(); } Accommodation.prototype.unlock = function(){ _isLocked = false; _disableAlarm(); } //定義一個getter函數來對私有變量_islocked的值進行只讀訪問——相當於把該變量定義為了受保護的 Accommodation.prototype.getIsLocked = function(){ return _isLocked; } //定義一個setter函數來對私有變量_alarmMessage進行只寫訪問——相當於將其定義為了受保護的 Accommodation.prototype.setAlarmMessage = function(message){ _alarmMessage = message; } //返回在這個作用域中創建的類,使之在外層作用域中即後面的代碼中的所有位置都可用,只有公有的屬性和方法是可用的 return Accommodation; } }());
面向對象的JavaScript-小結