javascirpt實現繼承
前言
在面試的過程中,被問到最多的問題的javascript的繼承,我之前也有了解過一些,但是總是理解得不夠透徹,在實際的應用中沒有很好的利用。這一次,我拿著《javascirtpt高級程序設計》這本書,將面向對象設計講的繼承反復的看了好幾遍,跟著書本代碼來敲,也按照自己的理解去實現繼承,旨在能夠充分理解熟練掌握js繼承的思想。下面是我的讀書筆記,記下來供以後翻看復習。
原型是什麽?
每一個對象實例內部都一個指針,指向一個普通的對象(原型)。js中一切都是對象,函數也是對象,所以函數也有一個原型指針。
對象實例與它的構造器函數擁有同一個原型,這個原型指向的是構造器的父類的一個實例。
原型鏈
每一個對象都有原型,而對象的原型也是一個普通的對象,那麽就可以形成一個鏈,例如String對象的原型是Object類的一個實例,而object對象的原型是一個空對象,空對象的原型是null.
當我們訪問對象的屬性時,會從對象自身找,如果自身沒有,就會沿著原型鏈往上找,直到找到為止。如果最後都沒有找到,就會返回undefined.通過修改原型的指向,對象可以獲得相應原型上的屬性,js就是這樣實現了繼承。
繼承
原型繼承
原型繼承實際就是利用原型鏈來繼承屬性和方法,通過重寫原型對象,以一個新的實例代替原型對象,這樣就是繼承到該新實例的屬性和方法。
function SuperType() { this.property = true; } superType.prototype.getSuperValue = function() { return this.property; } function SubType() { this.subProperty = false; } SubType.prototype = new SuperType(); // 重寫SubType的原型對象,讓它指向SuperType實例 SubType.prototype.getSubValue = function() { return this.subProperty; } var instance = new SubType(); instance.getSuperValue(); // true instance.getSubValue(); // false
使用原型方法可以很好的繼承到父類型的屬性和方法,只要通過更改子類型的原型對象為父類性的實例,這樣子就形成了一個原型鏈,但返回子類型的實例的某一個方法的時候,首先在實例中查找該方法,沒有就會往實例的原型對象查找,原型對象本來就是一個實例,找不到集繼續找該原型對象的原型對象,直到原型鏈的最末端為止。
我們可以通過intanceof 和 isPrototypeOf來確認實例和原型的關系
alert(instance intanceof Object) // true alert(instance instanceof SubType) // true alert(instance instanceof SuperType) // true alert(Object.prototype.isPrototypeOf(instance)) // true alert(SubTYpe.prototype.isPrototypeOf(instance)) // true alert(SuperType.prototype.isPrototypeOf(instacne)) // true
要註意的地方:
- 要謹慎的定義方法
子類型有時候會覆蓋父類型中的某個方法,或者是添加父類型中沒有的方法,但是無論怎麽樣,都是要放在替換原型的語句之後 - 通過原型鏈實現繼承的時候,不能使用對象字面量創建原型和方法。否則會重寫原型鏈
不過原型繼承也存在一些問題:
- 包含引用類型的原型被所有實例共享的問題,
如果包含引用類型的原型,會導致該引用類型會被所有的實例共享,這樣就會出現一個操作一個實例的引用類型會影響到另外一個實例的引用類型,這是我們不願意看到的。 所以我們平時在定義的屬性的時候都定義在構造函數中,定義方法就在原型對象中。 - 原型繼承中子類型不能夠向超類型傳遞參數。
所以我們在平常的使用中很少會單獨使用原型繼承。
構造函數繼承
構造函數繼承是通過在子類型中執行父類型的函數,讓父類型的作用域子類型自己本身,這樣就可以訪問到父類型的屬性和方法。
function Super(name) {
this.name = name
this.color = [‘red‘,‘green‘,‘blue‘];
}
function Sub(name,age) {
Super.call(this, name);
this.age = age
}
var instance = new Sub("lili", 12);
instance.name // lili
instance.color.push(‘white‘);
alert(instance.color) // red,green,blue,white
var instance new Sub(‘zhangsan‘,18);
alert(instance.name) // zhangsan
alert(intance.color) // red,green,blue
從上面的例子可以看到,我們利用構造函數繼承,在子類型中調用Super.call(this,name)可以繼承到父類型的name,color,並且可以在子類型中傳遞參數給父類型,同時我們在操作各自的實例屬性或者引用類型的時候,並沒有影響到另外的實例。
但是,如果所以的方法都要在子類型定義,這樣就沒有復用的說法了,而且,我們在父類型的原型對象中定義的方法,對子類型是不可見的,這樣沒有達到真正的繼承,所以在平時中單獨使用構造函數繼承也是很少的。
組合繼承(原型時繼承)
上面我們我們談到了兩種繼承方法都有自己的優點和缺點,無法達到真正繼承的效果,我們可以結合兩者的優點,相互禰補不足,發揮兩者之長。
中心思想:
- 使用原型鏈實現對原型屬性和方法的繼承
- 使用構造函數實現對實例屬性的繼承
```
function Super(name) {
this.name = name;
}
Super.prototype.sayName = function() {
alert(this.name);
}
function Sub(name,age) {
Super.call(this,name);
this.age = age;
}
Sub.prototype = new Super();
Sub.prototype.constructor = Sub;
Sub.prototype.sayName = function() {
alert(this.name + "and I‘m " + this.age)
}
var instance = new Sub("Lucy", 35);
instance.sayName();
```
總結
javascript主要是通過原型鏈來實現繼承,原型鏈的構建是通過將一個類型的實例對象賦值給另外一個構造函數的原型實現的。這樣,子類型就可以訪問超類型的所有屬性和方法。
原型鏈繼承的問題是對象實例共享所有的實例和方法,並且不能在子類型中傳遞參數給超類型,不適合單獨使用。解決這個問題的技術就是借助構造函數,即在子類型構造函數內部調用超類型的構造函數,這樣子可以做到每一個實例都有自己的屬性,同時還保證只用構造函數模式定義類型。使用最多的是組合繼承,這樣方法是使用構造函數來定義實例屬性,使用原型鏈來是實現原型的屬性和方法。
同時,還有以下供選擇的繼承方式
- 原型式繼承
- 寄生式繼承
- 寄生式組合繼承
javascirpt實現繼承