面對對象的語言有一個標誌,那就是他們都有類的概念,而經過類能夠建立任意多具備相同屬性和方法的對象。js語言沒有類的概念,所以他的對象也與基於類的語言中對象有所不一樣,esma-262中定義js對象爲:javascript
無序屬性的集合,其屬性能夠包含基本值、對象和函數java
ECMAScript中有兩種屬性:數據屬性和訪問器屬性編程
數據屬性包含一個數據值的位置,在這個位置能夠讀取和寫入值。數據值有四個描述其行爲的特性:數組
修改屬性值必須經過Object.defineProperty()方法,該方法接受三個參數,屬性所在的對象,屬性的名稱,和一個描述符對象描述符對象的屬性必須是上面四個特性。安全
訪問器屬性不包含數據值,它包含一對setter和getter函數。讀取調用getter,寫入調用setter,訪問器屬性包含四個特性:函數
訪問器屬性不能直接定義,必須經過Object.defineProperty()方法。性能
因爲定義多個屬性的機率很大,es5又定義了一個Object.defineProperties()方法
該方法接受兩個參數,第一個對象是要添加和修改其屬性的對象,第二個對象的屬性與第一個對象中要添加或修改的屬性一一對應,eg:this
var book = {} Object.defineProperties(book,{ _year:{ value:2004 }, edition:{ value:1 } year: { get:function(){ return this._year; } set:function(newValue){ if(newValue > 2004){ this._year = newValue; this.edition += newValue - 2004; } } } })
function createPerson(){ var obj = new Object(); obj.name = 'abc'; obj.age = 30; obj.sex = 'M'; return obj; }
工廠模式雖然解決了建立多個像是對象的問題,可是沒有解決對象識別的問題,即怎麼知道一個對象的類型。es5
function Person(){ this.name = 'abc'; this.age = 30; this.sex = 'M' }
構造函數解決了對象識別的問題,也很好用,可是也有缺點,這種方式的缺點會在每個實例中保存一份屬性的副本,會對性能形成影響。spa
function Person(){ } Person.prototype.name = 'Nicholas'; Person.prototype.age = 29; Person.prototype.job = 'Software Engineer'; Person.prototype.sayName = function(){ alert(this.name) }; var p1 = new Person(); p1.sayName(); // Nicholas var p2 = new Person(); p2.sayName();// Nicholas alert(p1.sayName == p2.sayName) //true
咱們建立的每一個函數都有一個prototype屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途包含能夠由特定類型的全部實例共享的屬性和方法。用這個對象的好處是可讓全部對象實例共享它所包含的屬性和方法。
上圖描述了Person構造函數、Person的原型對象以及Person現有的兩個實例之間的關係。此外,Person.prototype指向了原型對象,而Person.prototype.constructor又指回了Person原型對象中除了包含constructor屬性以外,還包括後來添加的其餘屬性。
每當代碼讀取某個對象的某個屬性時,都會執行一次搜索,目標是具備給定名字的屬性。搜索首先從對象實例自己開始。找到則返回,沒找到則繼續搜索指針指向的原型對象,也就是說若是原型對象和實例的屬性名相同的話,則優先執行實例的屬性。hasOwnProperty()能夠判斷屬性值是存在於實例仍是原型。p1.hasOwnProperty("name") 返回true。
單獨使用in仍是在for-in循環中使用,in操做符都會訪問到實例屬性和原型屬性。若是判斷屬性是否存在於原型中,能夠經過下面這種方式實現:
function hasPrototypeProperty(object, name){ return !object.hasOwnProperty(name) && (name in object); }
固然也能夠經過 Object.keys(Person.prototype) 來獲取,方法返回一個可枚舉屬性的字符串數組。或者Object.getOwnPropertyNames(Person.prototype)返回一個可枚舉屬性的字符串數組。可是上面兩種方法對不可枚舉的屬性失效,只能用for-in更簡單的原型語法:
function Person(){ } Person.prototype = { name : "Nicholas", age : 29, job: "Software Engineer", sayName : function () { alert(this.name); } };
可是問題是,原型的constructor指針再也不只想Person了。本質上這種寫法是重寫了prototype對象能夠增長一個從constructor並只想Person來解決:
function Person(){ } Person.prototype = { constructor : Person, name : "Nicholas", age : 29, job: "Software Engineer", sayName : function () { alert(this.name); } };
可是也有問題,這樣作會把constructor的enumerable特性被改爲true,最好的解決方案是以下:
Object.defineProperty(Person.prototype, "constructor", { enumerable: false, value: Person });
原型對象的缺點是初始化參數共享,對函數共享是很是合適,可是基本值的屬性卻會引起安全性問題。綜合構造函數模式的缺點和原型對象的缺點,經過組合使用構造函數模式與原型對象模式,構造函數模式用於定義實例屬性,而原型模式用於定義方法和共享的屬性。結果,每一個實例都會有本身的一份實例屬性副本,但同時又共享着對方法的引用,最大限度地節省了內存。
組合模式以下:
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.friends = ["Shelby", "Court"]; } Person.prototype = { constructor : Person, sayName : function(){ alert(this.name); } } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor"); person1.friends.push("Van"); alert(person1.friends); //"Shelby,Count,Van" alert(person2.friends); //"Shelby,Count" alert(person1.friends === person2.friends); //false alert(person1.sayName === person2.sayName); //true
上文爲《JavaScript高級編程》的讀書筆記,關於JavaScript面對對象編程的章節。