• 欢迎访问web前端中文站,JavaScript,CSS3,HTML5,web前端demo
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏web前端中文站吧

JavaScript ES2015 中对象继承的模式

ES2015(ES6) web前端中文站 2年前 (2017-06-17) 852次浏览 已收录 0个评论

JavaScript ES2015 中对象继承的模式

随着期待已久的 ES2015(以前称为 ES6)到来,JavaScript 配备了专门用于定义 类(classes) 的语法。在这篇文章中,我将探索如何利用类语法用较小的部分来组成一个类。

更多精彩内容请看 web 前端中文站
http://www.lisa33xiaoq.net 可按 Ctrl + D 进行收藏

将层次结构的深度保持在最低限度对于保持代码整洁很重要。这有助于你了解如何巧妙的分割类。对于一个大的代码库,一个选择是用较小的部分创建类;再组合成一个大类。这也是避免重复代码的常见策略。

想像一下,我们正在构建一个游戏,玩家生活在动物世界中。有些是朋友,其他的是敌人(像狗,可能会说所有的猫都是敌人)。我们可以创建一个 HostileAnimal 类,它扩展了类 Animal,作为 Cat 的基类。在某些时候,我们决定添加机器人来伤害人类。我们首先做的是创建 Robot 类。我们现在有两个类具有相似的属性。例如,HostileAnimalRobot 都有 attack() (攻击)方法。

如果我们可以以某种方式在一个单独的类或对象中定义一个称为 Hostile 类来表示敌对, 那么我们可以让 CatRobot 可以重复使用这个类。我们可以以各种方式做到这一点。

多重继承 是传统的 OOP 语言支持的一项功能。顾名思义,它给我们带来一种能力:创建一个继承自多个基类的类。看看 Cat 类如何在以下 Python 代码中扩展多个基类:

 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 语言一个常见的特性。它允许我们定义一个类应该包含什么方法(有时是属性)。如果那个类没有方法或者属性,编译器会抛出错误。以下 TypeScript 代码中,如果 Cat 没有 attack()walk() 方法,就会抛出一个错误。

 interface Hostile {   attack(); }  class Animal {   walk(); }  class Dog extends Animal {   // ... }  class Cat extends Animal implements Hostile {   attack() {     // ...   } } 

多重继承受到 菱形(diamond) 继承问题 的困扰(其中两个父类定义相同的方法)。一些语言通过实现其他策略(如 mixins)来规避这个问题。Mixins(混入)是只包含一些方法的微小类。而不是扩展这些类,mixins 被包含在另一个类中。例如在 PHP 中,使用 Traits 实现 mixins 。

 class Animal {   // ... }  trait Hostile {   // ... }  class Dog extends Animal {   // ... }  class Cat extends Animal {   use Hostile;   // ... }  class Robot {   use Hostile;   // ... } 

一个新的轮子: ES2015 Class 语法

如果你没有了解 ES2015 classes 或觉得你对这些语法还不是很了解的话,请务必先阅读 Jeff Mott 的 面向对象 JavaScript- 深入潜入 ES6 classes (愚人码头注:中文译文) 后继续。

概括地说:

  • 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 Classes

接口可以通过创建一个虚拟方法来模拟,但会抛出错误。一旦继承后,该函数必须被覆盖以避免这个错误:

 class IAnimal {   walk() {     throw new Error('Not implemented');   } }  class Dog extends IAnimal {   // ... }  const robbie = new Dog(); robbie.walk(); // Throws an error 

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

另一种方法是编写一个实用函数,在定义了一个类之后验证它。
一个例子可以在 Andrea Giammarchi 的 等待片刻,JavaScript 支持多重继承! 文章中找到,请参见“A Basic Object.implement Function Check.”一节。

探索应用多重继承和 mixins(混入) 的各种方法。以下所有调查的策略均可在 GitHub 上获得。

Object.assign(ChildClass.prototype, Mixin…)

在 ES2015 之前,我们使用原型继承。所有函数都有一个 prototype(原型) 属性。当使用 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", 因为 "myOwnProperty" 是 "myInstance" 的一个属性 console.log(myInstance.hasOwnProperty('myOwnProperty')); // logs "false", 因为 "myProtoProperty" 不是 "myInstance" 的一个属性, 但是,他是 "myInstance.__proto__" 的一个属性 console.log(myInstance.hasOwnProperty('myProtoProperty')); 

这些原型对象可以在运行时创建和修改。首先,我尝试使用 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, }); 

另一种方法是抛弃类,使用对象作为 mixins(混入)。一个确定的副作用是 mixin 对象不能用于创建实例,防止滥用。

 const Animal = {   walk() {     // ...   }, };  const Hostile = {   attack(target) {     // ...   }, };  class Cat {   // ... }  Object.assign(Cat.prototype, Animal, Hostile); 

优点

  • Mixins(混入) 不能被初始化

缺点

  • 需要一个额外的代码行
  • Object.assign() 有点晦涩
  • 重新设计原型继承来与 ES2015 classes 结合使用

在构造函数中组合对象

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

 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-这里声明明确的属性和方法     // ...      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 classes

类工厂函数

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

首先,我们将需要基类。在我们的例子中,AnimalRobot 作为基类。如果你想从头开始,一个空类也可以工作。

 class Animal {   // ... }  class Robot {   // ... } 

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

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

现在我们可以将任何类传递给 Hostile 函数,它将返回一个新的类,这个类结合了 Hostile 和我们传递给函数的任何类:

 class Dog extends Animal {   // ... }  class Cat extends Hostile(Animal) {   // ... }  class HostileRobot extends Hostile(Robot) {   // ... } 

我们可以通过几个 classes 应用多个 mixins :

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

你也可以使用 Object 作为一个基类:

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

优点

  • 更容易理解,因为所有信息都在类的头部声明

缺点

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

结论

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

类语法可能会让人产生 JavaScript 是基于类的 OOP 语言的错觉,但他不是。大多数方法,您必须修改对象的原型来模拟多个继承。最后一种方法,使用类工厂函数,是使用 mixins 组合 classes 一个可接受的策略。 愚人码头注:了解更多关于 JavaScript 中的工厂函数 。

如果您发现基于原型的编程限制,你可能想看看你的倾向。原型提供无与伦比的灵活性,您可以利用它。

如果由于任何原因,你还是喜欢传统的编程语言,您可能需要学习一下编译为 JavaScript 的语言。例如,TypeScript,它是 JavaScript 的一个超集,添加(可选)静态类型 和从其他传统的 OOP 语言中借鉴的模式。

您将在项目中使用上述方法之一吗?你有没有找到更好的方法?欢迎评论!

原文链接:https://www.sitepoint.com/patterns-object-inheritance-javascript-es2015/

【注:本文源自网络文章资源,由站长整理发布】


web 前端中文站 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:JavaScript ES2015 中对象继承的模式
喜欢 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址