ECMA-262 把對象定義爲git
無序屬性的集合,其屬性能夠包含基本值、對象或者函數。github
即對象是一組沒有特定順序的值。對象的每一個屬性或方法都有一個名字,而每一個名字都映射到一個值。正由於這樣,咱們能夠把 ECMAScript 的對象想象成散列表:無非就是一組名值對,其中值能夠是數據或函數。安全
ECMAScript 第 5 版 在定義只有內部採用的特性(attribute)時, 描述了屬性(property)的各類特徵。ECMAScript 定義這些特性是爲了實現 JavaScript 引擎用的,所以在 JavaScript 中不能直接訪問它們。爲表示特性是內部值,該規範把它們放在兩對方括號中,例如 [[Enuermerable]]。app
ECMAScript 中只有兩種屬性:數據屬性和訪問器屬性。函數
[[value]]:包含這個屬性的數據值。讀取屬性值的時候,從這個位置讀;寫入屬性值的時候,把新值保存在這個位置。this
要修改屬性默認的特性,必須用 ECMAScript 5 的 Object.defineProperty()
方法。這個方法接受三個參數`: 屬性所在的對象,屬性名,和一個描述符對象。其中,描述符(descriptor)對象的屬性必須是:configurable、enumerable、writable 和 value。spa
jsvar person = {}; Object.defineProperty(person,"name",{ writable: false, value: "Nicholas", configurable: false }); alert(person.name);//"Nicholas" person.name = "PaddingMe"; alert(person.name);//"Nicholas" delete person.name; alert(person.name);//"Nicholas"
一旦把屬性定義爲不可配置特性就不能再把它變爲可配置。prototype
在調用 Object.defineProperty()
方法時,若是不指定, configurable, enumerable 和 writable 特性的默認值爲 false。指針
[[set]]: 在寫入屬性時調用的函數。默認值爲 undefined。code
訪問器不能直接定義,必須使用 object.defineProperty()
來定義。
var book = { _year: 2004, edition: 1 }; Object.defineProperty(book, "year", { get: function() { return this._year; }, set: function() { if (newValue > 2004) { this._year = newValue; this.edition += newValue -2004; } } }); book.year = 2005; alert(book.edition); //2
_year 前面的下劃線用於表示只能經過對象方法訪問的屬性。book.edition 變爲 2 這是使用訪問器屬性的常見方式。即設置一個屬性的值會致使其餘屬性發生變化。
Object.definePorperties()
接受兩個對象參數:第一個要添加或修改其屬性的對象,第二個對象的屬性 與第一個對象中要添加或修改的屬性一一對應。
Object.getOwnPropertyDescriptor()
方法獲取給定屬性的描述符。
此方法接受2個參數:屬性所在的對象和要讀取其描述符的屬性名稱。
返回值是一個對象。
jsvar book = {}; Object.defineProperties(book,{ _year: {value: 2004}, edition: {value: 1}, year: { get: function() { return this._year; }, set: function() { if (newValue > 2004) { this._year = newValue; this.edition += newValue -2004; } } } }); var descriptor = Object.getOwnPropertyDescriptor(book,"_year"); alert(descriptor.value); //2004 alert(descriptor.configurable);//false alert(typeof descriptor.get); // "undefined" var descriptor = Object.getOwnPropertyDescriptor(book,"year"); alert(descriptor.value); //undefined alert(descriptor.enumerable);//false alert(typeof descriptor.get); //function
最簡單的能夠用 Object 構造函數,或者對象字面量來建立單個對象,但這樣使用一個接口建立不少對象,會產生大量的重複代碼。
jsvar paddingme = new Object(); //用 Object 構造函數 建立對象 var paddingme = {}; //對象字面量建立對象
工廠模式是用函數來封裝以特定接口建立對象的細節。
jsfunction createPerson(name,age,job) { var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function() { alert(this.name); }; return o; } var person = createPerson("PaddingMe",25,"front-end developer");
工廠模式雖然解決了建立多個類似對象的問題,但沒有解決對象識別的問題(即怎樣知道一個對象的類型)。
jsfunction Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = function() { alert(this.name); }; } var person1 = new Person("PaddingMe",25,"front-end developer"); alert(person1.constructor == Person);//true alert(person1 instanceof Person);//true alert(person1 instanceof Object);//true
構造函數模式與工廠模式不一樣的是:
- 沒有顯式地建立對象;
- 沒有 return 語句;
- 直接將屬性和方法賦給了 this 方法。
另按照慣例,構造函數首字母都應該大寫。
建立 Person 新實例,通過了如下4個新步驟:
建立自定義的構造函數意味着未來能夠將它的實例標識爲一種特殊的類型,而這正是構造函數模式賽過工廠模式的地方。
js//看成構造函數來使用 var person = new Person("paddingme",25,"F2ER"); person.sayName();//"paddingme" //做爲普通函數來使用; Person("paddingme",25,"F2ER"); window.sayName();//"paddingme" //**當在全局做用域中調用一個函數時,this 對象老是指向 Global 對象。** //在另外一個對象的做用域中調用 var o = new Object(); Person.call(o,"paddingme",25,"F2ER"); o.sayName();// "paddingme"
構造函數建立對象的問題在於:每一個方法都要在每一個實例上從新建立一遍,會致使不一樣的做用域鏈和標識符解析。不一樣實例上的同名函數是不相等的。
咱們建立的每一個函數都有一個 prototype(原型) 屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含能夠由特定類型的全部實例共享的屬性和方法。 按照字面意思即 prototype 就是調用構造函數而建立的那個對象實例的原型對象。使用原型對象的好處就是可讓全部對象實例共享它所包含的屬性和方法。換言之,沒必要在構造函數中定義實例的信息,而是能夠將這些信息直接添加到原型對象中。
jsfunction Person() {} Person.prototype.name = "PaddingMe"; Person.prototype.age = 29; Person.prototype.job = "Front-end Engineer"; Person.prototype.sayName = function () { alert(this.name); }; var person1 = new Person(); person1.sayName(); //"PaddingMe" var person2 = new Person(); person2.sayName(); //"PaddingMe"
理解原型對象
不管何時,只要建立一個新函數,就會根據一組特定的規則爲該函數建立一個 prototype 屬性,這個屬性指向函數的原型對象。在默認狀況下,全部原型對象都會自動得到一個 constructor 屬性。這個屬性包含一個指向所在函數的指針。 建立了自定義的構造函數以後,其原型對象默認只會取得 constructor 屬性;至於其餘方法,都是從 Object 繼承而來的。當調用構造函數建立一個新實例後,該實例的內部將包含一個指針(內部屬性),指向構造函數的原型對象。ECMAScript 管此指針叫 [[prototype]]
Person 構造函數、 Person 的原型屬性以及 Person 現有的兩個實例之間的關係。在此,Person.prototype 指向了原型對象,而 Person.prototype.constructor 又指回了 Person。 原型對象中除了包含 constructor 屬性以外,還包括後來添加的其餘屬性。 Person 的每一個實例都包含一個內部屬性,該屬性指向了 Person.prototype;換句話說,它們與構造函數沒有直接的關係。
jsalert(Person.prototype.isPrototype(person1));//true alert(Person.prototype.isPrototype(person2));//true
ECMASript 5 增長了Object.getPrototypeOf()
,在全部支持的實現中,這個方法返回[[Prototype]] 的值。
jsalert(Object.getPrototypeOf(person1) == Person.prototype); // true alert(Object.getPrototypeOf(person1).name); // "PaddingMe"
每當代碼讀取某個對象的某個屬性時,都會執行一次搜索,目標是具備給定名字的屬性。搜索首先從對象實例自己開始。若在實例中找到了具備給定名字的屬性,則返回該屬性的值。若沒有,則繼續搜索指針指向的原型鍍錫i昂,在原型對象中查找是否具備給定名字的屬性。若在原型對象中找到此屬性,則返回該屬性的值。
原型最初只包括 constructor 屬性,而該屬性也是共享的,所以能夠經過對象實例訪問
當爲對象實例添加一個屬性時,這個屬性就會屏蔽原型對象中同名屬性,換句話說,添加這個屬性只會阻止咱們訪問原型對象中的屬性,而不會修改那個屬性。
使用 hasOwnProperty()
方法能夠檢測一個屬性是存在於實例中,仍是存在於原型中。
這個方法(它繼承於 Object)只在給定屬性存在於對象實例中,纔會返回 true。
jsfunction Person() {} Person.prototype.name = "PaddingMe"; Person.prototype.age = 29; Person.prototype.job = "Front-end Engineer"; Person.prototype.sayName = function () { alert(this.name); }; var person1 = new Person(); var person2 = new Person(); alert(person1.hasOwnProperty("name"));//false person2.name = "hhb"; alert(person2.hasOwnProperty("name"));//true delete person2.name; alert(person2.hasOwnProperty("name"));//false
原型與 in 操做符
在單獨使用時,in 操做符會在經過對象可以訪問給定屬性時返回 true,不管該屬性是在實例中仍是原型中。與 hasOwnProperty()
一塊兒使用能夠肯定該屬性究竟是在對象中,仍是存在於原型中。
jsfunction hasPrototypeProperty(){ return !object.hasOwnProperty(name) && (name in object); }
在使用 for-in 循環時, 返回的是全部可以經過對象訪問的、可枚舉(enumerated)屬性,其中既包括存在於實例中的屬性,也包括存在於原型中的屬性。屏蔽了原型中不可枚舉屬性的實例屬性也會返回,由於根據規定,全部開發人員定義的屬性都是可枚舉的——只有在 IE8 以及更早版本中例外。
要去的對象上全部可枚舉的實例屬性,可以使用 ECMAScript 5 中的 Object.keys() 方法。此方法要接受一個對象做爲參數,返回一個包含全部可枚舉屬性的字符串組。
jsfunction Person() {} Person.prototype.name = "PaddingMe"; Person.prototype.age = 29; Person.prototype.job = "Front-end Engineer"; Person.prototype.sayName = function () { alert(this.name); }; var keys = Object.keys(Person.prototype); alert(keys);// "name,age,job,sayName" var p1 = new Person(); p1.name = "hhb"; p1.age = 25; var p1keys = Object.keys(p1); alert(p1keys);// "name,age"
想要獲得全部的實例屬性,不管它是否可枚舉,可使用 Object.getOwnPropertyNames()
方法。
jsvar keys = Object.getOwnPropertyNames(Person.prototype); alert(keys);// "constructor,name,age,job,sayName"
更簡單的原型方法
用對象自變量來重寫真哥哥原型對象。
jsfunction Person(){ } Person.prototype = { name : "PaddingMe", age : 25, job : "F2ER", sayName : function() { alert(this.name); } };
注意 constructor 屬性再也不指向 Person,而是指向了 Object 構造函數。
jsfunction Person(){ } Person.prototype = { constructor: Person, name : "PaddingMe", age : 25, job : "F2ER", sayName : function() { alert(this.name); } };
原型的動態性
對原型對象所在的任何修改都可以當即從實例上反映出來—— 即便是先建立了實例後修改原型也是同樣的。但如果重寫整個原型對象,狀況就不同了。
調用構造函數時會爲實例添加一個指向最初原型的 [[prototype]] 的指針,而把原型修改成另一個對象就等於切斷了構造函數於最初原型之間的聯繫。實例中的指針只指向原型,而不指向構造函數。
jsfunction Person() { } var friend = new Person(); Person.prototype = { constuctor : Person, name : "PaddingMe", age : 25, sayName : function(){ alert(this.name); } }; friend.sayName(); //error
原生對象的原型
jsString.prototype.startsWith = function (text) { return this.indexOf(text) == 0; } var msg = "Hello world!"; alert(msg.startsWith("Hello")); //true
原型對象的問題
全部實例在默認狀況下都將取得相同的屬性值。原型模式的最大問題是由其共享的本性所致使。
jsfuntion Person(){ } Person.prototype = { constuctor: Person, name: "PaddingMe", age: 26, job: "F2ER", friends: ['hw','wjj'], sayName: function() { alert(this.name); } }; var person1 = new Person(); var person2 = new Person(); Person1.friends.push("ex"); alert(person1.friends);//"hw,wjj,ex" alert(person2.friends);//"hw,wjj,ex" alert(person1.friends == person2.friends);//true
構造函數模式用於定義實例屬性,而原型模式用於定義方法和共享的屬性。
jsfunction Person(name,age,job) { this.name = name; this.age = age; this.job = job; this.friends = ["hw","wjj"]; } Person.prototype = { constructor: Person, sayName: function() { alert(this.name); } } var person1 = new Person("winter",59,"Full-Stack Enginner"); var person2 = new Person("PaddingMe",25,"F2ER"); person1.friends.push("ex"); alert(person1.friends); // "hw,wjj,ex" alert(person2.friends); // "hw,wjj" alert(person1.friends == person2.friends); //false alert(person1.sayName == person2.sayName); //true
jsfunction Person(name,age,job) { this.name = name; this.age = age; this.job = job; if(typeof this.sayName != "funtion") { Person.prototype.sayName = function(){ alert(this.name); }; } } var friend = new Person("PaddingMe",25,"F2ER"); friend.sayName();
寄生(parasitic)構造函數模式的基本思想是:建立一個函數,該函數的做用僅僅是封裝建立對象的代碼,而後再返回新建立的對象;但從表面上看,有很像典型的構造函數。
jsfunction Person(name,age,job) { var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function() { alert(this.name); } return o; } var friend = new Person("PaddingMe",25,"F2ER"); friend.sayName;// "PaddingMe"
jsfunction SpecialArray() { var values = new Array(); values.push.apply(values,arguments); values.toPipedString = function() { return this.join("|"); } } var colors = new SpecialArray("red","blue","green"); alert(colors.toPipedString());//"red|blue|green"
關於寄生構造函數模式須要說明的是:
返回的對象與構造函數或者與構造函數的原型屬性之間沒有關係,亦即構造函數返回的都喜好那個與在構造函數外部創建的對象沒什麼不一樣。因此不能依賴 instanceof 操做符來肯定對象模型。
所謂穩妥對象是指沒有公共屬性,並且其方法也引用 this 的對象。穩妥對象最適合在一些安全的環境中(這些環境中會禁止使用 this 和 new),或者在防止數據被其餘應用程序(如 Mashup 程序)改動時使用。
穩妥構造函數模式與寄生構造函數模式不同的是:
- 新建立對象的實例方法不引用 this;
- 不使用 new 操做符調用構造函數。
jsfunction Person(name,age,job) { var o = new Object(); o.sayName = function (){ alert(name); }; return o; }