說到原型,不得不提到原型鏈,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();
因此當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方法獲取。