【第780期】你不懂JS:ES6 `class`

图片

前言前端

很久不见了,你不懂JS系列又跟你们见面了,今天这篇是this与对象原型的最后一篇,继续由前端早读课专栏做者@JoeHetfield带来的翻译分享。设计模式


正文从这开始~框架


若是说本书后半部分(第四至六章)有什么关键信息,那就是类是一种代码的可选设计模式(不是必要的),并且用像JavaScript这样的[[Prototype]]语言来实现它老是很尴尬。ide


虽然这种尴尬很大一部分关于语法,但 不只 限于此。第四第五章审视了至关多的难看语法,从使代码杂乱的.prototype引用的繁冗,到 显式假想多态:当你在链条的不一样层级上给方法相同的命名以试图实现从低层方法到高层方法的多态引用。.constructor被错误地解释为“被XX构建”,这成为了一个不可靠的定义,也成为了另外一个难看的语法。函数


但关于类的设计的问题要深入多了。第四章指出在传统的面向类语言中,类实际上发生了从父类向子类,由子类向实例的 拷贝 动做,而在[[Prototype]]中,动做 不是 一个拷贝,而是相反——一个委托连接。工具


OLOO风格和行为委托接受了[[Prototype]],而不是将它隐藏起来,当比较它们的简单性时,类在JS中的问题就凸显出来。性能


classthis

咱们 没必要 再次争论这些问题。我在这里简单地重提这些问题仅仅是为了使它们在你的头脑里保持新鲜,以使咱们将注意力转向ES6的class机制。咱们将在这里展现它如何工做,而且看看class是否实质上解决了任何这些“类”的问题。编码


让咱们重温第六章的Widget/Button例子:spa

image.png

除了语法上 看起来 更好,ES6还解决了什么?


再也不有(某种意义上的,继续往下看!)指向.prototype的引用来弄乱代码。

Button被声明为直接“继承自”(也就是extends)Widget,而不是须要用Object.create(..)来替换.prototype连接的对象,或者用__proto__和Object.setPrototypeOf(..)来设置它。


super(..)如今给了咱们很是有用的 相对多态 的能力,因此在链条上某一个层级上的任何方法,能够引用链条上相对上一层的同名方法。第四章中有一个关于构造器的奇怪现象:构造器不属于它们的类,并且所以与类没有联系。super(..)含有一个对此问题的解决方法 —— super()会在构造器内部想正如你指望的那样工做。

class字面语法对指定属性没有什么启发(仅对方法有)。这看起来限制了某些东西,可是绝大多数状况下指望一个属性(状态)存在于链条末端的“实例”之外的地方,这一般是一个错误和使人诧异(由于这个状态被隐含地在全部“实例”中“分享”)的。因此,也能够说class语法防止你出现错误。


extends甚至容许你用很是天然的方式扩展内建的对象(子)类型,好比Array或者RegExp。在没有class .. extends的状况下这样作一直以来是一个极端复杂而使人沮丧的任务,只有最熟练的框架做者曾经正确地解决过这个问题。如今,它是小菜一碟!


凭心而论,对大多数明显的(语法上的)问题,和经典的原型风格代码令人诧异的地方,这些确实是实质上的解决方案。


class的坑

然而,它不全是优势。在JS中将“类”做为一种设计模式,仍然有一些深入和很是使人烦恼的问题。


首先,class语法可能会说服你JS在ES6中存在一个新的“类”机制。但不是这样。class很大程度上仅仅是一个既存的[[Prototype]](委托)机制的语法糖!


这意味着class实际上不是像传统面向类语言那样,在声明时静态地拷贝定义。若是你在“父类”上更改/替换了一个方法(有意或无心地),子“类”和/或实例将会受到“影响”,由于它们在声明时没有获得一份拷贝,它们依然都使用那个基于[[Prototype]]的实时委托模型。

图片


这种行为只有在 你已经知道了 关于委托的性质,而不是期待从“真的类”中 拷贝 时,才看起来合理。那么你要问本身的问题是,为何你为了根本上就和类不一样的东西选择class语法?


ES6的class语法不是使观察和理解传统的类和委托对象间的不一样 变得更困难 了吗?


class语法 没有 提供声明类的属性成员的方法(仅对方法有)。因此若是你须要跟踪对象间分享的状态,那么你最终会回到丑陋的.prototype语法,像这样:

图片


这里最大的问题是,因为它将.prototype做为实现细节暴露(泄露!)出来,而背叛了class语法的初衷。


并且,咱们还依然面临着那个使人诧异的陷阱:this.count++将会隐含地在c1和c2两个对象上建立一个分离的遮蔽属性.count,而不是更新共享的状态。class没有在这个问题上给咱们什么安慰,除了(大概是)经过缺乏语法支持来暗示你 根本 就不该该这么作。


另外,无心地遮蔽依然是个灾难:

image.png

还有一些关于super如何工做的微妙问题。你可能会假设super将会以一种相似与this获得绑定的方式(间第二章)来被绑定,也就是super老是会绑定到当前方法在[[Prototype]]链中的位置的更高一层。


然而,由于性能问题(this绑定已经很耗费性能了),super不是动态绑定的。它在声明时,被有些“静态地”绑定。不是什么大事儿,对吧?


恩……多是,可能不是。若是你像大多数JS开发者那样,开始把函数赋值给不一样的(来自于class定义的)对象,以各类不一样的方式,你可能不会意识到在全部这些状况下,底层的super机制会不得不每次都从新绑定。


并且根据你每次赋值采起的语法方式不一样,颇有可能在某些状况下super不能被正确地绑定(至少不会像你指望的那样),因此你可能(在写做这里时,TC39正在讨论这个问题)会不得不用toMethod(..)来手动绑定super(有点儿像你不得不用bind(..)绑定this —— 见第二章)。


你曾经能够给不一样的对象赋予方法,来经过 隐含绑定 规则(见第二章),自动地利用this的动态性。但对于使用super的方法,一样的事情极可能不会发生。


考虑这里super应当怎样动做(对D和E):

图片


若是你(十分合理地!)认为super将会在调用时自动绑定,你可能会指望super()将会自动地认识到E委托至D,因此使用super()的E.foo()应当调用D.foo()。


不是这样。 因为实用主义的性能缘由,super不像this那样 延迟绑定(也就是动态绑定)。相反它从调用时[[HomeObject]].[[Prototype]]派生出来,而[[HomeObject]]实在声明时静态绑定的。


在这个特定的例子中,super()依然解析为P.foo(),由于方法的[[HomeObject]]仍然是C并且C.[[Prototype]]是P。


可能 会有方法手动地解决这样的陷阱。在这个场景中使用toMethod(..)来绑定/重绑定方法的[[HomeObject]](设置这个对象的[[Prototype]]一块儿!)彷佛会管用:

图片

注意: toMethod()克隆这个方法,而后将它的第一个参数做为homeObject(这就是为何咱们传入E),第二个参数(可选)用来设置新方法的name(保持“foo”不变)。


除了这种场景之外,是否还有其余的极端状况会使开发者们陷入陷阱还有待观察。不管如何,你将不得不费心保持清醒:在哪里引擎自动为你肯定super,和在哪里你不得不手动处理它。噢!


静态优于动态?

可是关于ES6的最大问题是,全部这些种种陷阱意味着class有点儿将你带入一种语法,它看起来暗示着(像传统的类那样)一旦你声明一个class,它是一个东西的静态定义(未来会实例化)。使你彻底忘记了这个事实:C是一个对象,一个你能够直接互动的具体的东西。


在传统面向类的语言中,你从不会在晚些时候调整类的定义,因此类设计模式不提供这样的能力。可是JS的 一个最强大的部分 就是它 是 动态的,并且任何对象的定义都是(除非你将它设定为不可变)不固定的可变的 东西。


class看起来在暗示你不该该作这样的事情,经过强制你使用.prototype语法才能作到,或强制你考虑super的陷阱,等等。并且它对这种动态机制可能带来的一切陷阱 几乎不 提供任何支持。


换句话说,class好像在告诉你:“动态太坏了,因此这可能不是一个好主意。这里有看似静态语法,把你的东西静态编码。”


关于JavaScript的评论是多么悲伤啊:动态太难了,让咱们伪装成(但实际上不是!)静态吧。


这些就是为何ES6的class假装成一个语法头痛症的解决方案,可是它实际上把水搅得更浑,并且更不容易对JS造成清晰简明的认识。


注意: 若是你使用.bind(..)工具制做一个硬绑定函数(见第二章),那么这个函数是不能像普通函数那样用ES6的extend扩展的。


复习

class在伪装修复JS中的类/继承设计模式的问题上作的很好。但他实际上作的却正相反:它隐藏了许多问题,并且引入了其余微妙并且危险的东西。


class为折磨了JavaScript语言将近20年的“类”的困扰作出了新的贡献。在某些方面,它问的问题比它解决的多,并且在[[Prototype]]机制的优雅和简单之上,它总体上感受像是一个很是不天然的匹配。


底线:若是ES6 class使稳健地利用[[Prototype]]变得困难,并且隐藏了JS对象机制最重要的性质 —— 对象间的实时委托连接 —— 咱们不该该认为class产生的麻烦比它解决的更多,而且将它贬低为一种反模式吗?


我真的不能帮你回答这个问题。但我但愿这本书已经在你从未经历过的深度上彻底地探索了这个问题,并且已经给出了 你本身回答这个问题 所需的信息。


最后,虽然是this与对象原型的最后一篇,可能你须要如下这些文章能更好的链接:

【第773期】你不懂JS:行为委托

【第772期】你不懂JS:原型(Prototype)

【第767期】你不懂JS:混合(淆)“类”的对象


或者,回复[你不懂js]查看@JoeHetfield专栏文章。

相关文章
相关标签/搜索