打字稿继承深潜

Typescript中的继承如何在幕后起作用?

Typescript和JavaScript都是令人印象深刻的语言。 Typescript对广泛的开发人员来说更加熟悉,因为它提供了我们通常在传统编程语言中遇到的概念。 继承就是其中之一。

但是TypeScript不会在浏览器中运行。 我们需要将其编译为JavaScript 。 主要是ES5,它不包含类或扩展关键字。

那么,这如何工作? Typescript使用语法糖来“模仿”类和继承行为。 它给这些概念带来一种错觉。 让我们看看我的意思。

在这篇博文中,我们将深入探讨。 在我们的路上,我们将遇到很多概念。 重要的是要把这些概念弄清楚。 花些时间,如有必要,请稍作休息。 您不必一口气就能理解所有内容。

让我们建立一个新的TypeScript项目,以说明幕后发生的事情。 要创建一个新的空的Typescript项目,我们运行以下命令:

tsc --init

这将为我们建立一个tsconfig.json ,TypeScript编译器将使用它来编译我们的代码。 当前,它什么也不会做,因为我们还没有编写任何代码。 让我们继续添加一个index.ts:

Foo类充当Bar派生的基类。 Bar接受名称和喜欢的食物作为构造函数参数。 它通过对Foo的超级调用传递名称,并将喜欢的食物参数分配给私有财产。

然后,我们实例化bar1bar2并调用greet and talk。 Bar之所以只能打招呼,是因为它扩展了Foo 。 这里没什么好看的。

上面的代码显示了我一开始所谈论的语法糖。 它允许我们以一种经典的方式来编写继承,而这种继承是从其他语言(如Java或C#)中使用的。

但是,这如何工作呢? 让我们运行tsc命令看看“ unsugared”版本。

不要花太多时间来研究此代码-我们将在以后介绍。

哇! 那是很多额外的代码。 乍一看,我们可以看到所有的糖都消失了-不再有类别。 但是至少我们认识到我们之前写过的一些东西。

我们仍然可以看到Foo和Bar现在变成了“ iffe's”。 IFFE代表“立即调用的函数表达式” 。 顾名思义,它表示立即执行的功能😉

但是,这里添加了很多额外的东西。 __extends方法的目的是什么?

为了理解此代码,我们首先退后一步,用普通的旧JavaScript编写相同的功能。 扎根! ❤️

原始JavaScript中的继承

此代码与Typescript版本相同。 即使代码看起来很简单,也有很多事情要理解。 重要的是我们要获得背后的思想观念。

为了使这些概念形象化,我们将使用其“您不知道JS”系列中使用的相同图形表示形式Kyle Simpson( Kyle )。

顺便说说。 如果您还没有阅读它们,我强烈建议您阅读。 他们都是伟大的!

凯尔(Kyle)在有关“此原型和对象原型”的书中用圆表示函数,用方形表示对象。 因此,让我们看看如何以这种图形表示形式表示上面的JS代码。

👀 在Twitter或媒体上关注我,以获取有关最新博客文章和有趣的前端内容的通知!
  1. 当第一行执行时,它将创建一个函数Foo 。 注意,它还会创建一个对象,该对象是Foo函数的原型。
 function  Foo(name) {
this .name = name;
}

函数Foo包含.prototype对象的.prototype链接。 如链接所示,该对象充当Foo的原型。 但是.constructor属性呢?

不要被它的名字弄糊涂了。 它与建筑无关。 这只是从原型到功能的内部联系。

2.让我们转到第5行。在这一行中,我们将greet函数添加到原型中。

Foo.prototype.greet = function  () {
console.log(`Hi I am ${ this .name}`);
};

3.然后,第9行创建Bar函数:

 function  Bar(name, favouriteFood) {
this .favouriteFood = favouriteFood;
Foo.call( this , name);
}

4.第14行是使事情变得有趣的地方。

Bar.prototype = Object.create(Foo.prototype);

自ES5以来, Object.create是一种存在的方法。 它创建一个全新的对象并将其链接到我们作为参数传递的原型。 换句话说,我们创建了一个与Foo原型链接的全新对象。

然后,我们将该对象分配给Bar的原型。 结果如下图所示。

注意, Bar.prototypeFoo.prototype之间的链接由[[Prototype]][[Prototype]]只是从一个对象到另一个对象的内部链接。

也许您已经听说过“原型链”? 原型链是bar实例可以调用greet函数的原因。 它允许对象将方法调用委派给其他对象。 [[Prototype]]是此链的一部分。

因此,基本上,如果有人在bar上打招呼,则会检查Bar.prototype是否存在Bar.prototype 。 如果不是,则遵循内部[[Prototype]]链接,直到找到一个问候方法或链结束。 如果链结束,则返回未定义。

尽管[[Prototype]]是一个内部链接,但也有一个称为__proto__的公共链接。 这种链接是Mozilla发明的,但从未标准化。 但是,除IE之外,每个人都采用了它。 🤪

5.目前, bar非常友好,它与Foo关联,因此可以打招呼。 但是它仍然很害羞。 第16至18行将其更改。 Bar现在将告诉您他最喜欢的食物是什么,这确实非常有用;)

Bar.prototype.talk = function  () {
console.log(`${ this .name}: I love ${ this .favouriteFood}!`);
};

6.好。 凉。 我们创建了整个概念,但是当前代码没有执行任何操作。 因此,让我们创建对象并在其上调用一些方法。

 var bar1 = new  Bar('bar one');
var bar2 = new Bar('bar two');

现在图片已完成👨‍🎨(实际上还有更多内容在进行中,但这超出了本文的范围)

单击此处发布有关此文章的推文🐥

bar1bar2也链接到talk 。 由于有了这些链接,我们现在有了正确的原型链,可以在bar1bar2greettalk了。

现在,我们清楚地了解了原始JS代码的作用以及代码背后的心理概念。

很酷,但是Typescript代码呢?

是的,此博客文章的目标是深入研究Typescript继承。 我们将在一秒钟内这样做。 到目前为止,我们所做的事情只是更好地理解下一部分的必要步骤。 现在我们准备就绪,让我们开始潜水吧! 🐠

让我们从仔细看15到36行开始。我们的Typescript类消失了,几乎变成了我们之前编写的ES5代码。

FooBar现在包装在IIFE(立即调用的函数表达式)中。 IIFE允许我们将基础对象捕获为变量。

如果将其与编写的普通JS代码进行比较,可以看到Object.create方法已消失。 相反,我们现在看到__extends方法,该方法在Bar内部调用。 了解这种方法很重要,因为它负责“继承”魔术。 因此,让我们仔细看看。

__扩展

我们将__extends方法分为两部分。 首先,我们看一下extendStatics方法,然后转到返回的函数。

extendStatics

extendStatics是一个带有两个参数db调用的函数。 d表示派生类( Bar ), b表示基类( Foo )。

首先,检查Object.setPrototypeof存在。 如果没有

({ __proto__: [] } instanceof  Array && function  (d, b) { d.__proto__ = b; })

将被应用。 如果两个检查都返回false,我们将应用第三个函数

 function  (d, b) { for  ( var  p in  b) if  (b.hasOwnProperty(p)) d[p] = b[p]; };

所有这些功能均实现相同。 由于向后兼容,因此必须进行检查。

如果不支持前两种方法,则使用最后一种方法。 它也是最易读和最好地解释了extendStatic功能。

它检查基类上是否存在属性。 如果是这样,此属性将被复制到派生类。 通常说来,它将基类的静态成员复制到子类。

好。 这是容易的部分。 让我们看一看较难的部分。 我们做得到! 💪

重建原型继承

这部分非常棘手,可能需要阅读一些内容才能理解。

很明显,我们跳过第一行。 我们称上述的extendStatics方法。

在第3行中,我们创建了一个名为__的命名函数。 我们将很快讨论它的含义。 我认为理解此代码的最佳方法是首先查看最后一行。

d.prototype = b === null  ? Object.create(b) : (__.prototype = b.prototype, new  __());

多次仔细阅读此方法。 如果您不知道此方法的用途,请不要担心。 这行代码包含很多逻辑。

为了更好地解释此方法,我更愿意将此行重构为更易读的版本。 让我们重构吧!

 if  (b === null ) {
d.prototype = Object.create(b);
} else {
__.prototype = b.prototype;
d.prototype = new __();
}

好。 众所周知, b等于Food等于Bar 。 到我们输入该函数时, bFoo并因此被定义,这将导致else部分的执行。

为了使事物可视化,我将再次使用功能的表示法作为圆,将对象的表示为正方形。 在我们输入函数时,已经定义了bd

接下来,我们执行以下行:

function __() { this.constructor = d }

这将导致另一个函数__及其原型。 我们还获得了已经介绍的.constructor.prototype链接。

然后,将调用重构代码的else语句内的第一行。

__.prototype = b.prototype;

我们将__.prototype的链接更改为b.prototype

大! 差不多好了。 仅剩一行。 要了解最后一行,还需要一个先决条件。 我们需要了解new关键字对函数调用的影响。

JavaScript中的new关键字不同于传统语言中的new关键字。 在JavaScript中,构造函数只是使用新关键字调用的函数。

每个常规函数都可以使用new关键字来调用。 当您使用new关键字调用函数时,会发生四件事。

  1. 创建了一个全新的对象。
  2. 该对象链接了[[Prototype]] [[Prototype]]。
  3. 对象被设置为此绑定。
  4. 对象被返回。

在最后一行,我们用新的__

d.prototype = new  __();

通过在__上调用new,我们凭空创建了一个全新的对象。 然后,该对象链接到__函数的原型,即Foo.prototpye 。 然后,我们将该对象分配给Bar.prototype

哇,看起来对ES5图表相当熟悉,不是吗? 但是谈话方法发生了什么? 不应该在Bar的原型上吗?

它不在Bar上的原因很简单。 尚未添加。

我们可以看到在执行__ extend方法之后添加了Bar.prototype get。 最后,我们创建bar1bar2 。 完整的图如下所示:

您可能已经注意到,它与普通的ES5图相同。

结论

Typescript是一种出色的语言,与Javascript相比,它为我们提供了很多好处。 尽管如此,我已经看到太多使用TypeScript但不了解JavaScript的前端开发人员。

永远记住,在运行时执行的是JavaScript。 因此,我认为,对JavaScript具有扎实的理解至关重要。

🙏顺便说一句,如果您喜欢这篇文章,请单击左侧的👏🏻拍击👏🏻按钮。

拍手帮助其他人找到它并鼓励我写更多帖子😜

随时查看我的其他一些博客:

From: https://hackernoon.com/typescript-inheritance-deep-dive-9a53989af5a6

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值