JS學習筆記(第六章)(面向物件之建立物件)
一、工廠模式
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 0; } var person1 = createPerson("Nicholas",29,"Software Engineer"); var person2 = createPerson("Greg", 27, "Doctor");
抽象了建立具體物件的過程。工廠模式雖然解決了建立多個相似物件的問題,但卻沒有解決物件識別的問題(即怎樣知道一個物件的型別)
二、建構函式模式
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");
建構函式模式與工廠模式存在以下幾點不同:
(1)沒有顯示地建立物件;
(2)直接將屬性和方法賦給了this物件;
(3)沒有return語句
要建立Person的新例項,必須使用new操作符,以這種方式呼叫建構函式實際上會經歷一下4個步驟:
(1)建立一個新得物件;
(2)將建構函式的作用域賦值給新物件(因此this就指向了這個新物件);
(3)執行建構函式中的程式碼(為這個新物件新增屬性);
(4)返回新物件。
建立自定義的建構函式意味著將來可以將它的例項標識為一種特定的型別;而這正是建構函式模式勝過工廠模式的地方。
使用建構函式的主要問題就是每個方法都要在每個例項上重新建立一遍。
解決辦法:通過吧函式定義轉移到建構函式外部來解決這個問題。
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = sayName; } function sayName() { alert(this.name); }; var person1 = Person("Nicholas",29,"Software Engineer"); var person2 = Person("Greg", 27, "Doctor");
三、原型模式
我們建立的每個函式都有一個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
1、理解原型物件
(1)只要建立了一個新的函式,就會根據一組特定的規則誒該函式建立一個prototype屬性,這個屬性指向函式的原型物件;
(2)在預設情況下,所有原型物件都會自動獲得一個constructor(建構函式)屬性,這個屬性是一個指向prototype屬性所在函式的指標;
(3)當呼叫建構函式建立一個新例項後,該例項內部將包含一個指標(內部屬性),指向建構函式的原型物件。 注意:這個連線存在於例項與建構函式的原型物件之間,而不是存在於例項與建構函式之間。
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();
下圖展示了上述程式碼建立的各個物件之間的關係。
(1)可以通過 isPrototypeOf()
方法來確定物件之間是否存在[[Prototype]]關係,如果[[Prototype]]指向呼叫isPrototypeOf()方法的物件(Person.prototype),那麼這個方法就返回true。
alter(Person.prototype.isprototypeOf(person1));//true
Object.getPrototypeOf()
,在所有支援的實現中。這個方法返回[[Prototype]]的值。
alert(Object.getPrototypeOf(person1)== Person.prototype);//true alert(Object.getPrototypeOf(person1).name);//"Nicholas"
(2)每當程式碼讀取某個物件的某個屬性時,都會執行一次搜尋,目標是具有給定名字的屬性。搜尋首先從物件例項本身開始。如果在例項中找到啦具有給定名字的屬性,則返回屬性的值;如果沒有找到。則繼續搜尋指標指向的原型物件,在原型物件中查詢具有給定名字的屬性。如果在原型中找到了這個屬性,則返回該屬性的值。
(3)雖然可以意通過物件例項訪問儲存在原型中的值,但卻不能夠通過物件例項重寫原型的值。黨委物件例項新增一個屬性時,這個屬性就會遮蔽原型物件中儲存的同名屬性;也就是說,新增這個屬性只會阻止我們訪問原型中的那個屬性,但不會修改那個屬性。
(4)使用delete操作符則可以完全刪除例項屬性,從而使我們能夠重新訪問原型中的屬性。
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"來自例項 alert(person2.name); //"Nicholas"來自原型 delete(person1.name); //完全刪除例項物件 alert(person1.name); //"Nicholas"來自原型
(5)使用 hasOwnProperty()
方法可以檢測一個屬性是存在於例項中,還是存在於原型中。這個方法只在給定屬性存在於物件例項中時,才會返回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.hasOwnPrototype("name")); //false person1.name = "Greg"; alert(person1.name); //"Greg"來自例項 alert(person1.hasOwnPrototype("name")); //true alert(person2.name); //"Nicholas"來自原型 alert(person1.hasOwnPrototype("name")); //false delete(person1.name); //完全刪除例項物件 alert(person1.name); //"Nicholas"來自原型 alert(person1.hasOwnPrototype("name")); //false
2、原型與in操作符
(1)有兩方式使用in操作符:單獨使用和在for-in迴圈使用。在單獨使用時,in操作符會在通過物件能夠訪問給定屬性時返回true,無論屬性存在於例項還是原型中;在使用for-in迴圈時,返回的是所有能夠通過物件訪問的、可列舉的屬性,其中既包括存在於例項中的屬性,也包括存在於原型中的屬性。
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.hasOwnPrototype("name")); //false alert("name" in person1); //true person1.name = "Greg"; alert(person1.name); //"Greg"來自例項 alert(person1.hasOwnPrototype("name")); //true alert("name" in person1); //true alert(person2.name); //"Nicholas"來自原型 alert(person1.hasOwnPrototype("name")); //false alert("name" in person2); //true delete(person1.name); //完全刪除例項物件 alert(person1.name); //"Nicholas"來自原型 alert(person1.hasOwnPrototype("name")); //false alert("name" in person1); //true
(2)同時使用hasOwnProperty()方法和in操作符,就可以確定該屬性到底是存在於物件中,還是存在於原型中。
function hasOwnProperty(object, name) { return !object.hasOwnProperty(name) && (name in object); } function Person() { } Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function() { alert(this.name); }; var person = new Person(); alert(person1.hasOwnPrototype(person,"name")); //true表示屬性存在於原型中 person.name = "Greg"; alert(person1.hasOwnPrototype(person,"name")); //false表示屬性存在於例項中
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.getOwnPropertyName()
方法可以得到所有的例項屬性,無論它是否可列舉.
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.getOwnPropertyName(Person.prototype); alert(keys);//"constructor,name, age, job, sayName"
3、更簡單的原型語法
//原型模式 function Person() { } Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function() { alert(this.name); }; //更簡單的原型語法:用一個包含所有屬性和方法的物件字面量來重寫整個原型物件; 導致的問題:相當於重寫了預設的prototype屬性,constructor屬性不再指向Person function Person() { } Person.prototype = { name : "Nicholas", age : 29, job : "Software Engineer", sayName : function () { alert(this.name); } };
解決辦法:特意將constructor屬性設定回適當的值
function Person() { } Person.prototype = { constructor : Person, //將constructor設定為原來的值 name : "Nicholas", age : 29, job : "Software Engineer", sayName : function () { alert(this.name); } };
但是,以這種方式重設constructor屬性導致它的[[Enumberable]]特性被設定為true。預設情況下,原生的constructor屬性是不可列舉的。因此可以嘗試用Object.defineProperty()重設建構函式。
function Person() { } Person.prototype = { name : "Nicholas", age : 29, job : "Software Engineer", sayName : function () { alert(this.name); } }; //重設建構函式 Object.defineProperty(Person.prototype,"constructor",{ enumerable : false, value : Person });
4、原型的動態性
function Person() { } var friend = new Person(); //重寫整個原型物件 Person.prototype = { name : "Nicholas", age : 29, job : "Software Engineer", sayName : function () { alert(this.name); } }; friend.sayName();//error
重寫物件之前
重寫原型物件之後
重寫原型物件切斷了現有原型與任何之前已經存在的物件例項之間的聯絡;它們引用的仍然是最初的原型。
5、原型物件的問題
原型模式的最大問題是由其共享的本質所導致的,原型中的所有屬性是被很多例項共享的。但是,例項一般都是要有屬於自己的全部屬性的。
function Person() { } Person.prototype = { name : "Nicholas", age : 29, job : "Software Engineer", friends : ["Shebly", "Court"], sayName : function () { alert(this.name); } }; var person1 = new Person(); var person2 = new Person(); person1.friends.push["Van"]; alert (person1.friends); //"Shebly", "Court","Van" alert (person2.friends); //"Shebly", "Court","Van" alert(person1.friends === person2.friends); //true
四、組合使用建構函式模式和原型模式
建立自定義型別的最常見方式,就是組合使用建構函式模式和原型模式。建構函式模式用於定義例項屬性,而原型模式用於定義方法和共享的屬性。
//建構函式 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); //"Shebly", "Court","Van" alert (person2.friends); //"Shebly", "Court" alert(person1.friends === person2.friends); //false alert(person1.sayName === person2.sayName); //true
五、動態原型模式
可以通過檢查某個應該存在的方法是否有效來決定是否需要初始化
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; //方法 if (typeof this.name != "function") { Person.prototype.sayName = function() { alert(this.name); }; } } var friend = new Person("Nicholas", 29, "Software Engineer"); friend.sayName();
注意:如果在已經建立了例項的情況下重寫原型,就會切斷現有例項與新原型之間的聯絡。
六、寄生建構函式模式
P160封裝建立物件的程式碼,然後再返回新建立的物件
function 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("Nicholas", 29, "Software Engineer"); friend.sayName();//"Nicholas"
七、穩妥建構函式模式
P161穩妥物件,指的是沒有公共屬性,而且其方法也不引用this的物件。穩建構函式遵循與寄生建構函式類似的模式,但有兩點不同:(1)新建立物件的例項方法不引用this;(2)不使用new操作符點用建構函式。
function Person(name, age, job) { var o = new Object();//建立要返回的物件 //可以在這裡定義私有變數和函式 //新增函式 o.sayName = function() { alert(name); }; //返回物件 return o; } var friend = new Person("Nicholas", 29, "Software Engineer"); friend.sayName();//"Nicholas"
注意,在以這種模式建立的物件中,除了使用sayName()方法之外,沒有其他辦法訪問name的值。