天天看點

JavaScript原型和繼承的了解一、_proto_和prototype的關系二、繼承的實作

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可以基于這個生成的原型和構造函數生成執行個體

JavaScript原型和繼承的了解一、_proto_和prototype的關系二、繼承的實作

總結起來的話,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。

JavaScript原型和繼承的了解一、_proto_和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()

這樣隻繼承了屬性,但是原型鍊并沒有繼承

JavaScript原型和繼承的了解一、_proto_和prototype的關系二、繼承的實作

我們要把原型鍊修改成為可以用下面的組合法

JavaScript原型和繼承的了解一、_proto_和prototype的關系二、繼承的實作

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

JavaScript原型和繼承的了解一、_proto_和prototype的關系二、繼承的實作

可以手動改回來

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的原型而沒有生成構造函數中的屬性,避免了多次調用構造函數生成重複屬性

JavaScript原型和繼承的了解一、_proto_和prototype的關系二、繼承的實作

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繼承

JavaScript原型和繼承的了解一、_proto_和prototype的關系二、繼承的實作

優勢:

  • extends實作的繼承語義更清晰
  • 生成的對象的原型連結更簡潔易于了解

繼續閱讀