創建對象 工廠模式 function createPerson(name, age, job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(this.nam ...
創建對象
工廠模式
function 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 person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");
person1.sayName(); //"Nicholas"
person2.sayName(); //"Greg"
- 工廠模式雖然解決了創建多個相似對象的問題,但是沒有解決對象識別的問題(即如何知道對象的類型)
構造函數模式
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
};
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
person1.sayName(); //"Nicholas"
person2.sayName(); //"Greg"
alert(person1 instanceof Object); //true
alert(person1 instanceof Person); //true
alert(person2 instanceof Object); //true
alert(person2 instanceof Person); //true
alert(person1.sayName == person2.sayName); //false
構造函數始終都應該以一個大寫字母開頭,要創建新的實例,必須使用new操作符。
每一個實例都有一個constructor(構造函數)屬性,該屬性指向實例的構造函數。
alert(person1.constructor == Person); //true alert(person2.constructor == Person); //true
構造函數與其他函數唯一的不同就是調用方式不同,任何函數只要用new操作符來調用,那它就可以作為構造函數
構造函數的問題:每個方法在每個實例上都要重新創建一遍,不同實例上的同名方法其實是互相不相等的
原型模式
每個函數都有一個prototype(原型)屬性,這個屬性是一個指針,指向一個對象,這個對象的作用是包含可以由特定類型的所有實例共用的屬性和方法。prototype也就是通過調用構造函數而創建的那個對象實例的原型對象。
function Person(){ } Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function(){ alert(this.name); }; var person1 = new Person(); person1.sayName(); //"Nicholas" var person2 = new Person(); person2.sayName(); //"Nicholas" alert(person1.sayName == person2.sayName); //true
新對象的這些屬性和方法是所有實例共用的
理解原型對象
無論什麼時候,只要創建了一個新函數,就會根據一組特定的規則為該函數創建一個prototype屬性,這個屬性指向函數的原型對象。
預設情況下,所有原型對象會自動獲得一個constructor(構造函數)屬性,這個屬性是一個指向prototype屬性所在函數的指針。就上面的例子而言,Person.prototype.constructor指向Person。
創建了自定義構造函數之後,其原型對象只會獲得constructor屬性,至於其他方法,則全是從Object繼承來的,當調用構造函數創建一個新實例之後,該實例的內部將會包含一個指針(內部屬性),指向構造函數的原型對象。這個屬性叫做[[prototype]],也就是__proto__。註意:這個連接存在於實例和構造函數的原型對象之間,和構造函數沒有直接關係。
雖然所有實現中都無法訪問到[[prototype]],但是可以通過isPrototypeOf()方法來確定對象之間是否是否存在這種關係
alert(Person.prototype.isPrototypeOf(person1)); //true alert(Person.prototype.isPrototypeOf(person2)); //true
Object.getPrototypeOf() 取得一個對象的原型
//only works if Object.getPrototypeOf() is available if (Object.getPrototypeOf){ alert(Object.getPrototypeOf(person1) == Person.prototype); //true alert(Object.getPrototypeOf(person1).name); //"Nicholas" }
原型最初只包含constructor屬性,而該屬性也是共用的,因此可以通過對象實例來訪問
雖然可以通過對象實例訪問保存在原型中的值,但卻不能通過對象實例重寫原型中的值。
function Person(){ } Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function(){ alert(this.name); }; var person1 = new Person(); var person2 = new Person(); person1.name = "Greg"; alert(person1.name); //"Greg" from instance alert(person2.name); //"Nicholas" from prototype
通過delete操作符可以完全刪除實例屬性,從而讓我們能重新訪問原型中的屬性
delete person1.name; alert(person1.name); //"Nicholas" - from the prototype
hasOwnProperty() 方法可以檢測一個屬性是存在於實例中,還是原型中,只有給定屬性在對象實例中,才會返回true。
原型與in操作符
in操作符有兩種使用方式,一種是在for-in迴圈中,一種是單獨使用。單獨使用時,in操作符會在通過對象能夠訪問訪問給定屬性時返回true,無論該屬性在實例中還是原型中。
function Person(){ } Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function(){ alert(this.name); }; var person1 = new Person(); var person2 = new Person(); alert(person1.hasOwnProperty("name")); //false alert("name" in person1); //true person1.name = "Greg"; alert(person1.name); //"Greg" from instance alert(person1.hasOwnProperty("name")); //true alert("name" in person1); //true alert(person2.name); //"Nicholas" from prototype alert(person2.hasOwnProperty("name")); //false alert("name" in person2); //true delete person1.name; alert(person1.name); //"Nicholas" - from the prototype alert(person1.hasOwnProperty("name")); //false alert("name" in person1); //true
在for-in迴圈時,返回的是所有能夠通過對象訪問的,可枚舉的屬性。
Object.keys() 接受一個對象作為參數,返回一個包含所有可枚舉屬性的字元串數組。
function Person(){ } Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function(){ alert(this.name); }; var keys = Object.keys(Person.prototype); alert(keys); //"name,age,job,sayName"
Object.getOwnPropertyNames() 獲得所有實例屬性,無論是否可枚舉
var keys = Object.getOwnPropertyNames(Person.prototype); alert(keys); //"constructor,name,age,job,sayName"
更簡單的原型語法
function Person(){
}
Person.prototype = {
name : "Nicholas",
age : 29,
job: "Software Engineer",
sayName : function () {
alert(this.name);
}
};
如果將Person.prototype設置為等於一個以對象字面量形式創建的新對象,那麼constructor將不再指向Person。因為這樣本質上完全重寫了預設的prototype對象,因此constructor屬性也就變成了新對象的constructor屬性(指向Object)構造函數,不再指向Person函數。
var friend = new Person(); alert(friend instanceof Object); //true alert(friend instanceof Person); //true alert(friend.constructor == Person); //false alert(friend.constructor == Object); //true
可以把constructor重新設置回適當的值。
function Person(){ } Person.prototype = { constructor: Person, name : "Nicholas", age : 29, job: "Software Engineer", sayName : function () { alert(this.name); } };
以這種方式重設constructor屬性會導致它的[[Enumerable]]特性被設置為true。預設情況下,原生的constructor屬性是不可枚舉的。
原型的動態性
由於在原型中查找值得過程是一次搜索,因此我們對原型對象做的任何修改都能立即從實例上反映出來————即使是先創建實例後修改原型。
實例和原型之間的聯繫是一個指針,而不是一個副本。
調用構造函數時,會為實例添加一個指向最初原型的[[prototype]]指針。如果重寫整個原型對象,會切斷構造函數與最初原型之間的聯繫。實例中的指針僅指向原型,並非構造函數!
function Person(){ } var friend = new Person(); Person.prototype = { constructor: Person, name : "Nicholas", age : 29, job : "Software Engineer", sayName : function () { alert(this.name); } }; friend.sayName(); //error
上面報錯的原因是重寫原型對象切斷了現有原型和已經存在的對象實例的聯繫。實例引用的仍是原來的原型。
組合使用構造函數和原型
創建自定義類型的最常見模式就是組合使用構造函數和原型,構造函數用於定義實例屬性,原型用於定義方法和共用的屬性。這樣,每個實例都會有自己的一份實例屬性的副本,但同時也共用著對方法的引用,最大限度地節省了記憶體。
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,Court,Van" alert(person2.friends); //"Shelby,Court" alert(person1.friends === person2.friends); //false alert(person1.sayName === person2.sayName); //true