在前文全面理解Javascript的面向對象(一)中詳細介紹了面向對象的主要知識點,可以幫助大家很細緻的瞭解js面向對象的概念,本文作爲補充,主要從對象的構建和繼承的方式兩方面進行分析。
一、創建對象主要的幾種方式
1 工廠模式
工廠模式抽象了創建具體對象的過程,用函數封裝以特定接口創建對象的細節。
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("Nike",21,"Doctor");
var person2 = createPerson("Frank",21,"Manager");
缺點:沒有解決對象的識別問題,即怎樣知道一個對象的類別。於是,新的模式:構造函數模式出現了。
2 構造函數模式
像Object、Array這樣的原生構造函數,在運行時就會自動創建。此外也可以自定義構造函數,從而定義自定義對象類型的屬性和方法。
function Person(name,age,job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function () {
alert(this.name);
}
}
var person1 = new Person("Nike",21,"Doctor");
var person2 = new Person("Frank",21,"Manager");
使用構造函數方法創建對象經歷了四個過程:
- 創建新對象
- 將構造函數作用域賦給新對象(this指向了新對象)
- 執行構造函數的方法(爲新對象添加屬性)
- 返回新對象
自定義的構造函數可將它的實例標識爲特定的類型,這是勝過工廠模式的地方
alert(person1 instanceof Object); //ture
alert(person1 instanceof Person); //ture
alert(person2 instanceof Object); //ture
alert(person2 instanceof Person); //ture
缺點:每個方法在每個實例上多次創建。這個問題可以通過原型模式解決。
3 原型模式
function Person() {
}
Person.prototype.name = 'frank';
Person.prototype.age = 21;
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
更簡單的原型語法:
function Person() {
}
Person.prototype = {
constructor : Person,
name:'frank',
age:29,
sayName:function () {
alert(this.name);
}
};
4 組合使用構造函數模式和原型模式
構造函數模式用於定義實例屬性,原型模式用於定義方法和共享的屬性。結果,每個實例都會有自己的一份實例屬性的副本,同時又共享着方法的引用,最大限度節省內存。
function Person(name,age,job) {
this.name = name;
this.age = age;
this.job = job;
}
Person.prototype = {
constructor : Person,
sayName : function () {
alert(this.name);
}
}
var person1 = new Person("Nike",21,"Doctor");
var person2 = new Person("Frank",21,"Manager");
二、繼承的實現方式
1 原型鏈
如果對象無法相應某個請求,他會把這個請求委託給他的構造器的原型對象。
將一個原型對象等於另一個實例,這個原型對象包含一個指向另一個原型的指針,相應的,另一個原型也包括一個指向構造函數的指針,這樣層層遞進,構成了實例與原型的鏈條
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperType = function () {
return this.property;
}
function SubTpye() {
this.subproperty = false;
}
SubTpye.prototype = new SuperType();
var ins = new SubTpye();
alert(ins.getSuperType());
缺點:創建子類型的實例時,不能向超類型的構造函數中傳遞參數。
2 借用構造函數
在子類型構造函數中調用超類型構造函數。
function SuperType() {
this.colors = ['red','blue','green'];
}
function SubTpye() {
SuperType.call(this);
}
var ins1 = new SubTpye();
ins1.colors.push("black");
alert(ins1.colors); //'red','blue','green','black'
var ins2 = new SubTpye();
alert(ins2.colors); //'red','blue','green'
與原型鏈模式相比,借用構造函數的最大優勢是子類可以向超類中傳遞參數。
function SuperType(name) {
this. name = name;
}
function SubTpye() {
SuperType.call(this,"frank");
}
var ins1 = new SubTpye();
alert(ins1.name); //'frank'
缺點:方法都在構造函數中定義,因此函數複用就無從談起,超類型的原型中定義的方法,對子類型而言不可見。因此借用構造函數的模式很少單獨使用。
3 組合繼承
組合繼承是將原型鏈和借用構造函數的方法結合到一起。
思路是
- 使用原型鏈實現對原型屬性和方法的繼承;
- 使用借用構造函數的方法實現對實例屬性的繼承。
這樣,通過在原型上定義方法實現函數複用,又能夠保證對每個實例有自己的屬性。
function SuperType(name){
this.name = name;
this.colors = ["red","blue","green"];
}
SuperType.prototype.callName = function(){
alert(this.name);
}
function SubType(name,age){
SuperType.call(this,name);
this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype={
constructor:SubType,
callAge:function(){
alert(this.age);
}
}
var ins1 = new SubType("nike",29);
ins1.colors.push("black");
console.log(ins1.colors);
console.log(ins1.callName());
console.log(ins1.callAge());
組合模式是javascript中最常用的模式。
4 原型繼承
在上一篇博文全面理解Javascript的面向對象(一)中,介紹過,原型繼承的本質是基於原型鏈的委託機制。
function object(o){
function F(){}
F.prototype = o;
return new F();
}
在object()函數內部,先創建一個臨時性的構造函數,然後將傳入的對象作爲這個構造函數的原型,最後返回這個臨時類型的一個新實例。object()對傳入的對象進行了一次淺複製。
var Person = {
name:"nike",
friends:["frank","lucy"];
}
var another = object(Person);
another.gentle = "male";
another.friends.push("mike");
在ES5中新增Object.create()方法,規範了原型繼承。這個方法又兩個傳入的參數,一個是對象,另一個是==可選==的額外定義的屬性。
var another1 = Object.create(Person);
another.gentle = "male";
var another2 = Object.create(Person,{
name:{
value:"Greg"
}
});