在傳統的基於Class的語言如Java、C++中,繼承的本質是擴展一個已有的Class,並生成新的Subclass。函數
因爲這類語言嚴格區分類和實例,繼承其實是類型的擴展。可是,JavaScript因爲採用原型繼承,咱們沒法直接擴展一個Class,由於根本不存在Class這種類型。this
可是辦法仍是有的。咱們先回顧Student
構造函數:spa
function Student(props) { this.name = props.name || 'Unnamed'; } Student.prototype.hello = function () { alert('Hello, ' + this.name + '!'); }
如今,咱們要基於Student
擴展出PrimaryStudent
,能夠先定義出PrimaryStudent
:prototype
function PrimaryStudent(props) { // 調用Student構造函數,綁定this變量: Student.call(this, props); this.grade = props.grade || 1; }
可是,調用了Student
構造函數不等於繼承了Student
,PrimaryStudent
建立的對象的原型是:code
new PrimaryStudent() ----> PrimaryStudent.prototype ----> Object.prototype ----> null
必須想辦法把原型鏈修改成:對象
new PrimaryStudent() ----> PrimaryStudent.prototype ----> Student.prototype ----> Object.prototype ----> null
這樣,原型鏈對了,繼承關係就對了。新的基於PrimaryStudent
建立的對象不但能調用PrimaryStudent.prototype
定義的方法,也能夠調用Student.prototype
定義的方法。blog
若是你想用最簡單粗暴的方法這麼幹:繼承
PrimaryStudent.prototype = Student.prototype;
是不行的!若是這樣的話,PrimaryStudent
和Student
共享一個原型對象,那還要定義PrimaryStudent
幹啥?(就像,父親生個兒子,是讓保持他身上的特性,能夠幫父親幹活,可是生出的兒子去幹指定的活,是讓父親幫他乾的,那生這個兒子幹啥。。。)ip
咱們必須藉助一箇中間對象來實現正確的原型鏈,這個中間對象的原型要指向Student.prototype
。爲了實現這一點,參考道爺(就是發明JSON的那個道格拉斯)的代碼,中間對象能夠用一個空函數F
來實現:原型鏈
// PrimaryStudent構造函數: function PrimaryStudent(props) { Student.call(this, props); this.grade = props.grade || 1; } // 空函數F: function F() { } // 把F的原型指向Student.prototype: F.prototype = Student.prototype; // 把PrimaryStudent的原型指向一個新的F對象,F對象的原型正好指向Student.prototype: PrimaryStudent.prototype = new F(); // 把PrimaryStudent原型的構造函數修復爲PrimaryStudent: PrimaryStudent.prototype.constructor = PrimaryStudent; // 繼續在PrimaryStudent原型(就是new F()對象)上定義方法: PrimaryStudent.prototype.getGrade = function () { return this.grade; }; // 建立xiaoming: var xiaoming = new PrimaryStudent({ name: '小明', grade: 2 }); xiaoming.name; // '小明' xiaoming.grade; // 2 // 驗證原型: xiaoming.__proto__ === PrimaryStudent.prototype; // true xiaoming.__proto__.__proto__ === Student.prototype; // true // 驗證繼承關係: xiaoming instanceof PrimaryStudent; // true xiaoming instanceof Student; // true
發現這樣繼承的步驟有點繁瑣了,能夠將它封裝起來!!!
若是把繼承這個動做用一個inherits()
函數封裝起來,還能夠隱藏F
的定義,並簡化代碼:
function inherits(Child, Parent) { var F = function () {}; F.prototype = Parent.prototype; Child.prototype = new F(); Child.prototype.constructor = Child; }
這個inherits()
函數能夠複用:
function Student(props) { this.name = props.name || 'Unnamed'; } Student.prototype.hello = function () { alert('Hello, ' + this.name + '!'); } function PrimaryStudent(props) { Student.call(this, props); this.grade = props.grade || 1; } // 實現原型繼承鏈: inherits(PrimaryStudent, Student); // 綁定其餘方法到PrimaryStudent原型: PrimaryStudent.prototype.getGrade = function () { return this.grade; };
JavaScript的原型繼承實現方式就是:
定義新的構造函數,並在內部用call()
調用但願「繼承」的構造函數,並綁定this
;
藉助中間函數F
實現原型鏈繼承,最好經過封裝的inherits
函數完成;
繼續在新的構造函數的原型上定義新方法。