標籤:java 使用 資料 javascript 問題 cti
物件導向的程式設計都有一個特點,就是它們都有一個類的概念。而Javascript中沒有類的概念,因此它的對象的定義也與其他語言不一樣,在Javascript中把對象定義為:無序屬性的集合,其屬性可以包含基本值,對象和函數。也相當於說對象是一組沒有特定順序的值。
1、理解對象
建立自訂對象的最簡單方式就是建立一個Object的執行個體。然後再為它添加屬性和方法:
var person = new Object(); person.name = ‘Nicholas‘; person.age = 29; person.job = ‘Software Engineer‘; person.sayName = function() { alert(this.name); }
上面的例子建立了一個person對象並為添加了三個屬性和一份方法。現在對象字面量成為建立這種對象的首選模式,上面的例子可以寫成這樣:
var person = { name: ‘Nicholas‘, age: 29, job: ‘Software Engineer‘, sayName: function() { alert(this.name); } }
這個例子中的person對象和前面的是一樣的。
1.1、屬性類型
Javascript中有兩種屬性:資料屬性和訪問器屬性
資料屬性
資料屬性包含一個資料值的位置。在這個位置可以讀取和寫入值,資料屬性有四個描述其行為的特性:
- [[Configurable]]:表示能否通過delete刪除屬性從而重新定義蘇醒,能否修改屬性的特性,或者能否把屬性修改為訪問器屬性,前面定義的屬性,它們的特性預設值是true。
- [[Enumerable]]:表示能否通過for-in迴圈返回屬性。前面的例子中的特性預設是true。
- [[Writable]]:表示能否修改屬性的值,前面例子中的預設為true。
- [[Value]]:包含這個屬性的資料值。讀取屬性值的時候,從這個位置讀,寫入屬性的時候,把新值儲存在這個位置,這個特性的預設值為undefined。
前面例子中定義的對象上的屬性,它們的[[Configurable]],[[Enumerable]],[[Writable]]都預設true,而[[Value]]屬性被設定為指定的值。如:name:‘Nocholas‘。要修改屬性預設的特性,必須使用Object.defineProperty()方法,這個方法接收三個參數:屬性所在的對象,屬性的名字和一個描述符對象。其中描述符必須是以上四個中的一個。
var person = {}; Object.defineProperty(person,‘name‘,{ writable:false, value:‘hehe‘ });
這個例子中,person對象的name屬性的值是唯讀,如果嘗試為它指定值,在非strict 模式下,賦值操作將被忽略,在strict 模式下,就導致拋出錯誤。
類似的規則也適用於不可配置的屬性:
var person = {}; Object.defineProperty(person,‘name‘,{ configurable:false, value:‘hehe‘ });
上面例子表示不可從對象中刪除屬性,而且一旦把屬性變成不可配置的就不能再變回來,再調用Object.defineProperty()方法修改除writabel之外的所有特性,都會導致錯誤。
2、訪問器屬性
訪問器屬性不包含資料值:它們包含一對兒getter和setter函數(不是必須的)。在讀取存取器屬性是會調用getter函數,在寫入訪問器屬性時,會調用setter方法。訪問器屬性有四個特性:
- [[Configurable]]:表示能否通過delete刪除屬性從而重新定義屬性,能否修改屬性,能否把屬性修改為資料屬性。
- [[Enumerable]]:表示能否通過for-in迴圈返回屬性。
- [[Get]]:在讀取屬性時調用的函數,預設為undefined。
- [[Set]]:在寫入屬性時調用的函數,預設為undefined。
訪問器屬性不能直接定義,需使用Object.defineProperty()來定義。
var book = { _year: 2004, edition: 1 }; Object.defineProperty(book,‘year‘,{ get:function() { return this._year; } set:function(newValue) { if(newValue > 2004) { this._year = newValue; this.edition += newValue - 2004; } } }); book.year = 2005; alert(book.edition);
_year前面的底線表示是一種常見的記號,表示只能通過對象方法訪問的屬性。不一定一定要同時制定getter和setter方法。只指定getter方法意味著屬性是不能寫,嘗試寫入會被忽略。只指定setter方法表示屬性是不能被讀,會返回undefined。
2、建立對象
雖然Object建構函式或對象字面量都可以用來建立單個對象,但這些方式有一個明顯的缺點:使用同一個介面建立很多個物件,會產生大量的重複代碼。
2.1、原廠模式
考慮都Javascript中無法建立類,開發人員就發明了一種函數,用函數來封裝以特定介面建立對象的細節:
function createPerson(name,age,job) { var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function() { alert(name); }; return 0; } var person1 = createPerson(‘alex‘,23,‘Software Engineer‘); var person2 = createPerson(‘kim‘,5,‘boy‘);
可以無數次的調用這個函數,每次都會返回一個包含三個屬性一個方法的對象。原廠模式解決了建立多個相似對象的問題,但是沒有解決對象識別的問題,(即怎麼知道一個對象的類型)。
2.2、建構函式模式
在Javascript中,像Object和Array這樣的原聲建構函式,在運行時會自動出現在執行環境中。此外,也可以建立自訂的建構函式,從而自訂物件類型的屬性和方法。可以使用建構函式模式將前面的例子重寫:
function Person(name,age,job) { this.name = name; this.age = age; this.job = job; this.sayName = function() { alert(this.name); } } var person1 = new Person(‘alex‘,23,‘Software Engineer‘); var person2 = new Person(‘kim‘,5,‘boy‘);
Person()中的代碼除了與createPerson()中相同的部分外,還存在以下不同:
- 沒有顯示地建立對象
- 直接將屬性和方法賦給了this對象
- 沒有return語句
以建立Person的執行個體,必須使用new操作符。以這種方式調用建構函式實際上會經曆一下四個步驟:
- 建立一個新對象
- 將建構函式的範圍賦給新對象(因此this就指向了新對象)
- 執行建構函式中的代碼(為這個新對象添加屬性)
- 返回對象
在前面的例子中,person1和person2分別儲存這person的一個不容的執行個體。建立自訂的建構函式意味著將來可以將它的執行個體標識為一種特定的類型;而這正是建構函式模式勝過原廠模式的地方,在這個例子中,person1和person2之所以同時是Object的執行個體,是因為所有對象均繼承自Object,以這種方式定義的對象是定義在Global中的。
將建構函式當作函數
建構函式與其他函數的唯一區別就是調用它們的方式不同。任何函數,只要通過new操作符調用,就可以作為建構函式,任何函數,如果不通過new操作符,那它就和普通函數一樣。
建構函式的問題
使用建構函式的主要問題,就是每個方法都要在每個執行個體上重新建立一遍。在前面的例子中person1和person2都有sayName()方法,但兩個方法不是同一個Function對象的執行個體。此時的建構函式也可以這樣定義:
function Person(name,age,job) { this.name = name; this.age = age; this.job = job; this.sagName = new Function("alert(‘this.name‘)"); }
但是建立兩個完成同樣任務的Function執行個體的卻沒有必要;因此,可以通過把函數定義轉移到建構函式外面來解決這個為問題:
function Person(name,age,job) { this.name = name; this.age = age; this.job = job; this.sayName = sayName; } function sayName() { alert(this.name); }
person1和person2對象共用了在全域範圍中定義的同一個sayName()函數。這樣解決了兩個函數做同一件事情的問題,但是新問題:在全域範圍中定義的函數實際上只能被某一個對象調用,這讓全域範圍有點兒名不副實。而更讓人無法接收的是:如果對象需要定義很多方法,那就定義很多全域函數,於是我們這個自訂的參考型別就絲毫沒有封裝性了,這些問題可以通過原型模式來解決。