深潜mobi
Typescript中的继承如何在幕后起作用?
![](https://i-blog.csdnimg.cn/blog_migrate/851ea7f0793bcf5df5faea23160d4f26.png)
Typescript和JavaScript都是令人印象深刻的语言。 Typescript对广泛的开发人员来说更加熟悉,因为它提供了我们通常在传统编程语言中遇到的概念。 继承就是其中之一。
但是TypeScript不会在浏览器中运行。 我们需要将其编译为JavaScript 。 主要是ES5,它不包含类或扩展关键字。
那么,这如何工作? Typescript使用语法糖来“模仿”类和继承行为。 它给这些概念带来一种错觉。 让我们看看我的意思。
在这篇博文中,我们将深入探讨。 在我们的路上,我们将遇到很多概念。 重要的是要围绕这些概念。 花些时间,如有必要,请稍作休息。 您不必一口气就能理解所有内容。
让我们建立一个新的TypeScript项目,以说明幕后发生的事情。 要创建一个新的空Typescript项目,让我们运行以下命令:
tsc --init
这将为我们建立一个tsconfig.json
,TypeScript编译器将使用它来编译我们的代码。 当前,它什么也不会做,因为我们还没有编写任何代码。 让我们继续添加一个index.ts:
![](https://i-blog.csdnimg.cn/blog_migrate/4d3a2a8f03782d425db45f0bbea3fe34.png)
Foo
类充当Bar
派生的基类。 Bar
接受名称和喜欢的食物作为构造函数参数。 它通过对Foo
的超级调用传递名称,并将最喜欢的food参数分配给私有财产。
然后我们实例化bar1
和bar2
并打招呼和交谈。 Bar
之所以只能打招呼,是因为它扩展了Foo
。 这里没什么好看的。
上面的代码显示了我在一开始所谈论的语法糖。 它允许我们以一种经典的方式来编写继承,而这种继承是从其他语言(如Java或C#)中使用的。
但是,这如何工作? 让我们运行tsc
命令看看“ unsugared”版本。
![](https://i-blog.csdnimg.cn/blog_migrate/93e774caa0f896848fa11b5c426eafe9.png)
不要花太多时间来研究此代码-我们将在以后介绍。
哇! 那是很多额外的代码。 乍一看,我们可以看到所有的糖都消失了-不再分类。 但是至少我们认识到我们之前写过的一些东西。
我们仍然可以看到Foo和Bar现在变成了“ iffe's”。 IFFE代表“立即调用的函数表达式” 。 顾名思义,它表示立即执行的功能😉
但是,这里添加了很多额外的东西。 __extends
方法的目的是什么?
为了理解该代码,我们首先退后一步,用普通的旧JavaScript编写相同的功能。 回到根源! ❤️
原始JavaScript中的继承
![](https://i-blog.csdnimg.cn/blog_migrate/18bc66ef81355770b44d29df75d92511.png)
此代码与Typescript版本相同。 即使代码看起来很简单,也有很多事情要理解。 重要的是我们要获得其背后的思想观念。
为了使这些概念形象化,我们将使用其“您不知道JS”系列中使用的相同图形表示形式Kyle Simpson( Kyle )。
顺便说说。 如果您还没有阅读它们,我强烈建议您阅读。 他们都是伟大的!
凯尔(Kyle)在有关“此原型和对象原型”的书中用圆表示函数,用方形表示对象。 因此,让我们看看如何以这种图形表示形式来表示上面的JS代码。
👀 在Twitter或媒体上关注我,以获取有关最新博客文章和有趣的前端内容的通知!
- 当第一行执行时,它将创建一个函数
Foo
。 注意,它还会创建一个对象,该对象是Foo
函数的原型。
function Foo(name) {
this .name = name;
}
![](https://i-blog.csdnimg.cn/blog_migrate/7f6308b559d9f26ec5cc84531ec68204.png)
函数Foo
包含.prototype
对象的.prototype
链接。 如链接所示,该对象充当Foo的原型。 但是.constructor
属性呢?
不要被它的名字弄糊涂了。 它与建筑无关。 这只是从原型到功能的内部联系。
2.让我们转到第5行。在这一行中,我们将greet函数添加到原型中。
Foo.prototype.greet = function () {
console.log(`Hi I am ${ this .name}`);
};
![](https://i-blog.csdnimg.cn/blog_migrate/f65ba8daeddb3c7cfb05182ec440996c.png)
3.然后,第9行创建Bar
函数:
function Bar(name, favouriteFood) {
this .favouriteFood = favouriteFood;
Foo.call( this , name);
}
![](https://i-blog.csdnimg.cn/blog_migrate/4f3f8076c1bbff3b99cfcbf1b6e18453.png)
4.第14行是使事情变得有趣的地方。
Bar.prototype = Object.create(Foo.prototype);
自ES5以来, Object.create
是一种存在的方法。 它创建一个全新的对象并将其链接到我们作为参数传递的原型。 换句话说,我们创建了一个与Foo原型链接的全新对象。
然后,我们将该对象分配给Bar的原型。 结果如下图所示。
![](https://i-blog.csdnimg.cn/blog_migrate/05b48f7fca32c2b1cd00941960730302.png)
注意, Bar.prototype
和Foo.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}!`);
};
![](https://i-blog.csdnimg.cn/blog_migrate/6b800d33f476a60ea8d36925d2b89d3a.png)
6.好。 凉。 我们创建了整个概念,但是当前代码没有执行任何操作。 因此,让我们创建对象并在其上调用一些方法。
var bar1 = new Bar('bar one');
var bar2 = new Bar('bar two');
![](https://i-blog.csdnimg.cn/blog_migrate/d42256b0a6d82bc722e017bd5ead33dd.png)
现在图片已完成👨🎨(实际上还有更多内容在进行中,但这超出了本文的范围)
单击此处发布有关此文章的推文🐥
bar1
和bar2
也链接到talk
。 由于有了这些链接,我们现在有了正确的原型链,可以在bar1
和bar2
上greet
和talk
了。
现在,我们清楚地了解了原始JS代码的作用以及代码背后的心理概念。
很酷,但是Typescript代码呢?
是的,此博客文章的目标是深入研究Typescript继承。 我们将在一秒钟内这样做。 到目前为止,我们所做的事情只是更好地理解下一部分的必要步骤。 现在我们准备就绪,让我们开始潜水吧! 🐠
![](https://i-blog.csdnimg.cn/blog_migrate/93e774caa0f896848fa11b5c426eafe9.png)
让我们从仔细看15到36行开始。我们的Typescript类消失了,几乎变成了我们之前编写的ES5代码。
Foo
和Bar
现在包装在IIFE(立即调用的函数表达式)中。 IIFE允许我们将基础对象捕获为变量。
如果将其与编写的普通JS代码进行比较,可以看到Object.create
方法已消失。 相反,我们现在看到__extends
方法,该方法在Bar
内部调用。 了解这种方法很重要,因为它是“继承”魔术的原因。 因此,让我们仔细看看。
__扩展
我们将__extends
方法分为两部分。 首先,我们看一下extendStatics
方法,然后转到返回的函数。
extendStatics
![](https://i-blog.csdnimg.cn/blog_migrate/6164a8c3799d1abff2efb57ebd18e9eb.png)
extendStatics
是一个带有两个参数d
和b
调用的函数。 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
功能。
它检查基类上是否存在属性。 如果是这样,此属性将被复制到派生类。 一般说来,它将基类的静态成员复制到子类。
好。 这是容易的部分。 让我们看一看较难的部分。 我们可以完成这个! 💪
重建原型继承
![](https://i-blog.csdnimg.cn/blog_migrate/b49b32646e806986c433d1088ab5a250.png)
这部分非常棘手,可能需要阅读一些才能理解。
很明显,我们跳过第一行。 我们称上述的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
等于Foo
, d
等于Bar
。 到我们输入该函数时, b
为Foo
并因此被定义,这导致else部分的执行。
为了使事物可视化,我将再次使用功能的符号为圆,将对象的符号为正方形。 在我们输入函数时,已经定义了b
和d
。
![](https://i-blog.csdnimg.cn/blog_migrate/3f67e74ad23c7e970678c8c05e852613.png)
接下来,我们执行以下行:
function __() { this.constructor = d }
![](https://i-blog.csdnimg.cn/blog_migrate/4925eded8cf82c186d2b09b1c972e236.png)
这将导致另一个函数__
及其原型。 我们还获得了已经介绍的.constructor
和.prototype
链接。
然后,将调用重构代码的else语句内的第一行。
__.prototype = b.prototype;
![](https://i-blog.csdnimg.cn/blog_migrate/ce3786095d79173789eec92f61310835.png)
我们将__.prototype
的链接更改为b.prototype
。
大! 差不多了。 仅剩一行。 要了解最后一行,还需要一个先决条件。 我们需要了解new关键字对函数调用的影响。
JavaScript中的new关键字不同于传统语言中的new关键字。 在JavaScript中,构造函数只是使用新关键字调用的函数。
每个常规函数都可以使用new
关键字来调用。 当您使用new
关键字调用函数时,会发生四件事。
- 创建了一个全新的对象。
- 该对象链接了
[[Prototype]]
[[Prototype]]。 - 对象被设置为此绑定。
- 对象被返回。
在最后一行,我们用新的__
d.prototype = new __();
![](https://i-blog.csdnimg.cn/blog_migrate/fa4c6258b59d4dabf324dcf1e63aecea.png)
通过在__
上调用new,我们凭空创建了一个全新的对象。 然后将该对象链接到__
函数的原型,即Foo.prototpye
。 然后,我们将该对象分配给Bar.prototype
。
哇,看起来对ES5图表相当熟悉,不是吗? 但是谈话方法发生了什么? 不应该在Bar
的原型上吗?
它不在Bar
上的原因很简单。 尚未添加。
![](https://i-blog.csdnimg.cn/blog_migrate/0ae52570f8d6b515ebbc419eaaaa8492.png)
我们可以看到在执行__
extend方法之后添加了Bar.prototype
get。 最后,我们创建bar1
和bar2
。 完整的图如下所示:
![](https://i-blog.csdnimg.cn/blog_migrate/d42256b0a6d82bc722e017bd5ead33dd.png)
您可能已经注意到,它与普通的ES5图相同。
结论
Typescript是一种出色的语言,与Javascript相比,它为我们提供了很多好处。 尽管如此,我已经看到太多使用TypeScript但不了解JavaScript的前端开发人员。
永远记住,在运行时执行的是JavaScript。 因此,我认为,对JavaScript具有扎实的理解至关重要。
🙏顺便说一句,如果您喜欢这篇文章,请单击左侧的👏🏻拍击👏🏻按钮。
拍手帮助其他人找到它并鼓励我写更多帖子posts
随时查看我的其他一些博客:
翻译自: https://hackernoon.com/typescript-inheritance-deep-dive-9a53989af5a6
深潜mobi