簡單總結繼承的幾種方式
JavaScript作為一門弱類型的語言,本著精簡的原則,它取消了類的概念,只有對象的概念,
更是有萬物皆對象的說法。在基于類的面向對象方式中,對象(object)依靠類(class)來產生。
而在基于原型的面向對象方式中,對象(object)則是依靠構造器(constructor) 利用 原型(prototype)
構造出來的。而JavaScript語言正是如此,它是通過一種叫做原型(prototype)的方式來實現面向對象編程的。
它和其他的面向對象類編程語言一樣,只是它的實現方式不同而已,或者說他們采用了不同的面向對象設計哲學。
那么下面就讓我們來簡單總結一下繼承的幾種方式:
- 擴展原型對象實現繼承
構造函數有一個 prototype 屬性,指向的就是原型對象,通過給原型對象添加屬性和方法,讓構造函數的實例
都可以訪問到,從而實現繼承
function Animal(name,color,say){this.name = name;this.color = color;this.say = function(){console.log('喵喵喵');}
}var cat = new Animal('cat','white');
// 如果給Animal的prototype屬性上添加個 cry 方法 ,那么實例對象 cat將也會有 cry方法
Animal.prototype.cry = function(){console.log('嗚嗚嗚');
}
- 替換原型對象實現繼承 (常用類型)
為什么會有該方式呢?
其實上面擴展原型對象的方法已經很方便了,但是為什么我們還需要使用該方式呢?試想一下,
當我們需要給構造函數的原型對象添加許多屬性和方法的時候,豈不是要寫很多冗余(重復)的代碼。例如上面的例子
看下面代碼:// 我們要給 構造函數Animal 添加很多的方法 即:Animal.prototype.aaa = function(){};Animal.prototype.bbb = function(){};Animal.prototype.ccc = function(){};Animal.prototype.ddd = function(){};Animal.prototype.eee = function(){};...... // 諸如這樣的話,代碼就顯得不那么優雅,高效了吧。
實現方式?
重新給構造函數的prototype屬性(原型對象)賦值,指向一個全新的對象,在這個對象中添加屬性和方法,注:
一定要添加一個constructor屬性,并且指向構造函數本身 具體看代碼:Animal.prototype = {// 一定要指明constructor屬性,不然的話,會根據原型鏈查找一直到Object.prototypeconstructor : Animal; saying : function(){},crying : function(){},doing : function(){}......}
- 混入繼承
- 混入繼承的使用場景:已知對象 o1, o2, 需要把 o1中的功能(屬性方法)拷貝到 o2 中去
實現方式 :用 for...in... 對 o 進行遍歷(可以將混入繼承的模式封裝成函數),如下所示
// target : 目標對象(接收數據的對象)// source : 接收對象 (數據從哪個對象中來)function mixin(target,source){for(var key in source){target[key] = source[key];}return target;}
原理其實很簡單,jQuery中的$.extend 方法利用的就是混入繼承的原理
- 原型 + 混入繼承
- 本質上就是對混入繼承的一次運用
只不過目標對象為原型對象而已
// 運用上面封裝的混入繼承的函數 將對象{a:10,b:20,c:function(){}}的功能考本到Animal原型對象上去mixin(Animal.prototype, {a:10,b:20,c:function(){}} ); // 還可以 這樣操作 給Animal構造函數 的原型對象添加一個extend方法,在該方法中調用mixin函數,這樣的話也可以實現Animal.prototype.extend = function(){mixin(Animal.prototype, source);}// 不過兩種方法沒有本質之差,看大家易于接受哪個了
- 經典繼承 —> 道格拉斯《JavaScript語言精粹》中提到的一種繼承模型
實現的功能:已知一個對象 o1 需要創建一個新的對象 o2 ,這個新的對象需要繼承自對象 o1,代碼如下:
// 可以將經典繼承 封裝成一個函數function create(o){function F(){}; // 創建一個構造函數F.prototype = o; // 將F 的原型指向 對象 o;return new F(); // 將 構造函數的實例 返回出去,這樣的話 F的實例對象,就會繼承自 o}var o2 = create(o1); // 即:o2 繼承于 o1 ; o2.__proto__ = o1;
- 使用場景 :要創建一個對象(不需要關心構造函數),新對象需要繼承自另一個指定的對象
ES5中 :Object.create( ) 的實現原理就源自于經典繼承
- 借用構造函數 實現繼承
先看一下代碼 再解釋:
// 需要兩個構造函數function Person(name,age,gender){ this.name = name;this.age = age;this.gender = gender;}// function Student(name,age,){// this.name = name;// this.age = age;// this.gender = gender;// }// 這樣的話 name,age 屬性都一樣,就會產生重復的代碼 我們可以巧妙的利用call 來簡單的實現function Student(name,age){Person.call(this,name,age); // 其實就是用 call的方法,call借用Person的功能this.gender = gender;}
- 借用Person中的構造函數的功能,this表示構造函數的實例,即Student的實例對象可以繼承name,age屬性;
- 借用構造函數實現繼承,子構造函數借用父構造函數來完成,給子類Student的實例添加屬性
注意:由于要借用父類構造函數,所以父類構造函數的功能對子類對象要適用,例如下面情況就最好不用call
// 需要兩個構造函數 function Person(name,age,gender){ this.name = name;this.age = age;this.gender = gender; } // function Student(name,age,){ // this.name = name; // this.age = age; // } function Student(name,age){Person.call(this,name,age); }
- 上述情況,就最好不要使用call來借用父類構造函數Person的功能了,因為,gender屬性是Student子類構造函數
并不需要的,這樣的話,就會在Student中產生不必要的屬性和方法,如果子類函數還要有gender方法的話,那么就會和之前的產生沖突,交叉污染 其他情況:遇到需要實現功能,該對象上沒有這個功能,可以適當地去尋找已經有功能的對象