TDD測試驅動的javascript開發(2) ---- javascript麵向對象 ~~ 深入學習javascript中prototype
1. 原型模式
1.1 我們創建的每個函數都有一個prototype屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含可以由特定類型的所有實例共享的屬性和方法。
簡單的解釋上麵的話的意思就是:首先,我們要知道,麵向對象的語言中類的存在,而javascript也是一門麵向對象的語言(這句話說的可能有一些毛病,但是不影響),在javascript中定義一個類函數的時候,就默認創建了一個prototype屬性,在這個prototype屬性裏的所有的屬性和方法將被這個類的所有實例共享。看代碼:
//創建一個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操作符刪除實例屬性,讓我們可以繼續訪問原型中的屬性,看代碼:
<span >//創建一個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
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,就證明該屬性是原型中的屬性,看代碼:
<span ><span >//創建一個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()
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 更簡單的原型語法-----字麵量創建對象
function Person() { } Person.prototype = { name : "defaultName", age : 20, sayName : function() { return this.name; } };
在之前創建函數的時候,我們知道,每創建一個函數,就會同時創建它的prototype對象,這個對象也會自動獲得constructor。而我們在這裏使用的這種語法(字麵量),本質上完全重寫了默認的prototype對象,因此constructor對象也就變成了新對象的constructor的屬性(指向Object構造函數),不再指向Person函數。
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將變成可以枚舉的。
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
//重設構造函數 Object.defineProperty(Person.prototype,"constructor",{ enumerable:false, value:Persons });
1.8原型對象的問題
原型中的屬性是被所有實例所共享的,當然,這種共享對於函數非常合適,可是對於一些引用類型的屬性來說,問題就出現了,因為有些時候我們是不想我們的引用類型的屬性被共享:
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屬性也發生了變化 } });
2.0 javascript中的對象
2.1 原型模式創建對象: 參見1.1 - 1.8
2.2 構造函數模式創建對象
function Person(name,age) { this.name = name; this.age = age; this.sayName() { return this.name; }; }; var person1 = new Person("tong",24); var person2 = new Person("feng",24);
2.3組合使用構造函數模式和原型模式 ------ 解決引用類型的屬性不被共享
創建自定義類型的最常見方式,就是組合使用構造函數模式和原型模式。構造函數模式用於定義實例屬性,而原型模式用於定義方法和共享屬性。
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 } });
2.4 動態原型模式創建對象
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()方法不存在的時候,才會將它添加到原型中,這段代碼隻會在初次調用構造函數的時候執行。這裏對原型的修改,能夠立即在所有實例中得到反映。
使用動態原型模式時,不能使用對象字麵量重寫原型,如果在已經創建了實例的情況下重寫原型,那麼就會切斷現有實例和新原型之間的聯係。
2.5 穩妥構造函數模式創建對象
所謂穩妥對象,指的是沒有公共屬性,而且其方法也不引用this的對象,穩妥對象最適合使用在一些安全環境中(這些環境會禁止使用this和new),或者在防止數據被其他應用程序改動時使用。
穩妥構造函數的2個特點:
新創建對象的實例方法不引用this, 不使用new操作符調用構造函數
使用穩妥對構造函數模式創建的對象與構造函數之間沒什麼關係,因此instanceof操作符對這種對象沒有意義。
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); } });
關於安全模式的2個平台
https://www.adsafe.org/
https://code.google.com/p/google-caja/
最後更新:2017-04-03 15:21:43