前言
ES6時(shí)代的來臨,使得類繼承變得如此的圓滑。但是,你有思考過ES6的類繼承模式嗎?如何去實(shí)現(xiàn)它呢?
類繼承對(duì)于JavaScript來說,實(shí)現(xiàn)方式與Java等類語言大不相同。熟悉JavaScript的開發(fā)者都清楚,JavaScript是基于原型模式的。那么,在es6沒有出來之前,js是如何繼承的呢?這是一個(gè)非常有意思的話題。希望帶著疑問看文章,或許對(duì)你的提升會(huì)更加巨大。如果你喜歡我的文章,歡迎評(píng)論,歡迎Star~。歡迎關(guān)注我的github博客
正文
讓我來——構(gòu)造函數(shù)
其實(shí),js模擬一個(gè)類的方式非常的簡單——構(gòu)造函數(shù)?;蛟S,這是所有人都在普遍使用的方式。我們先來看一個(gè)例子:
function Person(name){
this.name = name;
}
Person.prototype.sayName = function(){
console.log(this.name);
}
const person = new Person('zimo');
person.sayName(); //zimo
這里通過構(gòu)造函數(shù)模擬出來的類,其實(shí)和其他語言的類行為上是基本一致的,唯一的區(qū)別就是它不具備私有方法。而且,他們同樣是通過new操作符來進(jìn)行實(shí)例化的,但是js的new操作與其它語言的new操作又會(huì)有所不同。比方說:
const person = Person('zimo');
console.log(person) //undefined
構(gòu)造函數(shù)前面沒有加new的情況下,會(huì)導(dǎo)致這個(gè)對(duì)象沒有返回值來進(jìn)行賦值。但是,在類語言中,在類名前不使用new是會(huì)報(bào)錯(cuò)的。
下面,我們應(yīng)該進(jìn)一步來看一下js的new操作符的原理,以及實(shí)現(xiàn)。
你懂我——new操作符
new方法原理:
- 創(chuàng)建一個(gè)新的對(duì)象
- 將對(duì)象的proto指向構(gòu)造函數(shù)的原型
- 調(diào)用構(gòu)造函數(shù)
- 返回新對(duì)象
js代碼實(shí)現(xiàn)部分:
const person = new Person(args);
//相當(dāng)于
const person = Person.new(args);
Function.prototype.new = function(){
let obj = new Object();
obj.__proto__ = this.prototype;
const ret = this.apply(obj, arguments);
return (typeof ret == 'object' && ret) || obj;
}
到此為止,js如何去模擬類,我們已經(jīng)講述完了。接下來,我們應(yīng)該看一下如何去實(shí)現(xiàn)類似與其他語言的類繼承模式。
盡管ES6已經(jīng)對(duì)extends關(guān)鍵詞進(jìn)行了實(shí)現(xiàn),但是原理性的知識(shí),我們應(yīng)該需要明白。
初印象——類繼承
先來看一個(gè)場景,無論是狗或者是貓,它們都有一個(gè)共同的類animal,如圖:
在真實(shí)開發(fā)中,我們必須去實(shí)現(xiàn)類與類之間的繼承關(guān)系,不然的話,我們就必須重復(fù)地去命名構(gòu)造函數(shù)(這樣的方式是丑陋的)。
所以,像上述的場景,開發(fā)過程中多的數(shù)不勝數(shù),但是本質(zhì)都是不變的。接下來,那我們以一個(gè)例子來做說明,并且明白大致是如何去實(shí)現(xiàn)的。
例子:我們需要去構(gòu)造一個(gè)交通工具類,該類具備屬性:輪子、速度和顏色(默認(rèn)為黑),它還具備方法run(time)返回距離。之后,我們還需要去通過該類繼承一個(gè)‘汽車’類和一個(gè)‘單車’類。
如圖:
實(shí)現(xiàn):
function Vehicle(wheel, speed){ //首先構(gòu)造一個(gè)交通工具類
this.wheel = wheel;
this.speed = speed;
this.color = 'black';
}
Vehicle.prototype.run = function(time){ //在它的原型上定義方法
return this.speed * time;
}
function Car(wheel, speed, brand){ //通過在汽車類中去調(diào)用父類
Vehicle.call(this, wheel, speed);
this.brand = brand;
}
Car.prototype = new Vehicle(); //將汽車類的原型指向交通工具的實(shí)例
function Bicycle(wheel, speed, owner){ //同樣,構(gòu)造一個(gè)自行車類,在其中調(diào)用父類
Vehicle.call(this, wheel, speed);
this.owner = owner;
}
Bicycle.prototype = new Vehicle(); //將其原型指向交通工具實(shí)例
const car = new Car(4, 10, 'baoma');
const bicycle = new Bicycle(2, 5, 'zimo');
console.log(car.run(10)); //100
console.log(bicycle.run(10)); //50
這樣子,就實(shí)現(xiàn)了類的繼承。
大致的思路是:在繼承類中調(diào)用父類,以及將繼承類的原型賦值為父類的實(shí)例。
但是,每次實(shí)現(xiàn)如果都是這樣子的話,又會(huì)顯得非常的累贅,我們并沒有將可以重復(fù)使用的部分。因此,我們需要將不變的部分進(jìn)行封裝,封裝成一個(gè)方法,然后將可變的部分當(dāng)中參數(shù)傳遞進(jìn)來。
接下來,我們來分析一下類繼承的封裝方法extend。
真實(shí)的我——繼承封裝
首先,我們來看一下,我們需要實(shí)現(xiàn)怎樣的繼承:
function Animal(name){ //構(gòu)造一個(gè)動(dòng)物類
this.name = name;
}
Animal.prototype.sayName = function(){
console.log('My name is ' + this.name);
}
/**
extends方法其中包含子類的constructor、自身的屬性、和來自父元素繼承的屬性
*/
var Dog = Animal.extends({ //使用extends方法來實(shí)現(xiàn)類的封裝
constructor: function(name, lan){
this._super(name);
this.lan = lan
},
sayname: function(){
this._super.sayName();
},
sayLan: function(){
console.log(this.lan);
}
});
var animal = new Animal('animal');
var dog = new Dog('dog', '汪汪汪');
animal.sayName(); //My name is animal
dog.sayName(); // My name is dog
dog.sayLan(); // '汪汪汪'
其中的extend方法是我們需要去實(shí)現(xiàn)的,在實(shí)現(xiàn)之前,我們可以來對(duì)比一下ES6的語法
class Animal {
constructor(name){
this.name = name;
}
sayName(){
console.log('My name is ' + this.name);
}
}
/**
對(duì)比上面的extend封裝和es6的語法,我們會(huì)發(fā)現(xiàn),其實(shí)差異并沒有太大
*/
class Dog extends Animal{
constructor(name, lan){
super(name);
this.lan = lan;
}
sayName(){
super.sayName();
}
sayLan(){
console.log(this.lan);
}
}
其實(shí),很多地方是相似的,比方說super和this._super。這個(gè)對(duì)象其實(shí)是看起來是父構(gòu)造函數(shù),因?yàn)樗梢灾苯诱{(diào)用this._super(name),但它同時(shí)還具備父構(gòu)造函數(shù)原型上的函數(shù),因此我們可以把它稱為父包裝器。但是,必須保證的是_super中的函數(shù)對(duì)象上下文必須都是指向子構(gòu)造函數(shù)的。
使用一張簡陋的圖來表示整個(gè)關(guān)系的話,如圖:
下面我們來實(shí)現(xiàn)一下這個(gè)extend方法。
Function.prototype.extend = function(props){
var Super = this;
var Temp = function(){};
Temp.prototype = Super.prototype;
var superProto = new Temp(); //去創(chuàng)建一個(gè)指向Super.prototype的實(shí)例
var _super = function(){ //創(chuàng)建一個(gè)父類包裝器
return Super.apply(this, arguments);
}
var Child = function(){
if(props.constructor){
props.constructor.apply(this, arguments);
}
for(var i in Super.prototype){
_super[i] = Super.prototype[i].bind(this); //確保Super的原型方法拷貝過來時(shí),this指向Child構(gòu)造函數(shù)
}
}
Child.prototype = superProto; //將子類的原型指向父類的純實(shí)例
Child.prototype._super = _super; //構(gòu)建一個(gè)引用指向父類的包裝器
for(var i in props){
if( i !== 'constructor'){
Child.prototype[i] = props[i]; //將props中方法放到Child的原型上面
}
}
return Child;
}
總結(jié)
繼承的一些內(nèi)容就分析到這里。其實(shí),自從ES6標(biāo)準(zhǔn)出來之后,類的繼承已經(jīng)非常普遍了,因?yàn)檎嫘暮糜?。但是,也是越來越有人不懂得如何去理解這個(gè)繼承的原理了。其實(shí)ES6中的繼承的實(shí)現(xiàn),也是挺簡單的。
如果你對(duì)我寫的有疑問,可以評(píng)論,如我寫的有錯(cuò)誤,歡迎指正。你喜歡我的博客,請(qǐng)給我關(guān)注Star~呦。大家一起總結(jié)一起進(jìn)步。歡迎關(guān)注我的github博客