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高級程序設計》 http://www.jb51.net/article/63977.htm http://www.iteye.com/topic/599108 http://www.cnblogs.com/sharpxiajun/p/4148932.html http://stackoverflow.com/questions/9001830/the-reason-to-use-js-call-method