JavaScript ES2015 中对象继承的模式
栏目: JavaScript · 发布时间: 7年前
内容简介:JavaScript ES2015 中对象继承的模式
随着期待已久的ES2015(以前称为ES6)到来,JavaScript 配备了专门用于定义 类(classes) 的语法。在这篇文章中,我将探索如何利用类语法用较小的部分来组成一个类。
将层次结构的深度保持在最低限度对于保持代码整洁很重要。这有助于你了解如何巧妙的分割类。对于一个大的代码库,一个选择是用较小的部分创建类;再组合成一个大类。这也是避免重复代码的常见策略。
想像一下,我们正在构建一个游戏,玩家生活在动物世界中。有些是朋友,其他的是敌人(像狗,可能会说所有的猫都是敌人)。我们可以创建一个 HostileAnimal
类,它扩展了类 Animal
,作为 Cat
的基类。在某些时候,我们决定添加机器人来伤害人类。我们首先做的是创建 Robot
类。我们现在有两个类具有相似的属性。例如, HostileAnimal
和 Robot
都有 attack()
(攻击)方法。
如果我们可以以某种方式在一个单独的类或对象中定义一个称为 Hostile
类来表示敌对, 那么我们可以让 Cat
和 Robot
可以重复使用这个类。我们可以以各种方式做到这一点。
多重继承是传统的 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'));
这些原型对象可以在运行时创建和修改。首先,我尝试使用 Animal
和 Hostile
类:
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 在运行时定义一个类的能力。
首先,我们将需要基类。在我们的例子中, Animal
和 Robot
作为基类。如果你想从头开始,一个空类也可以工作。
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/
以上所述就是小编给大家介绍的《JavaScript ES2015 中对象继承的模式》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 028.Python面向对象继承(单继承,多继承,super,菱形继承)
- 面向对象:理解 Python 类的单继承与多继承
- Java继承(面向对象篇)
- 《JavaScript面向对象精要》之五:继承
- JS笔记(12): 对象的继承
- JavaScript 面向对象高级——继承模式
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Netty实战
诺曼·毛瑞尔(Norman Maurer)、马文·艾伦·沃尔夫泰尔(Marvin Allen Wolfthal) / 何品 / 人民邮电出版社 / 2017-5-1 / 69.00
编辑推荐 - Netty之父”Trustin Lee作序推荐 - 阿里巴巴中间件高级技术专家为本书中文版作序推荐 - 系统而详细地介绍了Netty的各个方面并附带了即用型的优质示例 - 附带行业一线公司的案例研究 - 极实用的Netty技术书 无论是构建高性能的Web、游戏服务器、推送系统、RPC框架、消息中间件还是分布式大数据处理引擎,都离不开Nett......一起来看看 《Netty实战》 这本书的介绍吧!