JavaScript中Java語言不一樣,它沒有類這個說法,更沒有子類父類一說,所以它所謂的繼承機制,也是和其他語言完全不同的。
創建對象三種方式
1.最簡單的方式,創建一個object對象,然後為它添加屬性和方法
var person=new object();
person.name="zhangsan";
person.age=20;
person.sayName=function(){alert(this.name);};
2.工廠方式
本質上就是對上述最簡單的方式的包裝。
function createPerson(name,age)
{
var person=new object();
person.name=name;
person.age=age;
person.sayName=function(){alert(this.name);};
return person;
}
缺點是不知道對象的類型。
3 構造函數模式
即定義一個函數,該函數作為類似類中的構造函數,用於生成對象
function Person(name,age)
{
this.name=name;
this.age=age;
this.sayName=function(){alert(this.name);};
}
通過new方式創建對象,var person1=new Person("zhangsan",20);
該方式經歷4個步驟
1,創建一個新對象
2,函數的作用域付給力新對象,this就表示了新的對象
3,執行函數中的代碼,為新對象設置屬性
4,返回新對象。
構造函數也是普通函數,任何函數只要用new操作符調用,都可以成為構造函數。
Person作為普通函數調用的話,this就是window對象。
Person("zhangsan",20);
window.sayName();
缺點是太浪費資源,每個實例中的屬性都是不同的,特別是方法不能復用:
var person1=new Person("zhangsan",20);
var person2=new Person("zhangsan",20);
person1.sayName==person2.sayName// false
4,原型模式
每個函數都有一個prototype屬性,定義在其中的屬性,被各個實例對象所共享。也就是說,實例對象的屬性和方法,分成兩種,一種是自己定義的,另一種是prototype中共享的。 以Person為例子,按原型模式申明類:
function Person(){}
Person.prototype.name="zhangsan";
Person.prototype.age=20;
Person.prototype.sayName=function(){alert(this.name);};
var person1=new Person();
var person2=new Person();
此時person1這的屬性和person2中的屬性是指向同一個引用。
person1.sayName==person2.sayName// true
理解原型
constructor屬性,函數的prototype屬性有一個constructor屬性,該屬性的值默認情況下指向該prototype屬性所在的函數。比如:
Person.prototype.constructor
為
function Person(){}
看一個圖,更明白點
其中__proto__是內部屬性,javascript腳本中是沒有的,只能通過chrome調試的時候才能看到。所有實例對象的__proto__都指向其構造器的prototype。
person1和person2對象的__proto__內部屬性都指向了Person的原型,和Person自身沒有任何關系。
當person1的sayName方法調用的時候,實際會執行2次查找,現在person1實例上找sayName,找不到,會去person1指向的原型上去找。
上述可知道,如果在person1上也找到sayName方法,那麼原型上的方法就無法被調用到了。
通過方法 hasOwnProperty可以判斷一個方法是在實例中還是在原型中。
再次強調,實例中的指針僅指向原型,而不是指向構造函數。
通過快捷寫法
function Person(){}
Person.prototype.name="zhangsan";
Person.prototype.age=20;
Person.prototype.sayName=function(){alert(this.name);};
這種寫法比較羅嗦
可以用json方式來寫:
function Person(){}
Person.prototype={
name:"zhangsan",
age:20,
sayName:function(){alert(this.name);}
}
這種寫法比較簡潔,但是有一個問題,就是constructor屬性的只不再指向Person了,這種語法等於完全重寫了默認的prototype,因此constructor屬性也被指到Object構造函數了。如果有必要的話,需要把constructor值手動賦值:
function Person(){}
Person.prototype={
name:"zhangsan",
age:20,
sayName:function(){alert(this.name);}
}
原型模式缺點,就是由於其共享性。它的所有屬性是共享的,對於函數來說,求之不得,對於字段屬性,這種共享可能會引起大問題。因此,單獨使用原型模式很少見。
取長補短
同時使用構造函數模式和原型模式,非常簡單,把需要共享的放到原型中,不需要共享的放到構造函數中。
function Person(name,age)
{
this.name=name;
this.age=age;
}
Person.prototype={
sayName:function(){alert(this.name);}
}
這種方式是目前最流行的方式。
還有動態原型模式,寄生構造函數模式,穩妥構造函數某事,不作介紹。
繼承(主要兩種方法,原型鏈和call/apply)
原型鏈,利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。
如果原型對象指向了另一個類型的實例,那麼原型對象指向實例-->指向另一個原型對象-->constructor指向所在的函數。
這個就是所謂的原型鏈。如下圖中SuperType和SubType展現的那樣,SubType繼承了SuperType。
上圖中可以看到,原理存在於SuperType的實例中的所有屬性和方法,都能在SubType中訪問到了。
用代碼表示如下:
function SuperType() {
this.property =true;
}
SuperType.prototype.getSuperValue =function() {
returnthis.property;
}
function SubType() {
this.subproperty =false;
}
//發生繼承行為
SubType.prototype =new SuperType();
SubType.prototype.getSubValue =function() {
returnthis.subproperty;
}
var instance =new SubType();
alert(instance.getSuperValue()); //true
alert(instance instanceofObject); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true
alert(Object.prototype.isPrototypeOf(instance)); //true
alert(SuperType.prototype.isPrototypeOf(instance)); //true
alert(SubType.prototype.isPrototypeOf(instance)); //true
代碼中,重寫了SubType的原型,而不是用原來默認的。由於property是一個實例屬性,getSuperValue是一個原型方法。所以property會出現在SubType Prototype(SuperType的實例)中,而getSuperValue不會出現。instance的constructor現在只想的SuperType。
還有一個要提及的是,Javascript中,所有的函數默認的原型都是Object的實例,SuperType函數的prototype也是指向Object Prototype。 因此通過這樣一個鏈條訪問屬性的時候,還是會通過搜索機制順籐摸瓜的找到對應的屬性。
原型鏈的缺點很明顯,所有的子類的屬性是共享的。這個缺點是致命的,因此實踐中很少單獨使用原型鏈。
call/apply方法這種方法稱為constructor stealing(借用構造函數)。
call方法的作用,官方解釋 call方法:
語法:call([thisObj[,arg1[, arg2[, [,.argN]]]]]) 定義:調用一個對象的一個方法,以另一個對象替換當前對象。
說明: call 方法可以用來代替另一個對象調用一個方法。call 方法可將一個函數的對象上下文從初始的上下文改變為由 thisObj 指定的新對象。 如果沒有提供 thisObj 參數,那麼 Global 對象被用作 thisObj。
我的理解,就是把原有對象中的代碼,放到thisObj中運行,代碼中如果出現this,代表的就是thisObj中。
舉個例子:
function Animal(){
this.name = "Animal";
this.showName = function(){
alert(this.name);
}
}
/**定義一個Cat類*/
function Cat(){
this.name = "Cat";
}
/**創建兩個類對象*/
var animal = new Animal();
var cat = new Cat();
//通過call或apply方法,將原本屬於Animal對象的showName()方法交給當前對象cat來使用了。
//輸入結果為"Cat"
animal.showName.call(cat,",");
//animal.showName.apply(cat,[]);
關於call和apply方法的概念,可以自行網上查閱,其中call和apply的差別在於調用時候的參數。 call調用時,參數為用逗號分隔的一組值,而apply調用的時候,參數為是一個數組,一個記憶方式為,C對應comma,A對應Array。 call的主要運用場景就是在面向對象值的模擬繼承關系。
例子:
function SuperType(){
this.colors = ["red", "blue", "green"];
}
function SubType(){
//繼承了SuperType
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"
代碼中,通過call方法,在SubType中,借用了SuperType中的函數的代碼,以此來為完善自己的屬性。這就導致SubType中的實例都有了自己的colors屬性。 這種方式可以在子類的構造方法調用中輸入參數。
function SuperType(name)
{
this.name= name;
}
function SubType(){//繼承了SuperType,並且傳遞了參數
SuperType.call(this,"zhangsan");
this.age=20;
}
var instance=new SubType();
alert(instance.name) //zhangsan
alert(instance.age) //20
當然,這種方式也存在缺點,方法都在構造函數中定義,並沒有真正復用。
function SuperType(){
this.colors = ["red", "blue", "green"];
this.sayHi=function(){console.log("Hi")};
}
function SubType(){
//繼承了SuperTypeSuperType.call(this);
}
var instance1 = new SubType();
var instance2 = new SubType();
alert(instance1.sayHi==instance2.sayHi); //false
組合繼承原型鏈和call方式都有缺點,但是把兩者取長補短,形成組合繼承。其思想非常簡單,用原型鏈方式,對需要共享的原型屬性和方法實現繼承。再通過call方式借用構造函數來實現無需共享的屬性的繼承。這樣即有了共享的屬性,也有了不共享的屬性。
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
//sayName能夠被共享
SuperType.prototype.sayName = function() {
alert(this.name);
};
function SubType(name, age) {
//繼承屬性
call方式繼承 ,name屬性不會被共享
SuperType.call(this, name);
this.age = age;
}
//繼承方法 原型鏈方式繼承
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
可以看到,對於需要共享的屬性、方法,采用原型鏈的方式繼承,對於不需要的共享的,比如屬性,則用call方法實現繼承。這種方式是javascript中最常用的繼承方式。 原型式繼承 這個方式是json格式的發明人Douglas Crockford所創造。 提出了一個object()函數,可以做到這一點。
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
這個object()函數,借用了一個臨時函數,把傳入的對象作為父對象,作為臨時函數的prototype屬性,並返回這個臨時函數的一個實例作為子對象,從而使得子對象與父對象連在一起。
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
這個例子中,person對象作為父對象,把它傳入到object函數中,會返回一個新的對象,該對象的原型就是person,所以它的原型中就包含一個基本類型值屬性和一個引用類型值屬性。這意味著person.friends 不僅屬於person 所有,而且也會被anotherPerson以及yetAnotherPerson 共享。實際上,這就相當於又創建了person 對象的兩個副本。
參考資料:
JavaScript高級程序設計(第3版)高清完整PDF中文+英文+源碼 http://www.linuxidc.com/Linux/2014-09/107426.htm