創建對象
Js中可以用構造函數模式創建對象,如:
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, "aa");var person2 = new Person("YU", 29, "BB");
這里應注意函數名首字母應大寫,按照約定,構造函數始終都應該以一個大寫字母開頭,而非構造函數則應以一個小寫字母開頭。
原型對象
上述方法中,有一個缺點,就是對象中的sayName方法,每次都會重新new一個對象,因此,也就是說不同對象的sayName方法地址是不同的,然而這個方法都只是執行提示姓名,這種創建兩個相同的function,完成同樣的任務,確實沒有必要。所以這存在了缺陷。使用下面的方法可以避免這種缺陷。
????
function Person() {}Person.prototype.name = "YuKaifei";Person.prototype.age = 25;Person.prototype.job = "SoftWare";Person.prototype.sayName = function () {alert(this.name);}var person1 = new Person();person1.sayName();//YuKaifeivar person2 = new Person();person2.sayName(); //YuKaifei alert(person1.sayName == person2.sayName); //true
?
理解原型對象
函數
Person | |
prototype | 指向所對應的原型對象 |
?
函數的原型對象
Person? Prototype? (person的原型對象) | |
constructor | 指向所對應函數(person)的指針 |
Name | “YuKaiFei” |
Age | 25 |
Job | “Soft Ware” |
sayName | (function) |
?
自定義函數1
Person1 | |
prototype | 指向所對應的原型對象 |
?
自定義函數2
Person2 | |
prototype | 指向所對應的原型對象 |
?
無論什么時候,只要創建了一個新函數,就會為該函數創建一個prototype屬性,這個屬性就指向所對應的原型對象。而默認情況下,原型對象的constructor屬性會指向所對應函數的地址。也就是說這時這兩個對象各有一個屬性,是存放對方的地址的。
當調用一個構造函數創建一個新實例之后,該函數內部也有一個屬性prototype,這個屬性是執行原型對象的地址。也就是說新實例其實與構造函數并沒有直接關系。
需要注意的是,新實例雖然沒有屬性和方法,但是卻可以通過查找對象屬性的方式來調用原型對象中的屬性和方法。
?
當新的實例創建新的屬性之后,如果和原型對象是相同的屬性,那么在下次調用時會調用新實例的屬性,而不是原型對象的屬性。如:
var person1 = new Person();person1.name="aaaa";var person2 = new Person();alert(person1.name);//aaaa alert(person2.name);//YuKaifei
?
in 操作符
有兩種方式可以使用in,一種是單獨in,一種是for-in,需要注意的是,無論該屬性是存在于實例中還是存在于原型中,只要存在,就返回true。
例如 alert(name in person1)? 返回true
for (var prop in person1) {if (prop == "name") {alert("name")}}
?
判斷該屬性是否存在實例中的方式是hasOwnPropery()方法。
例如:person1.hasOwnProperty(“Name”) 返回true
???????? Person2.hasOwnProperty(“Name”) 返回false
?
獲取對象上所有可枚舉的實例屬性:
var keys = Object.keys(Person.prototype);alert(keys); //name,age,job,syName,注意keys是一個數組。
更簡單的原型語法—存在缺陷
Person.prototype = {name: "Nicholas",age: 29,jon: "SowfWare",syaName: function () {alert(this.name);}}
可以使用這種方法更簡單的創建原型,但需要注意的是,這種寫法相當于重寫了原型對象,所有consturctor屬性是新的,即不在指向person。如:
function Person() {}var friend = new Person();Person.prototype = {name: "Nicholas",age: 29,jon: "SowfWare",syaName: function () {alert(this.name);}}friend.sayName(); //error
?
上面代碼會報錯,原因在與重寫了原型對象,指向丟失,也就是切斷了現有原型與之前已經存在的對象之間的聯系。如果避免這種方法可以在聲明原型中指定: constructor:Person
原生對象的原型—String、Array
原型的模式體現在所有原生的引用類型,例如object、array、string等,都在其構造函數的原型上定義了方法,例如在Array.prototype中可以找到sort()方法,在string.prototype可以找到substring()方法。
通過原生對象的原型,不僅可以獲得所有默認方法的引用,也可以隨意的修改原生對象的原型,因此可以隨時添加方法。例如:
為String添加一個名為startsWith()?方法。
String.prototype.startsWith = function (text) {return this.indexOf(text) == 0;}var msg = "Hello world!";alert(msg.startsWith("Hello")); //true
優化原型對象的缺陷
原型對象為了省略函數傳遞初始化這一環節,結果所有的實例在默認情況下都會取得相同的值,這對于值類型屬性共享還可以,但是對于引用類型則會存在問題。如:
?
function Person() {}Person.prototype = {name: "Nicholas",age: 29,jon: "SowfWare",friends:["Yu","Kai"],syaName: function () {alert(this.name);}}var person1 = new Person;person1.name = "yy";var person2 = new Person;
alert(person1.name); //yy alert(person2.name); //Nicholas person1.friends.push("Fei");alert(person1.friends); //YuKaiFei alert(person2.friends); //YuKaiFei
可以看到值類型并沒有什么影響,但因為引用類型的特殊,所有實例的值都將會被改變。
所有最好采用動態原型模式
動態原型模式-聲明原型的推薦方式
function Person(name, age, job) {this.name = name;this.age = age;this.job = job;this.friends = ["Yu", "kai"];if (typeof this.sayName != "function") {this.sayName = function () {alert(this.name);};}}var f1 = new Person("Y", 29, "s");var f2 = new Person("Y", 29, "s");f1.friends.push("Fei");alert(f1.friends); //yu,kai,fei alert(f2.friends);//yu,kai
?