//1 新建對象 var box = new Object(); box.name = "lee"; box.age = 100; box.run = function(){ return this.name+this.age+"運行中"; } alert(box.run()); //lee100運行中 //缺點:不能重復,如果再新建一個對象還要寫大量重復代碼 //2 工廠模式 function box(name,age){ var obj = new Object(); obj.name = name; obj.age = age; obj.run = function(){ return this.name+this.age+"運行中"; }; return obj; //返回對象 } var box1 = box("lee",100); var box2 = box("dang",200); alert(box1.run()); alert(box2.run()); //缺點:無法搞清實例是哪個對象的實例 alert(typeof box1); //object alert(box1 instanceof Object);//true,無法識別是哪個對象的實例 alert(box1 instanceof box); //false,box是function alert(typeof box); //function //3 構造函數創建對象:解決上述問題,不知道是哪個對象的實例 function Box(name,age){ //構造函數名首字母大寫(非強制,為了區分),不用返回對象 this.name = name; this.age = age; this.run = function(){ return this.name+this.age+"運行中"; }; } var box1 = new Box("lee",100); //構造函數必須通過new來調用 var box2 = new Box("lee",100); alert(box1.run()); alert(typeof box1);//object alert(box1 instanceof Box); //true,證明是Box的實例 //缺點:引用地址的不一致 alert(box1.name == box2.name); //true alert(box1.run() == box2.run()); //true 方法的值一樣,因為傳參一直 alert(box1.run == box2.run); //false 方法其實是一種引用地址, //解決辦法:使用了全局的函數run()來解決了保證引用地址一致,單沒什么必要,了解就行 // 可以使用原型 function Box(name, age) { this.name = name; this.age = age; this.run = run; } function run() { //通過外面調用,保證引用地址一致 return this.name + this.age + '運行中...'; } //原型 //4 構造函數創建原型 //我們創建的每個函數都有一個prototype(原型)屬性,這個屬性是一個對象,它的用途是包含可以由特定類型的所有實例共享的屬性和方法。邏輯上可以這么理解:prototype通過調用構造函數而創建的那個對象的原型對象。使用原型的好處可以讓所有對象實例共享它所包含的屬性和方法。也就是說,不必在構造函數中定義對象信息,而是可以直接將這些信息添加到原型中。 function Box(){} Box.prototype.name = "lee"; Box.prototype.age = 100; Box.prototype.run = function(){ return this.name + this.age + '運行中...'; } var box1 = new Box(); var box2 = new Box(); alert(box1.run == box2.run); //true,方法的引用地址保持一致 alert(Box.prototype.isPrototypeOf(box1));//true,判斷一個對象是否指向了該構造函數的原型對象,可以使用isPrototypeOf()方法來測試。 //在原型模式聲明中,多了兩個屬性,這兩個屬性都是創建對象時自動生成的。__proto__屬性是實例指向原型對象的一個指針,它的作用就是指向構造函數的原型屬性constructor。通過這兩個屬性,就可以訪問到原型里的屬性和方法了。 alert(box1.__proto__); //object,但IE瀏覽器在腳本訪問__proto__會不能識別 alert(box1.constructor); //構造函數的函數體返回 /* 原型模式的執行流程:就近原則 1.先查找構造函數實例里的屬性或方法,如果有,立刻返回; 2.如果構造函數實例里沒有,則去它的原型對象里找,如果有,就返回;*/ var box = new Box(); alert(box.name); //lee box.name = "jack"; alert(box.name); //jack delete box.name; alert(box.name); //lee,可以刪除構造函數里的屬性 //如何判斷屬性是在構造函數的實例里,還是在原型里?可以使用hasOwnProperty()函數來驗證 alert(box.hasOwnProperty("name")); //實例里有true,沒有后返回false //in操作符會在通過對象能夠訪問給定屬性時返回true,無論該屬性存在於實例中還是原型中。 alert("name" in box); //5 為了讓屬性和方法更好的體現封裝的效果,減少不必要的輸入,原型的創建可以使用字面量方式 function Box(){}; Box.prototype={ name:"lee", age:100, run:function(){ return this.name + this.age + '運行中...'; } } //使用構造函數創建原型對象和使用字面量創建對象在使用上基本相同,但還是有一些區別,字面量創建的方式使用constructor屬性不會指向實例,而會指向Object,構造函數創建的方式則相反。 alert(box.constructor == Box); //字面量方式,返回false,否則,true alert(box.constructor == Object); //字面量方式,返回true,否則,false //如果想讓字面量方式的constructor指向實例對象,那么可以這么做: Box.prototype = { constructor : Box, //直接強制指向即可 }; //PS:字面量方式為什么constructor會指向Object?因為Box.prototype={};這種寫法其實就是創建了一個新對象。而每創建一個函數,就會同時創建它prototype,這個對象也會自動獲取constructor屬性。所以,新對象的constructor重寫了Box原來的constructor,因此會指向新對象,那個新對象沒有指定構造函數,那么就默認為Object。 //原型的聲明是有先后順序的,所以,重寫的原型會切斷之前的原型。 function Box() {}; Box.prototype = { //原型被重寫了 constructor : Box, name : 'Lee', age : 100, run : function () { return this.name + this.age + '運行中...'; } }; Box.prototype = { age = 200 }; var box = new Box(); //在這里聲明 alert(box.run()); //box只是最初聲明的原型 //原型對象不僅僅可以在自定義對象的情況下使用,而ECMAScript內置的引用類型都可以使用這種方式,並且內置的引用類型本身也使用了原型。 alert(Array.prototype.sort); //sort就是Array類型的原型方法 alert(String.prototype.substring); //substring就是String類型的原型方法 String.prototype.addstring = function () { //給String類型添加一個方法 return this + ',被添加了!'; //this代表調用的字符串 }; alert('Lee'.addstring()); //使用這個方法 ////PS:盡管給原生的內置引用類型添加方法使用起來特別方便,但我們不推薦使用這種方法。因為它可能會導致命名沖突,不利於代碼維護。 //原型的缺點:不能傳參和共享(共享也是原型的最大優點) //原型中所有屬性是被很多實例共享的,共享對於函數非常合適,對於包含基本值的屬性也還可以。但如果屬性包含引用類型,就存在一定的問題: function Box() {}; Box.prototype = { constructor : Box, name : 'Lee', age : 100, family : ['父親', '母親', '妹妹'], //添加了一個數組屬性 run : function () { return this.name + this.age + this.family; } }; var box1 = new Box(); box1.family.push('哥哥'); //在實例中添加'哥哥' alert(box1.run()); var box2 = new Box(); alert(box2.run()); //共享帶來的麻煩,也有'哥哥'了 //6 構造函數+原型模式 為了解決傳參和共享的問題 function Box(name,age){ this.name = name; this.age = age; this.family = ["baba","mama","meimei"]; }; Box.prototype={ constructor:Box, run:function(){ return this.name+this.age+this.family; } }; var box1 = new Box(); box1.family.push("gege"); alert(box1.family); var box2 = new Box(); alert(box2.family); 這種方法解決了傳參和引用共享的大難題,是比較好的方法 //7 動態原型模式,相比上邊的方法封裝性好一點,要注意一點,不可以再使用字面量的方式重寫原型,因為會切斷實例和新原型之間的聯系。 function Box(name,age){ this.name = name; this.age = age; this.family = ["baba","mama","meimei"]; if(typeof this.run != "function"){ Box.prototype.run = function(){ return this.name+this.age+"運行中"; }; } } var box1 = new Box(); box1.family.push("gege"); alert(box1.family); var box2 = new Box(); alert(box2.family); //8 如果上述都不能滿足條件,可以使用寄生構造函數 //其實就是工廠模式+構造函數模式,比較通用,但不能確定對象關系,所以,在可以使用之前所說的模式時,不建議使用此模式。 function Box(name,age){ var obj = new Object(); obj.name = name; obj.age = age; obj.run = function(){ return this.name+this.age+"運行中"; } return obj; } //在什么情況下使用寄生構造函數比較合適呢?假設要創建一個具有額外方法的引用類型。由於之前說明不建議直接String.prototype.addstring,可以通過寄生構造的方式添加。 function myString(string){ var str = new String(string); str.addstring = function(){ return this+',被添加了!'; }; return str; } var box = new myString("lee"); alert(box.addstring()); //在一些安全的環境中,比如禁止使用this和new,這里的this是構造函數里不使用this,這里的new是在外部實例化構造函數時不使用new。這種創建方式叫做穩妥構造函數。 function Box(name,age){ var obj = new Object(); obj.run = function(){ return name+age+"運行中"; }; return obj; } var box = Box("lee",100); //不使用new alert(box.run()); //穩妥構造函數和寄生相似
