从无聊的书呆子的角度继承JavaScript:Constructors Factory

灯和不和谐的苹果 这是一个有关JavaScript的非常特殊的故事,这是当今(2019)世界上最常用的人工语言。

本文提出了JavaScript继承的一种哲学观点,我只希望它基于最令人印象深刻的知识来源:生命本身的所有表现形式。 我不知道这是不是用JavaScript创建原型链设计的灵感。

但是,如果是这样,那么它是如此重要和强大,以至于当我开始考虑它时,有时甚至变得难以呼吸...

(所有链接都带有下划线


我也确信,我们谁都不会怀疑JavaScript编程语言的作者Brendan Ike (Eich)是一个杰出的天才! 不仅因为他经常重复:
总是赌JavaScript!
让我们开始吧! 我们的第一个起点将是想象力,其中我们首先要关闭所有偏见,遗漏和其他副作用。

我们正在回到“未来 ”时代,即1990年代初期创建现代互联网之前的时代。

最早发明我们( IT员工 )现在使用的一切的黑客之时起,我们就已经有了一个令人印象深刻的草图:关于浏览器Netstcape Navigator 2和Internet Explorer 3之间的战争。Java刚刚问世,现代Internet的几乎所有东西尚未发明或没有预先开放。 像我一样,在那些“美好的旧时光”中,您还很年轻,您可能还记得这种美好的参与感,就在您眼前所产生的所有辉煌中。

因此,您在拥有32Mb RAM,Windows 3.11甚至Windows 95的最现代化的Intell Pentium 200 MMX上拥有一台功能非常强大的PC ,并且您对未来充满希望! 并且,当然,两个浏览器也都安装了。 您有一个Dial-Up ,通过它也可以连接到网络以查找新的垃圾,以学习或聊天,聊天。 虽然,嘿,您仍然无法直接在浏览器中聊天,但是很可能您正在使用延迟的邮件传递系统,例如EMailUseNet ,或者很可能已经掌握了通过IRC进行即时传递的功能。

几年过去了,几乎一切都发生了变化。...突然间,您在网页上观看了雪花的动画,祝贺您新年和圣诞节。 当然,您对实现方法感兴趣,并且发现了一种新的语言-JavaScript。 由于HTML对您而言并不陌生,因此您开始学习这种诱人的技术。 很快您就会发现CSS ,事实证明这也很重要,因为现在所有内容都是由以下三种组合而成:HTML,JavaSript和CSS。 哇

大约在同一时间,您可能会注意到Windows本身有很多很棒的功能,其中出现了CScriptHTA ,即使这样,也有可能直接在JS上创建成熟的桌面应用程序(并且仍然可以使用)。

因此,您开始制作第一个Web服务器,可能是使用Perl或C〜C ++ 。 也许甚至您开始使用类似Unix的 操作系统并在bash上进行操作 。 归功于通用网关接口 (不要将其与其他CGI混淆),所有这些操作都是“自旋旋转”的。 PHP几乎仍然不存在,但是也许您很快就会喜欢它。

200倍时代。 您现在正在使用JScript执行ASP 。 这与您的网页内的JavaScript非常相似。 太酷了! 您正在考虑创建自己的模板引擎 ,这是XML的一种模仿。 然后有人突然将AJAX称为所有这些有趣的方式来动态加载您已经使用了几年的内容。 现在所有的人都认为只有XMLHTTPRequest ,但是您还记得可以将数据传输到BMPIFrame或什至通过插入<script>标记 。 然后,当您一直在使用诸如此类的永恒数据来驱动数据时,突然有人热烈地谈论JSON及其有用性:

document.write("<" + "script src=" + path + ">"); 

现在 ,这并不重要,但是您仍然可以记住...

当您产生感觉时,您会不时发现自己会与RhinoNashorn闲逛,试图满足使用AlfrescoAsterisk的Java客户的需求。 而且您已经听说过JavaScript在微芯片领域即将到来的情况,并且受到这一消息的启发。 当然,现在有了jQueryBackbone

看着即将到来的2010年降雪,您已经知道,随着“第一玩家”这一领域的到来,游戏的所有规则都将很快改变: Node.js。 在接下来的十年中,您将在这个新玩具上度过,即使现在,在2019年,您仍然无法获得足够的酷感。

总的来说,您对所有事物都感到满意,所有适合您的事物,其中所有这些玩具和游戏构成了您生活中很大一部分的兴趣。

但是,您有一个小问题要问自己二十年来日复一日,一夜又一夜:

如果必须这样做,您将如何使用JavaScript解释“ 同理心”


您知道JavaScript中最复杂的主题之一是Prototype Inheritance和Prototype Chain 。 您喜欢这个主题,就可以解释它是如何工作和工作的,仅仅是因为您几乎是从一开始就了解到它的,甚至在标准第一个版本诞生之前就已经了解了,而您记得在哪里有4.2 .1对象
ECMAScript支持基于原型的继承。 每个构造函数都有一个关联的原型,并且该构造函数创建的每个对象都有对其与其构造函数关联的原型(称为对象的原型) 的隐式引用 。 此外,原型可能对其原型具有非null的隐式引用,依此类推;等等。 这称为原型链
每个对象都是使用对原型的隐式引用创建的。 这被称为原型继承链,您可以根据需要将其继续进行。

哇...如果突然之间,像我一样,您可能会认为这是计算机科学领域最伟大的发明之一,那么您将如何表达阅读此声明对您的影响?

让我们回到开始。 在院子里1995 您是Brendan Ike,您需要发明一种新的编程语言。 也许您喜欢LispScheme ,至少喜欢它们的某些部分。 并且您必须解决继承问题,因为您必须假装该语言具有一定的OOP实现。 让我们想想 :您需要混合所有您喜欢的东西,也许还混合一些您不喜欢的东西,并且所得到的鸡尾酒应该足够好,这样在真正需要理解它之前,没有人会发现一个窍门安排在里面。

现在的问题是: 继承将发生什么?

让我们回到现实中。 我们都知道继承吗? 该问题的一些显而易见的答案:

  1. 大多数生命形式都基于基因组 。 它是有关生物的可能特征和据称行为的信息的资料库。 当您是生物时,您会携带一部分基因组,可以进行分配,但是是从前几代获得的。
  2. 您可以使用两种技术来创建生物:混合两个祖先(基因组),或者可能使用其中之一的雌雄同株的克隆 。 当然,今天我们有了可用的技术,可以混合一个以上生物的基因组,但这并不那么明显,也不是那么自然。
  3. 时间因素很重要。 如果没有或没有继承的属性,那么我们唯一的出路就是从头开始创建它。 除此之外,还有遗产,它不是通过基因组而是通过财产法则从其祖先传给生物的,这也很重要。

回到过去,现在我们需要问自己的正确问题是: 实际上,继承我们想得到什么?

嗯,无论如何,在解决继承问题的过程中,我们至少需要填补编程与现实生活之间的空白,否则,通常,我们将无权将其称为继承。

现在,让我们完成自己的工作:我们处于1995年代,我们拥有功能最强大的PC,仅具有32 MB的RAM,并且我们正试图创建一种解释型语言,因此我们必须特别注意此内存。 每条数据,特别是String对象 ,都占用大量内存,并且我们应该只能声明一次,并且在将来可能的情况下,始终仅使用指向该部分所占内存区域的指针。 我们总结:一个非常困难的问题。

有一种流行的观点:“ JavaScript是根据对象创建的 。” 使用这种琐碎性,我们可以很容易地回答“ 从什么 ”继承和“ 什么 ”的问题:从对象到对象。 为了节省内存,事实证明所有数据都必须存储在这些对象中,并且所有这些数据链接也必须存储在这些对象的Inherited属性中。 也许现在很清楚,为什么在1995年我们真的需要基于原型链创建设计:它可以尽可能长地节省内存! 总的来说,我认为这仍然是一个非常重要的方面。

基于指示的设计和“ 一切都是对象 ”的见解,我们可以尝试克隆类似的东西。 但是现在在这里克隆什么呢? 我认为,按照我们要求的要求,我们可以采用类似于Modern Object.assign的前身之类的结构或表面复制之类的方法
让我们在1995年使用for(var i in){}实现一个简单的结构副本,因为该标准已允许这样做

 // back in 1995 cloning // it is not deep clone, // though we might not need deep at all var cloneProps = function (clonedObject, destinationObject) { for (var key in clonedObject) { destinationObject[key] = clonedObject[key]; } }; 

如您所见,这种方法仍然可以“起作用”,尽管总的来说,当然,我建议您查看深度扩展模块,以更详细地了解如何在JavaScript中进行克隆,但是就本文而言,一致的应用程序非常适合我们描述为cloneProps ,因为我们可以在远古时代很好地使用它:

  • 使用构造函数克隆对象: 使用构造函数创建至少两个不同的克隆

     // cloneProps is described above var SomeConstructor = function (clonedObject) { cloneProps(clonedObject, this); }; var someExistingObjectToClone = { foo : 'bar' }; var clone1 = new SomeConstructor(someExistingObjectToClone); var clone2 = new SomeConstructor(someExistingObjectToClone); // clone1.foo == clone2.foo 
  • 从构造函数克隆一个构造函数:我们实现了从另一个构造函数使用一个构造函数的行为

     var SomeConstructor = function () { this.a = 'cloned'; }; var AnotherConstructor = function () { // Function.prototype.call // was already invented in 1st ECMA-262 SomeConstructor.call(this); }; 
  • 使用对象克隆构造函数: 我们将使用同一对象在至少两个构造函数中实现克隆

     var existentObject = { foo : 'bar' }; var SomeConstructor = function () { cloneProps(foo, this); }; var OtherConstructor = function () { cloneProps(foo, this); }; 
  • 从另一个对象克隆一个对象: 使用一个对象创建其多个克隆 。 这里没有什么可以描述的,就像上面第一个示例中的cloneProps一样

总体而言,使用克隆,一切都很简单,正如我们所看到的,总体而言一切都很清晰,但是...

对我们来说,使用它们的前辈组合进行实体继承是否那么容易?

  • 使用构造函数继承对象: 这是设计人员的目的,我们将仅展示其最初的设计方式

     var existentObject = { foo : 'bar' }; var SomeConstructor = function () {}; SomeConstructor.prototype = existentObject; var inheritedObject = new SomeConstructor(); // we have no instanceof yet in ECMA 262 of 1995 // therefore we are unable to rely on this window.alert(inheritedObject.foo); // bar 
  • 构造函数从另一个构造函数的继承:毫无疑问,第一个注意到此构造函数的人是杰出的天才。 总而言之,这是到处都有的另一个经典例子

     var FirstConstructor = function () { this.foo = 'bar'; }; var InheritedConstructor = function () { FirstConstructor.call(this); }; InheritedConstructor.prototype = { bar : 'foo' }; InheritedConstructor.prototype.constructor = FirstConstructor; var inherited = new InheritedConstructor(); // { foo : 'bar', bar : 'foo' } 

    可以陈述一些复杂的东西,但是为什么
  • 从对象继承构造函数:再次,我们每次需要时都使用.prototype = object ,没有什么可描述的,我们总是需要分配Constructor.prototype,因为它应该将对象放在那里,并通过隐式链接获得其所有属性。
  • 从对象继承对象:一样。 我们只需将第一个对象放入Constructor.prototype中 ,一旦我们说新的Constructor,我们将创建一个继承的副本,其中将隐式引用第一个对象的属性。

当然,在所有这些具有继承的情况下,我们将有机会使用我们从中创建对象的构造方法的instanceof进行检查,尽管当然,应该注意的是, instanceof本身在将近四年后才出现在标准中。

的确,第4.2.1段保留了这么小的细节:
能够在必要的时间内执行此操作,如下所示:
依此类推
好吧, 让我们尝试使用1995年 以来的技术使继承真正永无止境

实际上,让我们想象一下,我们有两个实体, 不是构造函数,而是简单的对象。 我们想从另一个继承,然后从另一个继承,再从另一个继承,依此类推...

但是如何?


进一步深入研究。
这里的正确答案再次是: 我们需要继承什么?

毕竟,我们不需要自己的这些实体。 我们需要它们的属性:关联的数据消耗内存; 而且,我们可能需要一些行为:使用此数据的方法。 如果我们有机会检查继承的地点和地点以及使用的内容,这也将是诚实的 。 总的来说,如果将来可以重现继承模式的固有设计,那将很酷,这意味着如果我们多次继承一个继承模式,根据我们写的内容(合同),我们将始终得到相同的结果。 。 尽管它仍然存在,但它对于我们以某种方式修复创作时刻同样有用,因为我们的“先前”实体可以随着时间而变化,而“继承人”在这方面给予他们尊重,但仍然随着它们而改变可能不太想。

而且,由于我们所有的代码都是数据和行为的组合,因此在设计继承系统时,使用相同的方法(将数据和表示形式相结合)通常正常吗?

对我而言,所有这些都类似于我们以各种难以置信的形式观察生命时所看到的。 从第一个单细胞到多细胞及其后代,再到动物,人,人文主义和部落,文明,智力及其人工形式,再到太空,银河系,再到星星! 和:
“ ...我们要做的就是确保我们一直在说话...”
(我们要做的就是继续沟通)

史蒂芬·霍金Stephen Hawking)的话语深思熟虑,后来在此平德 ·弗洛伊德(Pind Floyd)杰作中广为传播

基于消息传递的编程语言和通过健壮的内部API实现的基于流的概念,使您可以从简单的数据转移到更高的抽象,描述和其他所有内容。 我认为这是纯粹的艺术,它是如何通过原型链中数据之间的隐式关系在深隐藏的JavaScript结构中特别起作用的。

再次想象一下两个祖先,他们交流,并在一瞬间他们的情感和感情造就了一个孩子。 这个孩子长大,遇到另一个孩子,他们交流,下一个后代出现,并不断地……我们总是需要两个父母,否则这不是自然的,那已经是基因工程了。 第二,不多也不少。 后代获得的遗产与他们的遗产相同,因此简单易懂。

我知道这听起来很奇怪,但是是的,在1995年,我们拥有创建继承模型所需的一切。 而所有这些的基础正是4.2.1对象 ,即通过原型的隐式引用。

就是这样,通过指定.prototypeParentObjectParentConstructor结合起来,然后,如果我们说出神奇的单词“ new ”,那么Constructor可能会创建一个ChildObject

 var ParentObject = { foo : 'bar' }; var ParentConstructor = function () {}; ParentConstructor.prototype = ParentObject; var ChildObject = new ParentConstructor(); // starting from 1995 and then ECMA 262 // we are able to say new // each time we need a ChildObject 

我们可以在这里辨认我们的两个祖先。 当我们说出神奇的单词“ new ”时,我们要求他们聊天。 如果他们不想交流,生命将停止,进程将因错误而失败, 编译器 (解释器)将告诉我们。

当然,是的,但是我们确实要求继承树,或者让它明显地简单得多,至少对于族谱树而言。 答案仍然是相同的……我们的子对象长大,并成为父对象 ,然后遇到新的构造函数对象 ,一旦我们说出令人垂涎的单词“ ”,即魔术:

 // this Assignment is just to show it grew up var ChildObjectGrownToParent = ChildObject; var AnotherConstructor = function () {}; AnotherConstructor.prototype = ChildObjectGrownToParent; var SequentialChildObject = new AnotherConstructor(); // checking Life Cycle ;^) console.log(ChildObject instanceof ParentConstructor); // true console.log(SequentialChildObject instanceof ParentConstructor); // true console.log(SequentialChildObject instanceof AnotherConstructor); // true 

而且我们可以继续做这则广告。 我也许真的相信这是开发原型链设计时的主要思想,因为众所周知,这种方法会产生一些非常整洁但同样令人不愉快的问题 ……

1:社区...正如您可以轻松进行检查一样,在.prototype中指定ParentConstructor'aAnotherConstructor'a是我们部落中非常严格的社会契约 。 它为继承人创建对ParentObject.foo )属性的引用: ChildObjectSequentialChildObject 。 而且,如果我们摆脱了这种迹象,那么这些链接将消失。 如果我们进行设计并将此引用重新分配给其他对象,则我们的继承人将立即继承其他属性。 因此,通过.prototype组合祖先,我们可能会说我们正在建立某种社会细胞 ,因为每次我们使用new询问这些“后代”时,它们都可以繁殖出许多相同的后代。 因此,摧毁了这个“家庭”,我们正在破坏他的后代的遗传特质,例如一部戏剧; ^)

也许所有这些都是关于代码中的遗留问题,在创建安全受支持的代码时,我们应该注意这一点! 当然,在1995年没有讨论SOLIDLiskov 合同 原则设计以及GRASP ,但是很明显,这些方法并不是“从头开始”创建的,而是早于所有方法。

2:家庭...我们可以轻松地验证我们的ParentObject与其他Constructos 相比是轻浮的。 这是不公平的,但是我们可以在ParentObject的继承中使用任意数量的构造方法,从而创建任意数量的族。 另一方面,每个构造函数都通过.prototype任务与ParentObject紧密关联,并且如果我们不希望损害继承人,则我们必须保持尽可能长的联系。 在我们部落的历史中,我们可以称其为悲剧艺术。 尽管这当然可以保护我们免受失忆症的影响-遗忘了我们继承的财产以及从谁那里遗忘的遗忘,以及我们的继承人为何获得这样的遗产 。 而且,赞美伟大的Mnemosyne !,我们真的可以轻松地测试我们的原型链并找到我们做错事的像。

3: 年纪大了 ……在程序的生命周期中,我们的ParentObjectConstructor当然容易受到损坏。 我们可以尝试解决这一问题,但是没有人会犯错。 所有这些变化都可能损害继承人。 我们必须注意内存泄漏 。 当然,我们可以很好地销毁运行时中不必要的代码部分,从而释放生命周期中不再使用的内存。 此外,我们必须摆脱在原型链中创建临时悖论的所有可能性,因为实际上从其后代继承祖先非常简单。 但这已经是非常危险的,因为从未来调情过去的这种技术可能会造成难以复制的整个海森袋 ,尤其是当我们尝试测量随时间变化的东西时。

决策纪事


让它简单,明显并且不是很有用,但不要将我们的构造函数和ParentObject看作是“妈妈和爸爸”,而是将它们描述为一个鸡蛋和... 花粉 。 然后,将来,当我们使用珍贵的单词“ new ”创建Zygote时,它将不再对我们的想象力造成任何损害!

我们这样做的那一刻,我们将立即摆脱上述所有三个问题! 当然,为此,我们需要自己创建合子的能力,这意味着我们需要设计师工厂。 现在称呼它是您喜欢的,母亲,父亲,这有什么区别,因为最重要的是,如果我们要说“ new ”,那么我们必须创建一个“全新”的花艺设计师笼子,在上面放上花粉,只有这样我们才能在遥远而又多雪的2020m中生长新的“正确”的Snowdrop

 var Pollen = { season : 'Spring' }; // factory of constructors var FlowersFactory = function (proto) { var FlowerEggCell = function (sort) { this.sort = sort; }; FlowerEggCell.prototype = proto; return FlowerEggCell; }; var FlowerZygote = FlowersFactory(Pollen); var galanthus = new FlowerZygote('Galanthus'); 

( , , , -, ...)

, , . « » , , (. .: , , , bla-bla-bla )…

, , 146% , . , , (. .: , , ).

, , : JavaScript , . , , , FlowerEggCell FlowerEggCellClass FlowersFactory : , instanceof FlowerEggCell FlowerZygote . FlowerZygote , FlowersFactory , , FlowerEggCell FlowerEggCellClass " reference " .

, .prototype null this , . call (null , . apply (null . bind (null code style (. .: Sorrow , , , ).

!

!

V

Source: https://habr.com/ru/post/zh-CN470994/


All Articles