JavaScript原型和繼承的了解
- 一、_proto_和prototype的關系
- 二、繼承的實作
-
- 2.1 構造函數到底聲明了什麼
- 2.2 如何來實作繼承
-
- 2.2.1 原型鍊繼承
- 2.2.2 構造函數繼承
- 2.2.3 組合式繼承
- 2.3 多繼承
- 2.4 class和extends繼承
-
- 2.4.1 class
- 2.4.2 extends繼承
一、_proto_和prototype的關系
隻有function對象才有prototype
生成function對象的時候,會生成prototype屬性,并将function對象指派給prototype.constructor。
後面的例子都以Base作為父類
//父類
function Base(){
this.x = 0;
this.y = 0;
}
Base.prototype.constructor === Base//true
使用 new function對象,可以生成一個新的對象。new做法是建立一個obj對象o1,并且讓o1的__proto__指向了Base.prototype對象。并且使用call 進行強轉作用環境。進而實作了執行個體的建立。
new的時實際上執行的是:
var o1 = new Object();
o1.[[Prototype]] = Base.prototype;//__proto__指向prototype
Base.call(o1);
是以
new Base().__proto__ === Base.prototype
構造函數生成了一個原型,new可以基于這個生成的原型和構造函數生成執行個體
總結起來的話,prototype是一個在構造函數中,用來共享資料結構的模闆。
__proto__是prototype在對象中的具體表現
二、繼承的實作
2.1 構造函數到底聲明了什麼
一個構造函數其實定義了兩部分資料結構。以Base為例:
- 自身的x,y
- 用來共享的prototype。
2.2 如何來實作繼承
繼承其實就是想辦法繼承這兩部分資料
首先還是以Base為例定義被繼承的父類
//父類
function Base(){
this.x = 0;
this.y = 0;
}
Base.prototype.getAdd = function(){
return this.x+this.y
}
2.2.1 原型鍊繼承
将父類的執行個體直接複制到子類的prototype裡面,依靠原型鍊來通路父類,這樣自身屬性和原型鍊都放到了原型鍊上
//父類
function Base(){
this.x = 0;
this.y = 0;
}
Base.prototype.className = "Base"
function PrototypeChildren(){
}
PrototypeChildren.prototype = new Base()
var pc1 = new PrototypeChildren()
var pc2= new PrototypeChildren()
需要注意的是通過子類可以讀取原型鍊上的屬性,但是無法對原型鍊上的屬性指派
pc1.className = "pc1"
console.log(pc2.className)//Base
console.log(pc1.className)//pc1
直接指派會在子對象上動态生成一個新的屬性,不會修改父類的prototype。
如果要修改原型鍊上的屬性需要修過父類的prototype
PrototypeChildren.prototype.className = "Children"
console.log(pc2.className)//Children
console.log(pc1.className)//pc1
2.2.2 構造函數繼承
使用call方法,把構造函數中的屬性複制到新對象裡面,繼承了構造函數裡面的資料結構,隻繼承了構造函數内聲明的屬性,沒有繼承原型中的屬性
function ConChildren(){
Base.call(this)
}
var cc = new ConChildren()
這樣隻繼承了屬性,但是原型鍊并沒有繼承
我們要把原型鍊修改成為可以用下面的組合法
2.2.3 組合式繼承
通過構造函數和原型鍊相結合的方式實作繼承
function SubTypeChildren(){
Base.call(this)
}
// 空函數F:
function F() {
}
// 把F的原型指向Base.prototype:
F.prototype = Base.prototype;
SubTypeChildren.prototype = new F()
var sc = new SubTypeChildren()
這裡面沒有直接使用,對Base.prototype進行了保護,避免修改SubTypeChildren.prototype時直接修改了Base.prototype放到了,同時将Base.prototype放到了SubTypeChildren的原型鍊上
SubTypeChildren.prototype = Base.prototype
但是這樣SubTypeChildren.prototype.constructor指向了Base
可以手動改回來
SubTypeChildren.prototype.constructor = SubTypeChildren
空函數F寫法很繁瑣,看起來很奇怪,ES5之後可以用Object.create代替。Object.create規範了組合繼承的寫法
function SubTypeChildren(){
Base.call(this)
}
SubTypeChildren.prototype = Object.create(Base.prototype);
SubTypeChildren.prototype.constructor = SubTypeChildren
Object.create(Base.prototype)和 new Base()的差別。Object.create(Base.prototype)生成一個對象隻包含了Base的原型而沒有生成構造函數中的屬性,避免了多次調用構造函數生成重複屬性
2.3 多繼承
可以使用Object.assign混入結合Object.create實作多繼承
function MyClass() {
SuperClass.call(this);
OtherSuperClass.call(this);
}
// 繼承一個類
MyClass.prototype = Object.create(SuperClass.prototype);
// 混合其它
Object.assign(MyClass.prototype, OtherSuperClass.prototype);
// 重新指定constructor
MyClass.prototype.constructor = MyClass;
MyClass.prototype.myMethod = function() {
// do a thing
};
2.4 class和extends繼承
2.4.1 class
新的關鍵字class從ES6開始正式被引入到JavaScript中。class是生成類的文法糖,目的就是讓定義類更簡單。
舊的寫法
function Student(name) {
this.name = name;
}
Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
}
新的class寫法
class Student {
constructor(name) {
this.name = name;
}
hello() {
alert('Hello, ' + this.name + '!');
}
}
2.4.2 extends繼承
現在,原型繼承的中間對象,原型對象的構造函數等等都不需要考慮了,直接通過extends來實作:
class PrimaryStudent extends Student {
constructor(name, grade) {
super(name); // 記得用super調用父類的構造方法!
this.grade = grade;
}
myGrade() {
alert('I am at grade ' + this.grade);
}
}
對比組合繼承和extends繼承
優勢:
- extends實作的繼承語義更清晰
- 生成的對象的原型連結更簡潔易于了解