javascript继承_JavaScript ES2015中的对象继承模式

javascript继承

一个孩子躺在床上用火炬读书,玩具在墙上投射出可怕的剪影

随着人们期待已久的ES2015(以前称为ES6)的到来,JavaScript配备了专门用于定义类的语法。 在本文中,我将探讨是否可以利用类语法从较小的部分组成类。

保持层次结构深度最小对于保持代码干净很重要。 明智地安排班级对您有所帮助。 对于大型代码库,一种选择是从较小的部分创建类。 作文课。 这也是避免重复代码的常见策略。

想象一下,我们正在构建一个游戏,其中玩家生活在动物世界中。 有些是朋友,有些则是敌对的(像我这样的狗人可能会说所有的猫都是敌对的生物)。 我们可以创建一个HostileAnimal类,该类扩展了Animal ,作为Cat的基类。 在某个时候,我们决定添加旨在伤害人类的机器人。 我们要做的第一件事是创建Robot类。 现在,我们有两个具有相似属性的类。 HostileAnimalHostileAnimalRobot都能够attack()

如果我们可以以某种方式在单独的类或对象中定义敌对性,比如说Hostile ,我们可以将CatRobot重用。 我们可以通过各种方式做到这一点。

多重继承是某些经典OOP语言支持的功能。 顾名思义,它使我们能够创建从多个基类继承的类。 在下面的Python代码中查看Cat类如何扩展多个基类:

class Animal(object):
  def walk(self):
    # ...

class Hostile(object):
  def attack(self, target):
    # ...

class Dog(Animal):
  # ...

class Cat(Animal, Hostile):
  # ...

dave = Cat();
dave.walk();
dave.attack(target);

接口是(类型化的)经典OOP语言中的常见功能。 它允许我们定义类应包含的方法(有时是属性)。 如果没有该类,则编译器将引发错误。 如果Cat不具有attack()walk()方法,则以下TypeScript代码将引发错误:

interface Hostile {
  attack();
}

class Animal {
  walk();
}

class Dog extends Animal {
  // ...
}

class Cat extends Animal implements Hostile {
  attack() {
    // ...
  }
}

多重继承遭受菱形问题 (两个父类定义相同的方法)。 有些语言通过实现其他策略(例如mixins)来回避这个问题。 Mixins是仅包含方法的微小类。 除了扩展这些类之外,mixin还包含在另一个类中。 例如,在PHP中,mixin是使用Traits实现的。

class Animal {
  // ...
}

trait Hostile {
  // ...
}

class Dog extends Animal {
  // ...
}

class Cat extends Animal {
  use Hostile;
  // ...
}

class Robot {
  use Hostile;
  // ...
}

回顾:ES2015类语法

如果您没有机会深入研究ES2015类或感到对它们不了解,请确保在继续之前阅读Jeff Mott的“ 面向对象JavaScript —深入ES6类”

简而言之:

  • class Foo { ... }描述了一个名为Foo的类
  • class Foo extends Bar { ... }描述了一个类Foo ,它扩展了另一个类Bar

在类块中,我们可以定义该类的属性。 对于本文,我们只需要了解构造函数和方法:

  • constructor() { ... }是一个保留函数,在创建时执行( new Foo()
  • foo() { ... }创建一个名为foo的方法

类语法主要是JavaScript原型模型上的语法糖。 它没有创建类,而是创建了一个函数构造函数:

class Foo {}
console.log(typeof Foo); // "function"

这里的要点是,JavaScript不是基于类的OOP语言。 甚至有人可能认为语法具有欺骗性,给人以为是欺骗性的印象。

编写ES2015类

可以通过创建引发错误的伪方法来模仿接口。 继承后,必须重写该函数以避免该错误:

class IAnimal {
  walk() {
    throw new Error('Not implemented');
  }
}

class Dog extends IAnimal {
  // ...
}

const robbie = new Dog();
robbie.walk(); // Throws an error

如前所述,这种方法依赖于继承。 要继承多个类,我们将需要多个继承或混合。

另一种方法是编写定义类后验证类的实用程序函数。 一个示例可以在“ 等待片刻,JavaScript不支持多重继承!”中找到。 由Andrea Giammarchi撰写。 请参见“基本对象。实现功能检查”部分。

是时候探索各种方法来应用多个继承和混合。 以下所有检查的策略都可以在GitHub找到

Object.assign(ChildClass.prototype, Mixin...)

在ES2015之前,我们使用了原型进行继承。 所有函数都具有prototype属性。 使用new MyFunction()创建实例时, new MyFunction() prototype复制到实例中的属性。 当您尝试访问实例中没有的属性时,JavaScript引擎将尝试在原型对象中查找它。

为了演示,请看以下代码:

function MyFunction () {
  this.myOwnProperty = 1;
}
MyFunction.prototype.myProtoProperty = 2;

const myInstance = new MyFunction();

// logs "1"
console.log(myInstance.myOwnProperty);
// logs "2"
console.log(myInstance.myProtoProperty);

// logs "true", because "myOwnProperty" is a property of "myInstance"
console.log(myInstance.hasOwnProperty('myOwnProperty'));
// logs "false", because "myProtoProperty" isn’t a property of "myInstance", but "myInstance.__proto__"
console.log(myInstance.hasOwnProperty('myProtoProperty'));

免费学习PHP!

全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。

原价$ 11.95 您的完全免费

这些原型对象可以在运行时创建和修改。 最初,我尝试使用AnimalHostile类:

class Animal {
  walk() {
    // ...
  }
}

class Dog {
  // ...
}

Object.assign(Dog.prototype, Animal.prototype);

上面的方法不起作用,因为类方法不可枚举 。 实际上,这意味着Object.assign(...)不会从类中复制方法。 这也使得创建将方法从一个类复制到另一个类的函数变得困难。 但是,我们可以手动复制每个方法:

Object.assign(Cat.prototype, {
  attack: Hostile.prototype.attack,
  walk: Animal.prototype.walk,
});

另一种方法是放弃类并将对象用作混合。 积极的副作用是,不能使用混合对象来创建实例,从而防止滥用。

const Animal = {
  walk() {
    // ...
  },
};

const Hostile = {
  attack(target) {
    // ...
  },
};

class Cat {
  // ...
}

Object.assign(Cat.prototype, Animal, Hostile);

优点

  • Mixins无法初始化

缺点

  • 需要额外的一行代码
  • Object.assign()有点晦涩
  • 重塑原型继承以与ES2015类一起使用

在构造函数中合成对象

使用ES2015类,可以通过在构造函数中返回一个对象来覆盖实例:

class Answer {
  constructor(question) {
    return {
      answer: 42,
    };
  }
}

// { answer: 42 }
new Answer("Life, the universe, and everything");

我们可以利用该功能从子类中的多个类组成一个对象。 请注意, Object.assign(...)仍然不能与mixin类一起使用,因此我也在这里使用了对象:

const Animal = {
  walk() {
    // ...
  },
};

const Hostile = {
  attack(target) {
    // ...
  },
};

class Cat {
  constructor() {
    // Cat-specific properties and methods go here
    // ...

    return Object.assign(
      {},
      Animal,
      Hostile,
      this
    );
  }
}

由于this是在上述上下文中引用的类(具有不可枚举的方法),因此Object.assign(..., this)不会复制Cat的方法。 相反,您将必须this显式设置字段和方法,以便Object.assign()能够应用这些字段和方法,如下所示:

class Cat {
  constructor() {
    this.purr = () => {
      // ...
    };

    return Object.assign(
      {},
      Animal,
      Hostile,
      this
    );
  }
}

这种方法不切实际。 因为您要返回一个新对象而不是实例,所以它本质上等效于:

const createCat = () => Object.assign({}, Animal, Hostile, {
  purr() {
    // ...
  }
});

const thunder = createCat();
thunder.walk();
thunder.attack();

我认为我们可以同意后者更具可读性。

优点

  • 它有效,我猜呢?

缺点

  • 很晦涩
  • ES2015类语法的零收益
  • ES2015类的滥用

类工厂功能

这种方法利用了JavaScript在运行时定义类的能力。

首先,我们将需要基类。 在我们的示例中, AnimalRobot作为基类。 如果您想从头开始,那么也可以使用空类。

class Animal {
  // ...
}

class Robot {
  // ...
}

接下来,我们必须创建一个工厂函数,该函数返回一个扩展了Base类的新类,该类作为参数传递。 这些是mixin:

const Hostile = (Base) => class Hostile extends Base {
  // ...
};

现在我们可以将任何类传递给Hostile函数,该函数将返回一个新的类,将Hostile和我们传递给该函数的任何类组合在一起:

class Dog extends Animal {
  // ...
}

class Cat extends Hostile(Animal) {
  // ...
}

class HostileRobot extends Hostile(Robot) {
  // ...
}

我们可以通过几个类来应用多个mixin:

class Cat extends Demonic(Hostile(Mammal(Animal))) {
  // ...
}

您还可以将Object用作基类:

class Robot extends Hostile(Object) {
  // ...
}

优点

  • 易于理解,因为所有信息都在类声明标头中

缺点

  • 在运行时创建类可能会影响启动性能和/或内存使用率

结论

当我决定研究此主题并撰写有关此主题的文章时,我期望JavaScript的原型模型对生成类有帮助。 由于类语法使方法不可枚举,因此对象操作变得更加困难,几乎不切实际。

类语法可能会产生一种幻觉,即JavaScript是一种基于类的OOP语言,但事实并非如此。 使用大多数方法,您将必须修改对象的原型以模仿多重继承。 最后一种方法是使用类工厂函数,这是使用mixin组成类的可接受策略。

如果您发现基于原型的编程有局限性,则可能需要考虑一下自己的心态。 原型提供了您可以利用的无与伦比的灵活性。

如果出于某种原因仍然喜欢经典编程,则可能需要研究可编译为JavaScript的语言。 例如, TypeScript是JavaScript的超集,它添加了(可选)静态类型和模式,您可以从其他经典OOP语言中识别出这些类型。

您将在项目中使用以上两种方法之一吗? 您找到更好的方法了吗? 在评论中让我知道!

Jeff MottScott MolinariVildan SofticJoan Yin 对此文章进行了同行评审。 感谢所有SitePoint的同行评审员使SitePoint内容达到最佳状态!

翻译自: https://www.sitepoint.com/patterns-object-inheritance-javascript-es2015/

javascript继承

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值