對js原型的思考

思考

說到原型,不得不提到原型鏈,js中無論是對象仍是方法(也是對象)都有個隱藏屬性_proto_,來表示原型鏈的下一個指向,通常對象是指向Object.prototype,方法是指向Function.prototype,構造函數new出來的對象指向想構造函數的prototype

原型鏈的思考

由於原型鏈的存在,當前對象或者方法能夠共用原型鏈上的上級屬性和方法es6

var obj = {};
obj.toString() //[object Object]

obj對象上是沒有toString方法的,由於obj._proto_指向Object.prototype,具體上是調用Object.prototype方法:Object.prototype.toString.apply(obj)app

function Foo() {

}
Foo.toString();//function Foo() {}

方法的原型鏈是Foo->Function.prototype->Object.prototype,因此Foo調用toString方法是引用Function上的toString方法,具體上是調用Function.prototype方法:Function.prototype.toString.apply(obj)函數

有時候咱們爲了繼承父類(其實js並無類這個概念),會經過原型繼承去繼承父類的一些方法,在此以前,先簡單敘述下經過構造函數實例化一個對象的過程,好比如下建立一個obj對象,this

var obj = new Object();
  • 先建立一個空對象{}
  • 空對象{}._proto_指向Object.prototype
  • Object.apply({})
  • 再執行構造方法裏面的代碼

因此當es5繼承方法時,能夠選擇原型繼承,經過修改prototype的值,如:
ES5的狀況:es5

var Father = function(name) {
    this.name = name;
}

Father.prototype.say = function() {
    return "my name is " + this.name;
}

var Child = function(name) {
    this.name = name;
}

Child.prototype = new Father();

var child = new Child('Nico');
child.say();//my name is Nico

可是在上面原型繼承的狀況,咱們還要對Child的構造函數的constructor作一個聲明prototype

Child.prototype.constructor = Child;

由於Child原型是Father實例的一個引用,當想修改Child原型的方法時,會被掛在Father的實例對象上。code

ES6的狀況:對象

class Father {
    constructor(name) {
        this.name = name;
    }
    say() {
        return "my name is " + this.name;
    }
}

class Child extends Father {
    constructor(name) {
        super(name)
    }
}

var child = new Child('Nico');
child.say()//my name is Nico

es6的class類中,Child類的原型構造器不用作額外聲明,由於,es6的class的constructor指向自身繼承

繼承的思考

子類能使用父類的方法

除了上面說起到的原型鏈繼承,還有例如構造函數繼承:ip

function Veticle(name) {
    this.name = name || null

    this.drive = function() {
        console.log(this.name + " drives! --from Veticle ");
    }
}

function Car(name) {
    Veticle.call(this)
    this.name = name;
}

var car = new Car('a car');
car.drive();//a car drives! --from Veticle

這種方法核心就經過改變構造函數的上下文(context),達到子類可以使用父類方法,可是這種方法不能使用父類原型方法。

除此以外,還能夠遍歷父類實例繼承父類方法:

function Veticle(name) {
    this.name = name || null

    this.drive = function() {
        console.log(this.name + "drives! --from Veticle ");
    }
}

Veticle.prototype.getName = function() {
    return this.name;
}

function Car(name) {
    var veticle = new Veticle();
    for (var p in veticle) {
        console.log(p)
        Car.prototype[p] = veticle[p];
    }
    this.name = name;
}

var car = new Car('a car');
car.getName();//a car

這種方法能夠獲取實例能調用的全部方法,除了不可枚舉(ES6 class方法是不可枚舉的)

其實關於繼承js已經有個很好的的實現方法叫寄生組合繼承,大概原理是用構造函數繼承實例屬性,用原型繼承原型方法,而寄生組合繼承是在組合繼承的基礎上強化,二者的區別是前者避免了兩次實例化父類,是目前比較好的es5實現繼承的方法:

function Veticle(name) {
    this.name = name || null

    this.drive = function() {
        console.log(this.name + "drives! --from Veticle ");
    }
}

Veticle.prototype.getName = function() {
    return this.name;
}

function Car(name) {
    Veticle.call(this)

    this.name = name || 'car'
}

function inheritProto(subType, superType) {
    var prototype = Object.create(superType.prototype);
    subType.prototype.constructor = subType;
    subType.prototype = prototype;
}

inheritProto(Car, Veticle)

var car = new Car('siip');

總結

    js中有一些構造方法諸如Object、Function、String、Number等等,噹噹前對象調用方法或獲取屬性時,會順着自身對象到構造函數原型,再到Object原型,最後到Null這麼一條原型鏈上查找。原型鏈上有兩個關鍵詞prototype和constructor比較重要,prototype是設置構造函數的原型對象,constructor是聲明原型的構造函數,無論是對象仍是函數,都有一個隱式屬性_proto_用來構成一條完整原型鏈的指向。    繼承有繼承屬性和繼承方法,不少時候es5實現繼承比es6要稍微簡單一點,es6的class的原型方法是不可枚舉的,有時候,掛載方法時須要經過Object.getOwnPropertyNames方法獲取。

相關文章
相關標籤/搜索