javascript之模拟类继承

前言

ES6时代的来临,使得类继承变得如此的圆滑。但是,你有思考过ES6的类继承模式吗?如何去实现它呢?

类继承对于JavaScript来说,实现方式与Java等类语言大不相同。熟悉JavaScript的开发者都清楚,JavaScript是基于原型模式的。那么,在es6没有出来之前,js是如何继承的呢?这是一个非常有意思的话题。希望带着疑问看文章,或许对你的提升会更加巨大。如果你喜欢我的文章,欢迎评论,欢迎Star~。欢迎关注我的github博客

正文

让我来——构造函数

其实,js模拟一个类的方式非常的简单——构造函数。或许,这是所有人都在普遍使用的方式。我们先来看一个例子:

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操作又会有所不同。比方说:

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

构造函数前面没有加new的情况下,会导致这个对象没有返回值来进行赋值。但是,在类语言中,在类名前不使用new是会报错的。

下面,我们应该进一步来看一下js的new操作符的原理,以及实现。

你懂我——new操作符

new方法原理:

  1. 创建一个新的对象
  2. 将对象的proto指向构造函数的原型
  3. 调用构造函数
  4. 返回新对象

js代码实现部分:

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如何去模拟类,我们已经讲述完了。接下来,我们应该看一下如何去实现类似与其他语言的类继承模式。

尽管ES6已经对extends关键词进行了实现,但是原理性的知识,我们应该需要明白。

初印象——类继承

先来看一个场景,无论是狗或者是猫,它们都有一个共同的类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博客

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • 官方中文版原文链接 感谢社区中各位的大力支持,译者再次奉上一点点福利:阿里云产品券,享受所有官网优惠,并抽取幸运大...
    HetfieldJoe阅读 3,652评论 2 27
  • class的基本用法 概述 JavaScript语言的传统方法是通过构造函数,定义并生成新对象。下面是一个例子: ...
    呼呼哥阅读 4,070评论 3 11
  • 0 写在前面的话 大多数的面向对象编程语言中,比如C++和Java,在使用他们完成任务之前,必须创建类(class...
    自度君阅读 988评论 0 3
  • 上一篇我们简单实现了一个MVP的构架,下面我们来做一个简单的封装使其使用更简单方便 源码地址RxMVP分支Tag0...
    Javen205阅读 1,592评论 0 1
  • 我的永无乡是在一个探险岛,那里没有一个人来过,我是第一个来到这个岛的。我走在路边,一边看着这里的景色,一边哼着愉快...
    琐歌阅读 236评论 0 0