ECMAScript中沒有類的概念,里面對于對象的描述是:“無序?qū)傩缘募稀⑵鋵傩钥梢园局怠ο蠡蛘吆瘮?shù)”
一、理解對象
創(chuàng)建自定義對象的方法:
- 以前:
var person = new Object();
person.name = "McRay";
person.sayName = function() { };
- 現(xiàn)在(對象字面量):
var person = {
name: "McRay";
sayName : function(){}
};
二、創(chuàng)建對象
1、工廠模式:用函數(shù)來封裝以特定接口創(chuàng)建對象的細(xì)節(jié)
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 o;
}
var person1 = createPerson("McRay",23,"Student");
var person2 = createPerson("Greg",27,"Doctor");
雖然解決了創(chuàng)建多個相似對象的問題,但是卻沒有解決對象識別的問題(即怎么知道對象的類型)。
2、構(gòu)造函數(shù)模式:創(chuàng)建自定義的構(gòu)造函數(shù),從而定義自定義對象類型的屬性和方法
function Person(name,age,job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){};
}
var person1 = new Person("McRay",23,"Student");
var person2 = new Person("Greg",27,"Doctor");
與createPerson()
中的不同之處:
- 沒有顯式地創(chuàng)建對象;
- 直接將方法和屬性賦給了this對象;
- 沒有return語句;
創(chuàng)建自定義的構(gòu)造函數(shù)意味著將來可以將它的實例標(biāo)識為一種特定的類型。
上面的代碼中有一個缺點就是每一個構(gòu)造方法都必須在每個實例上重新創(chuàng)建一遍,可以通過把函數(shù)定義轉(zhuǎn)移到構(gòu)造函數(shù)外部來解決這個問題:
function Person(name,age,job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName(){}
var person1 = new Person("McRay",23,"Student");
var person2 = new Person("Greg",27,"Doctor");
3、原型模式
我們創(chuàng)建的每個函數(shù)都有一個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();
var person2 = new Person();
person2.sayName();
alert(person1.sayName == person2.sayName);//true
下面是Person構(gòu)造函數(shù)、Person的原型屬性以及Person現(xiàn)有的兩個實例之間的關(guān)系圖:
1、理解原型對象
- 從圖中可以看出實例與構(gòu)造函數(shù)沒有直接的關(guān)系。
- 每當(dāng)代碼讀取某個對象的某個屬性時,都會執(zhí)行一次搜索,首先從對象實例本身開始,然后一直搜索指針指向的原型對象,直到找到屬性。
- 雖然可以通過對象實例訪問保存在原型中的值,但卻不能通過對象實例重寫原型中的值。如果為實例添加了一個與實例原型中同名的屬性,那么該屬性會覆蓋原型中的那個屬性。
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"
- 可以使用
hasOwnProperty()
方法檢測一個屬性是存在于實例中,還是存在于原型中。這個方法只在給定屬性存在于對象實例中,才會返回true,注意是只存在。
2、原型與in操作符
- 當(dāng)單獨使用in操作符時,會在通過對象能夠訪問給定屬性時返回true,無論該屬性存在于實例中還是原型中。
- 同時使用
hasOwnProperty()
和in操作符就可以確定該屬性到底是存在于對象中,還是存在于原型中,如下所示
function hasPrototypeProperty(object,name){
return !object.hasOwnProperty(name) && (name in object);
}
- 要取得對象上所有可枚舉的實例屬性,可以使用Object.keys()方法。這個方法接收一個對象作為參數(shù),返回一個包含所有可枚舉屬性的字符串?dāng)?shù)組。如果你向得到所有實例屬性,無論是否枚舉,可以使用
Object.getOwnPropertyNames()
方法。
3、更簡單的原型語法
function Person(){}
Person.prototype = {
name : "Nicholas",
age : 27,
job: "Software Engineer",
sayName : function() { }
};
- 需要注意的是原型對象的
constructor
屬性不再指向Person了,變成了新對象的constructor
屬性,如果想指向原來的Person對象,可以如下設(shè)置:
function Person(){}
Person.prototype = {
constructor : Person,
name : "Nicholas",
age : 27,
job: "Software Engineer",
sayName : function() { }
};
但是如果這樣設(shè)置的話,會將constructor
屬性的[[Enumerable]]特性被設(shè)置為true。所以還可以使用
Object.defineProperty()```方法
//重構(gòu)構(gòu)造函數(shù)
Object.defineProperty(Person.prototype,"constructor",{
enumerable : false,
value : Person
});
4、原型的動態(tài)性
由于在原型中查找值得過程是一次搜索,因此我們對原型對象所做的任何修改都能夠立即從實例中反映出來——即使是先創(chuàng)建了實例后修改原型也照樣如此.
var friend = new Person();
Person.prototype.sayHi = function(){
alert("hi");
};
friend.sayHi();//"hi"
原因可以歸結(jié)為實例與原型之間的松散連接關(guān)系。盡管可以隨時為原型添加屬性和方法,但是如果重寫整個原型對象,就會切斷了現(xiàn)有原型與任何之前已經(jīng)存在的對象實例之間的聯(lián)系,因為實例中的指針僅指向原型,而不指向構(gòu)造函數(shù)。
5、原型對象的問題
由于其共享的本性,如果對于包含引用類型值的屬性來說,問題就比較突出了,請看下面的例子:
function Person(){}
Person.prototype = {
constructor : Person,
name : "Nicholas",
age : 27,
job: "Software Engineer",
friends : ["Shelby","Court"],
sayName : function() { }
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("Van");
alert(person1.friends);//"Shelby,Court,Van"
alert(person2.friends);//"Shelby,Court,Van"
alert(person1.friends == person2.friends);//"true"
4、組合使用構(gòu)造函數(shù)模式和原型模式
構(gòu)造函數(shù)模式用于定義實例屬性,而原型模式用于定義方法和共享的屬性,解決了原型模式的問題。
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("McRay",23,"Student");
var person2 = new Person("Greg",27,"Doctor");
person1.friends.push("Van");
alert(person1.friends);//"Shelby,Court,Van"
alert(person2.friends);//"Shelby,Court"
alert(person1.friends == person2.friends);//"false"
5、動態(tài)原型模式
function Person(name,age,job){
//屬性
this.name = name;
this.age = age;
this.job = job;
//方法
if(typeof this.sayName != "function") {
Person.prototype.sayName = function(){}
};
}
var friend = new Person("Nicholas",27,"Software Engineer");
friend.sayName();
6、寄生構(gòu)造函數(shù)模式
基本思想就是創(chuàng)建一個函數(shù),該函數(shù)的作用僅僅是封裝創(chuàng)建對象的代碼,然后再返回新創(chuàng)建的對象。
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",27,"Software Engineer");
friend.sayName();
7、穩(wěn)妥構(gòu)造函數(shù)模式
與寄生構(gòu)造函數(shù)模式的不同是:新創(chuàng)建對象的實例方法不引用this;不適用new操作符調(diào)用構(gòu)造函數(shù)。
function Person(name,age,job){
var o = new Object();
o.sayName = function(){};
return o;
}
var friend = Person("Nicholas",27,"Software Engineer");
friend.sayName();
四、繼承
談到面向?qū)ο螅筒坏貌徽f到繼承這個概念,因為javascript中的函數(shù)是沒有簽名的,所以ECMAScript只支持實現(xiàn)繼承。
1、原型鏈
原型鏈?zhǔn)荍S繼承中的一個重要概念,其基本思想就是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。具體的做法就是讓原型對象等于另一個類型的實例,此時原型對象將包含一個指向另一個原型的指針,相應(yīng)地,另一個原型中也包含著指向另一個構(gòu)造函數(shù)的指針,如此層層遞進(jìn),就構(gòu)成了實例與原型的鏈條,這就是原型鏈的概念。
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function(){
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue());//true
關(guān)系圖如下:
- 別忘記默認(rèn)的原型Object。
- 不管怎樣,給原型添加方法的代碼一定要放在替換原型的語句之后。
- 在通過原型鏈實現(xiàn)繼承時,不能使用對象字面量創(chuàng)建原型方法,因為這樣做就會重寫原型鏈。
- 原型鏈的問題還是包含引用類型值得原型的問題。
借用構(gòu)造函數(shù)
解決原型中包含引用類型值所帶來問題的過程中,可以借用構(gòu)造函數(shù),別忘了函數(shù)只不過是在特定環(huán)境中執(zhí)行代碼的對象,因此通過使用apply()和call()方法可以在新創(chuàng)建對象上執(zhí)行構(gòu)造函數(shù),如下所示:
function SuperType(name){
this.name = name;
this.color = ["red","blue","green"];
}
function SubType(){
//繼承了SuperType
SuperType.call(this,"Nicholas");
}
var instance1 = new SubType();
instance1.color.push("black");
alert(instance1.color);//"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.color);//"red,blue,green"
- 借用構(gòu)造函數(shù)帶來的問題是方法都在構(gòu)造函數(shù)中定義,因此函數(shù)復(fù)用就無從談起。
組合繼承
將原型鏈和借用構(gòu)造函數(shù)的技術(shù)組合一起,使用原型鏈實現(xiàn)對原型屬性和方法的繼承,通過借用構(gòu)造函數(shù)來實現(xiàn)對實例屬性的繼承。
function SuperType(name){
this.name = name;
this.colors = ["red","green","blue"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name,age){
//繼承實例屬性
SuperType.call(this,name);//第二次調(diào)用SuperType();
this.age = age;
}
//繼承原型屬性和方法
SubType.prototype = new SuperType(); //第一次調(diào)用SuperType();
SubType.prototype.constuctor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance1 = new SubType();
instance1.color.push("black");
alert(instance1.color);//"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.color);//"red,blue,green"
組合繼承的問題是多次調(diào)用超類型構(gòu)造函數(shù)而導(dǎo)致的低效率問題,可以與寄生模式組合使用
原型式繼承
function object(o){
function F(){}
F.prototype = o;
return new F();
}
可以使用Object.create()方法規(guī)范化了原型式繼承。接收兩個參數(shù),一個是用作新對象原型的對象和(可選的)一個為新對象定義額外屬性的對象。
寄生式繼承
function createAnother(original){
var clone = object(original);
clone.sayHi = function() {
}
return clone;
}
寄生式組合繼承
背后思路是:不必為了指定子類型的原型而調(diào)用超類型的構(gòu)造函數(shù),我們所需要的無非就是超類型原型的一個副本而已。本質(zhì)上,就是使用寄生式繼承來繼承超類型的原型,然后再將結(jié)果指定給子類型的原型。
function SuperType(name){
this.name = name;
this.colors = ["red","green","blue"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name,age){
//繼承實例屬性
SuperType.call(this,name);//第二次調(diào)用SuperType();
this.age = age;
}
inheritPrototype(SubType,SuperType){
var prorotype = Object(superTYpe.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
//繼承原型屬性和方法
inheritPrototype(SubType,SuperType);
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance1 = new SubType();
instance1.color.push("black");
alert(instance1.color);//"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.color);//"red,blue,green"