1. 原型模式1.1 我們建立的每個函數都有一個prototype屬性,這個屬性是一個指標,指向一個對象,而這個對象的用途是包含可以由特定類型的所有執行個體共用的屬性和方法。簡單的解釋上面的話的意思就是:首先,我們要知道,物件導向的語言中類的存在,而javascript也是一門物件導向的語言(這句話說的可能有一些毛病,但是不影響),在javascript中定義一個類函數的時候,就預設建立了一個prototype屬性,在這個prototype屬性裡的所有的屬性和方法將被這個類的所有執行個體共用。看代碼:[javascript] //建立一個Person類 function Person() { } //Person類prototype裡包含了name、age屬性和sayName()方法 Person.prototype.name = "defaultName"; Person.prototype.age = 29; Person.prototype.sayName = function() { return this.name; }; var person = new Person(); var person3 = new Person(); TestCase("test extends",{ "test person.sayName() should be equals person3.sayName()" : function() { assertEquals(person.sayName(),person3.sayName()); } }); 1.2 在預設的情況下,所有的原型對象都會自動獲得一個constructor(建構函式)屬性,這個屬性包含一個指向prototype屬性所在函數的指標。Person.prototype.constructor 指向 Person當為對象執行個體添加一個屬性時,這個屬性就會 屏蔽 原型對象中儲存的同名屬性(它只會阻止我們訪問原型中的那個屬性,並不會修改那個屬性),不過我們可以使用delete操作符刪除執行個體屬性,讓我們可以繼續訪問原型中的屬性,看代碼:[javascript] <span style="font-size:14px;">//建立一個Person類 function Person() { } //Person類prototype裡包含了name、age屬性和sayName()方法 Person.prototype.name = "defaultName"; Person.prototype.age = 29; Person.prototype.sayName = function() { alert(this.name); }; var person = new Person(); person.sayName(); // defaultName var person2 = new Person(); person2.name = "person2"; person2.sayName(); //person2 //刪除執行個體中的name屬性 delete person2.name; person2.sayName(); //defaultName</span> 1.3 使用hasOwnProperty()方法可以檢測一個屬性是存在於執行個體中還是存在於原型中,只有在給定的屬性是存在於對象的執行個體中才會返回true[javascript] function Person() { } //Person類prototype裡包含了name、age屬性和sayName()方法 Person.prototype.name = "defaultName"; Person.prototype.age = 29; Person.prototype.sayName = function() { return this.name; }; var person = new Person(); var person2 = new Person(); person2.name = "person2"; TestCase("test extends",{ "test person should not be hasOwnProperty name" : function() { assertEquals(false,person.hasOwnProperty("name")); }, "test person2 should not be hasOwnProperty name" : function() { assertEquals(true,person2.hasOwnProperty("name")); }, }); 1.4 使用 in 操作符確定一個屬性是原型中的屬in操作符只要通過對象能夠訪問到的屬性就返回true,(hasOwnProperty()只在屬性存在於執行個體中才返回true),因此,只要in操作符返回true,hasOwnProperty()返回false,就證明該屬性是原型中的屬性,看代碼:[javascript] <span style="font-size:14px;"><span style="font-size:12px;">//建立一個Person類 function Person() { } //Person類prototype裡包含了name、age屬性和sayName()方法 Person.prototype.name = "defaultName"; Person.prototype.age = 29; Person.prototype.sayName = function() { alert(this.name); }; var person = new Person(); var person2 = new Person(); person2.name = "person2"; person2.color = "blue"; TestCase("test extends",{ "test Person person.hasOwnProperty(name)" : function() { assertEquals(false,person.hasOwnProperty("name")); //name是原型中的屬性,執行個體中不存在 }, "test Person name in person" : function() { assertEquals(true,"name" in person); }, "test Person person2.hasOwnProperty(name)" : function() { assertEquals(true,person2.hasOwnProperty("name"));//name是執行個體對象的屬性 }, "test Person Person2 name in person2" : function() { assertEquals(true,"name" in person2); }, "test Person person2.hasOwnProperty(color)" : function() { assertEquals(true,person2.hasOwnProperty("color")); //color是執行個體對象的屬性 }, "test Person color property in Person2" : function() { assertEquals(true,"color" in person2); //color是執行個體對象的屬性,in也同樣可以訪問到 } });</span> </span> 1.5枚舉出執行個體的全部屬性---- Object.keys() [javascript] function Person() { } //Person類prototype裡包含了name、age屬性和sayName()方法 Person.prototype.name = "defaultName"; Person.prototype.age = 29; Person.prototype.sayName = function() { return this.name; }; var person = new Person(); var person2 = new Person(); person2.name = "person2"; var keys = Object.keys(Person.prototype); var keys2 = Object.keys(person2); TestCase("test extends",{ "test keys should be an array" : function() { assertArray("keys is not an array",keys); }, "test keys[0] should be equals name" : function() { assertEquals("name",keys[0]); }, "test keys[1] should be equals age" : function() { assertEquals("age",keys[1]); }, "test keys[2] should be equals age" : function() { assertEquals("sayName",keys[2]); }, "test keys2 should be an array" : function() { assertArray("keys is not an array",keys2); }, "test keys2[0] should be equals age" : function() { assertEquals("name",keys2[0]); } }); 1.6 更簡單的原型文法-----字面量建立對象[javascript] function Person() { } Person.prototype = { name : "defaultName", age : 20, sayName : function() { return this.name; } }; 在之前建立函數的時候,我們知道,每建立一個函數,就會同時建立它的prototype對象,這個對象也會自動獲得constructor。而我們在這裡使用的這種文法(字面量),本質上完全重寫了預設的prototype對象,因此constructor對象也就變成了新對象的constructor的屬性(指向Object建構函式),不再指向Person函數。[javascript] function Person() { } Person.prototype = { name : "defaultName", age : 20, sayName : function() { return this.name; } }; var con = new Person(); TestCase("test extends",{ "test con should be instance of Object" : function() { assertInstanceOf("con shoud be instance of Obejct", Object, con); }, "test con should be instance of Person" : function() { assertInstanceOf("con shoud be instance of Person", Person, con); }, "test con constructor should be equals Person" : function() { assertNotEquals(Person,con.constructor); //不再等於Person }, "test con constructor should not be equals Object" : function() { assertEquals(Object,con.constructor); }, "test con constructor should be same as Person" : function() { assertNotSame(Person,con.constructor); }, "test con constructor should not be same as Object" : function() { assertSame(Object,con.constructor); } }); 1.7如何保留1.6中的constructor,在constructor的值比較重要的時候,需要我們來儲存這個constructor的值,這個時候,我們可以在Person.prototype中重寫constructor屬性,並設定其值為Person。 -------但是注意:原生的constructor屬性是不可以枚舉的,如果這樣設定了之後,constructor將變成可以枚舉的。[javascript] function Person() { } Person.prototype = { constructor : Person, name : "defaultName", age : 20, sayName : function() { return this.name; } }; var con = new Person(); TestCase("test extends",{ "test con should be instance of Object" : function() { assertInstanceOf("con shoud be instance of Obejct", Object, con); }, "test con should be instance of Person" : function() { assertInstanceOf("con shoud be instance of Person", Person, con); }, "test con constructor should be equals Person" : function() { assertEquals(Person,con.constructor); //完全重寫了constructor,所以不再等於Object,但是還是Object的執行個體哦 }, "test con constructor should not be equals Object" : function() { assertNotEquals(Object,con.constructor); }, "test con constructor should be same as Person" : function() { assertSame(Person,con.constructor); }, "test con constructor should not be same as Object" : function() { assertNotSame(Object,con.constructor); } }); 補充:如果你的瀏覽器是相容ECMAScript5,就可以使用defineProperty[javascript] //重設建構函式 Object.defineProperty(Person.prototype,"constructor",{ enumerable:false, value:Persons }); 1.8原型對象的問題原型中的屬性是被所有執行個體所共用的,當然,這種共用對於函數非常合適,可是對於一些參考型別的屬性來說,問題就出現了,因為有些時候我們是不想我們的參考型別的屬性被共用:[javascript] function Person() { } Person.prototype = { name : "defaultName", age : 20, friends : ["fengfeng","tongtong"], sayName : function() { return this.name; } }; var person = new Person(); person.friends.push("ty"); var person2 = new Person(); TestCase("test property",{ "test person friends.length should be 3 " : function() { assertEquals(person.friends.length,3); }, "test person friends[2] should be ty" : function() { assertEquals("ty",person.friends[2]); }, "test person2 friends.length should be became 3 " : function() { assertEquals(person2.friends.length,3); //person2的長度也變成了3 }, "test person2 friends should be equals person friends " : function() { assertEquals(person.friends,person2.friends); //person2的friends屬性也發生了變化 } }); 1.9 如何解決參考型別的屬性不被共用-----組合使用建構函式模式和原型模式建立自訂類型的最常見方式,就是組合使用建構函式模式和原型模式。建構函式模式用於定義執行個體屬性,而原型模式用於定義方法和共用屬性。[javascript] function Person(name,age) { this.name = name; this.age = age; this.friends = ['tong','feng']; } Person.prototype = { constructor : Person, sayName : function() { return this.name; } }; var person = new Person("tongtong",25); var person2 = new Person("fengfeng",26); person.friends.push("ty"); TestCase("test property",{ "test person friends.length should be 3 " : function() { assertEquals(person.friends.length,3); }, "test person friends[2] should be ty" : function() { assertEquals("ty",person.friends[2]); }, "test person2 friends.length should be 2 " : function() { assertEquals(person2.friends.length,2); //person2的長度還是2 }, "test person2 friends should not be equals person friends " : function() { assertNotEquals(person.friends,person2.friends); //person2的friends屬性沒有發生變化 }, "test person2 sayName should be equals person sayName" : function() { assertEquals(person.sayName,person2.sayName); //person.sayName===person2.sayName } }); 1.10 繼1.9擴充 ------ 動態原型模式[javascript] function Person(name,age) { this.name = name; this.age = age; this.friends = ['tong','feng']; //method if (typeof this.sayName != "function") { Person.prototype.sayName = function() { return this.name; }; } } 只有在sayName()方法不存在的時候,才會將它添加到原型中,這段代碼只會在初次調用建構函式的時候執行。這裡對原型的修改,能夠立即在所有執行個體中得到反映。使用動態原型模式時,不能使用對象字面量重寫原型,如果在已經建立了執行個體的情況下重寫原型,那麼就會切斷現有執行個體和新原型之間的聯絡。1.11穩妥建構函式模式所謂穩妥對象,指的是沒有公用屬性,而且其方法也不引用this的對象,穩妥對象最適合使用在一些安全環境中(這些環境會禁止使用this和new),或者在防止資料被其他應用程式改動時使用。穩妥建構函式的2個特點:新建立對象的執行個體方法不引用this, 不使用new操作符調用建構函式使用穩妥對建構函式模式建立的對象與建構函式之間沒什麼關係,因此instanceof操作符對這種對象沒有意義。[javascript] function Person(name,age) { var o = new Object(); o.sayName = function() { return name; }; return o; }; var person = Person("tongtong","25"); TestCase("test property", { "test person sayName should be tongtong " : function() { assertEquals(person.sayName(),"tongtong"); }, "test person is not instanceof Person " : function() { assertNotInstanceOf(Person,person); } });