javascript之模擬類繼承

前言

ES6時代的來臨,使得類繼承變得如此的圓滑。可是,你有思考過ES6的類繼承模式嗎?如何去實現它呢?git

類繼承對於JavaScript來講,實現方式與Java等類語言大不相同。熟悉JavaScript的開發者都清楚,JavaScript是基於原型模式的。那麼,在es6沒有出來以前,js是如何繼承的呢?這是一個很是有意思的話題。但願帶着疑問看文章,或許對你的提高會更加巨大。若是你喜歡個人文章,歡迎評論,歡迎Star~。歡迎關注個人github博客es6

正文

讓我來——構造函數

其實,js模擬一個類的方式很是的簡單——構造函數。或許,這是全部人都在廣泛使用的方式。咱們先來看一個例子:github

function Person(name){
      this.name = name;
}

Person.prototype.sayName = function(){
     console.log(this.name);
}

const person = new Person('zimo');

person.sayName();       //zimo

這裏經過構造函數模擬出來的類,其實和其餘語言的類行爲上是基本一致的,惟一的區別就是它不具有私有方法。並且,他們一樣是經過new操做符來進行實例化的,可是js的new操做與其它語言的new操做又會有所不一樣。比方說:app

const person = Person('zimo');
console.log(person)    //undefined

構造函數前面沒有加new的狀況下,會致使這個對象沒有返回值來進行賦值。可是,在類語言中,在類名前不使用new是會報錯的。函數

下面,咱們應該進一步來看一下js的new操做符的原理,以及實現。工具

你懂我——new操做符

new方法原理:this

  1. 建立一個新的對象
  2. 將對象的__proto__指向構造函數的原型
  3. 調用構造函數
  4. 返回新對象

js代碼實現部分:spa

const person = new Person(args);
//至關於
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如何去模擬類,咱們已經講述完了。接下來,咱們應該看一下如何去實現相似與其餘語言的類繼承模式。prototype

儘管ES6已經對extends關鍵詞進行了實現,可是原理性的知識,咱們應該須要明白。code

初印象——類繼承

先來看一個場景,不管是狗或者是貓,它們都有一個共同的類animal,如圖:

animal

在真實開發中,咱們必須去實現類與類之間的繼承關係,否則的話,咱們就必須重複地去命名構造函數(這樣的方式是醜陋的)。

因此,像上述的場景,開發過程當中多的數不勝數,可是本質都是不變的。接下來,那咱們以一個例子來作說明,而且明白大體是如何去實現的。

例子:咱們須要去構造一個交通工具類,該類具有屬性:輪子、速度和顏色(默認爲黑),它還具有方法run(time)返回距離。以後,咱們還須要去經過該類繼承一個‘汽車’類和一個‘單車’類。

如圖:

inherits

實現:

function Vehicle(wheel, speed){      //首先構造一個交通工具類
    this.wheel = wheel;
    this.speed = speed;
    this.color = 'black';
}

Vehicle.prototype.run = function(time){    //在它的原型上定義方法
    return this.speed * time;
}

function Car(wheel, speed, brand){     //經過在汽車類中去調用父類
    Vehicle.call(this, wheel, speed);
    this.brand = brand;
}

Car.prototype = new Vehicle();      //將汽車類的原型指向交通工具的實例

function Bicycle(wheel, speed, owner){        //一樣,構造一個自行車類,在其中調用父類
    Vehicle.call(this, wheel, speed);
    this.owner = owner;
}

Bicycle.prototype = new Vehicle();             //將其原型指向交通工具實例

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

這樣子,就實現了類的繼承。

大體的思路是:在繼承類中調用父類,以及將繼承類的原型賦值爲父類的實例。

可是,每次實現若是都是這樣子的話,又會顯得很是的累贅,咱們並無將能夠重複使用的部分。所以,咱們須要將不變的部分進行封裝,封裝成一個方法,而後將可變的部分當中參數傳遞進來。

接下來,咱們來分析一下類繼承的封裝方法extend。

真實的我——繼承封裝

首先,咱們來看一下,咱們須要實現怎樣的繼承:

function Animal(name){         //構造一個動物類
  this.name = name;
}
Animal.prototype.sayName = function(){    
  console.log('My name is ' + this.name);
}

/**
extends方法其中包含子類的constructor、自身的屬性、和來自父元素繼承的屬性
*/

var Dog = Animal.extends({                            //使用extends方法來實現類的封裝
  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方法是咱們須要去實現的,在實現以前,咱們能夠來對比一下ES6的語法

class Animal {
  constructor(name){
    this.name = name;
  }

  sayName(){
    console.log('My name is ' + this.name);
  }
}

/**
對比上面的extend封裝和es6的語法,咱們會發現,其實差別並無太大
*/

class Dog extends Animal{
  constructor(name, lan){
    super(name);

    this.lan = lan;
  }

  sayName(){
    super.sayName();
  }

  sayLan(){
    console.log(this.lan);
  }
}

其實,不少地方是類似的,比方說super和this._super。這個對象實際上是看起來是父構造函數,由於他能夠直接調用this._super(name),但它同時還具有父構造函數原型上的函數,所以咱們能夠把它稱爲父包裝器。可是,必須保證的是_super中的函數對象上下文必須都是指向子構造函數的

使用一張簡陋的圖來表示整個關係的話,如圖:

image

下面咱們來實現一下這個extend方法。

Function.prototype.extend = function(props){
  var Super = this;

  var Temp = function(){};

  Temp.prototype = Super.prototype;

  var superProto = new Temp();   //去建立一個指向Super.prototype的實例

  var _super = function(){   //建立一個父類包裝器
    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的原型方法拷貝過來時,this指向Child構造函數
    }

  }

  Child.prototype = superProto;               //將子類的原型指向父類的純實例
  Child.prototype._super = _super;                //構建一個引用指向父類的包裝器

  for(var i in props){
    if( i !== 'constructor'){
      Child.prototype[i] = props[i];   //將props中方法放到Child的原型上面
    }
  }

  return Child;
}

總結

繼承的一些內容就分析到這裏。其實,自從ES6標準出來以後,類的繼承已經很是廣泛了,由於真心好用。可是,也是愈來愈有人不懂得如何去理解這個繼承的原理了。其實ES6中的繼承的實現,也是挺簡單的。

若是你對我寫的有疑問,能夠評論,如我寫的有錯誤,歡迎指正。你喜歡個人博客,請給我關注Star~呦。你們一塊兒總結一塊兒進步。歡迎關注個人github博客

相關文章
相關標籤/搜索