打算針對js的繼承寫一系列文章,詳細的分析js里繼承原理,實現方式,各種繼承方式的優缺點,以及最優繼承方案,還有多繼承的問題等….
面向對象的編程的核心是封裝、繼承和多態,js可以看作是一種面向對象的語言,而面向對象的擴展性最核心的部分是多態,多態的必要條件有三個,首先就是繼承,其次父類的引用指向子類,最后是方法重寫。對於js來說,由於其創建對象的方式多種多樣,因此,需要對父類的多種屬性和方法實現很好的繼承,就必須找到一個比較完善的方法。本篇文章首選介紹三種最基本的繼承方式,並分析這幾種繼承方式的缺陷。
介紹js繼承前,大家先需要js里類的各種屬性以及js創建對象的幾種模式有所了解。
js類的屬性可以參考: javascript中類的屬性研究 這篇文章。
js創建對象的方式可以參考:javascript創建對象的三種模式 這篇文章。
第一種方式:對象冒充
對象冒充,是指將父類的屬性和方法一起傳給子類作為特權屬性和特權方法。
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類的共有屬性和方法。
第二種方式:call或apply
使用call或apply改變對象的作用域來實現繼承,讓父類的this等於新創建的子類的對象(因為call和apply繼承實現機制是一樣的,就是傳參方式不同,call傳多個參數用逗號隔開,apply用數組),本文主要介紹call來實現繼承。
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的繼承功能。
