打算針對js的繼承寫一系列文章,詳細的分析js裏繼承原理,實現方式,各類繼承方式的優缺點,以及最優繼承方案,還有多繼承的問題等….javascript
面向對象的編程的核心是封裝、繼承和多態,js能夠看做是一種面向對象的語言,而面向對象的擴展性最核心的部分是多態,多態的必要條件有三個,首先就是繼承,其次父類的引用指向子類,最後是方法重寫。對於js來講,因爲其建立對象的方式多種多樣,所以,須要對父類的多種屬性和方法實現很好的繼承,就必須找到一個比較完善的方法。本篇文章首選介紹三種最基本的繼承方式,並分析這幾種繼承方式的缺陷。html
介紹js繼承前,你們先須要js裏類的各類屬性以及js建立對象的幾種模式有所瞭解。java
js類的屬性能夠參考: javascript中類的屬性研究 這篇文章。編程
js建立對象的方式能夠參考:javascript建立對象的三種模式 這篇文章。數組
第一種方式:對象冒充app
對象冒充,是指將父類的屬性和方法一塊兒傳給子類做爲特權屬性和特權方法。函數
function Person(name,age){ this.name = name; this.age = age; this.sayHi = function(){ alert('hi'); } } Person.prototype.walk = function(){ alert('walk.......'); } function Student(name,age,grade){ this.newMethod = Person; this.newMethod(name,age); delete this.newMethod; this.grade = grade; } var s1 = new Student('xiaoming',10,3); console.log(s1.name,s1.age,s1.grade);//xiaoming 10 3 //s1.walk();//s1.walk is not a function
可見Student類只繼承了Person類的特權屬性和方法,並無繼承Person類的共有屬性和方法。this
第二種方式:call或applyspa
使用call或apply改變對象的做用域來實現繼承,讓父類的this等於新建立的子類的對象(由於call和apply繼承實現機制是同樣的,就是傳參方式不一樣,call傳多個參數用逗號隔開,apply用數組),本文主要介紹call來實現繼承。prototype
function Person(name,age){ this.name = name; this.age = age; this.sayHi = function(){ alert('hi'); } } Person.prototype.walk = function(){ alert('walk.......'); } function Student(name,age,grade){ Person.call(this,name,age); this.grade = grade; } var s1 = new Student('xiaoming',10,3); console.log(s1.name,s1.age,s1.grade);//xiaoming 10 3 //s1.walk();//s1.walk is not a function
同第一種問題同樣,沒有繼承共有屬性和方法。call改變了Person中this的做用域,使其指向了Student。對於call方法舉例以下:
function Person(){ this.name ='xiaoming'; } Person.call(this); alert(window.name);
此例將Person中this的做用域擴大到window上,使得Person中的name屬性變爲一個全局變量。
第三種方式:prototype
使用prototype屬性實現繼承,讓父類的prototype賦給子類的prototype,也能夠將父類的實例賦給子類的prototype,這裏先介紹將父類的原型賦給子類的原型這種方式,並探討這種方式的缺陷。在之後會着重介紹prototyp這種繼承方式。
function Person(name,age){ this.name = name; this.age = age; this.sayHi = function(){ alert('hi'); } } Person.prototype.walk = function(){ alert('walk.......'); } function Student(name,age,grade){ this.grade = grade; } Student.prototype = Person.prototype; var s1 = new Student('xiaoming',6,3); s1.walk();//walk....... console.log(s1.name,s1.age,s1.grade);//xiaoming 6 3 console.log(s1.constructor); // Person(name,age) Student.prototype.study = function(){ alert('I am study'); } var p1 = new Person(); p1.study();//I am study
主要缺陷:不能繼承父類的特權屬性和特權方法,子類的構造函數變成了Person(name,age),直接致使修改子類的原型方法時,父類也跟着修改了,耦合度過高了。
若是將父類的實例指向子類的原型會出現什麼狀況呢?
function Person(name,age){ this.name = name; this.age = age; this.sayHi = function(){ alert('hi'); } } Person.prototype.walk = function(){ alert('walk.......'); } function Student(name,age,grade){ this.grade = grade; } Student.prototype = new Person(); var s1 = new Student('xiaoming',6,3); s1.walk();//walk....... console.log(s1.name,s1.age,s1.grade);//undefined undefined 3 console.log(s1.constructor); // Person(name,age) Student.prototype.study = function(){ alert('I am study'); } var p1 = new Person(); //p1.study();// p1.study is not a function
雖然子類Student的實例s1仍然指向父類Person的構造函數,但此時修改子類的共有方法並不會對父類有所影響。而後這種方式存在一個更爲嚴重的問題是,子類雖然繼承父類的特權屬性,可是無法進行修改。而且每建立一個子類的實例時都會把父類的全部屬性和方法建立一遍,相對於繼承父類的prototype屬性中共有方法使用同一代碼塊對代碼空間存在較爲嚴重的浪費。
總結:幾種繼承方式各有各的缺陷,那麼如何實現完美的繼承呢。也許將其中的某兩種繼承方式結合起來才行。在之後會接着介紹call和prototype結合實現js的繼承功能。