JavaScript 中真的还需要类吗?
ES2015 引入了类语法,这是众多优秀特性之一。有些人认为它是面向对象编程中缺失的一环;而另一些人则认为我们一开始就不应该引入类。然而,类已经赢得了库作者和用户的青睐,如今,几乎所有 JavaScript 库或框架中都能找到它们的身影。
这些类是否实现了它们所承诺的目标? 三年后,我可以肯定地说,除了语法更简洁(不再使用函数构造器和原型)之外,它们在很多方面都失败了。让我们一起来探讨一下其中的一些主要缺陷。
class MyComponent extends CoolComponent {
constructor ( one , two ) {
// Use super() always before calling `this`
// and don't forget to pass arguments 🤭
super ( one , two );
this . foo = ' bar ' ;
}
update (... args ) {
this . value = ' ... ' ;
// Does CoolComponent include update method or not? 🧐
super . update (... args );
}
}
Enter fullscreen mode
Exit fullscreen mode
类语法可能令人困惑。 库通常强制用户使用 extends关键字来调用其 API。虽然看起来很简单,但扩展类需要 super()在需要的地方调用 `__init__` 方法。为了确保我们的方法不会覆盖父类定义的内部方法,我们必须谨慎命名它们(不久之后就可以使用一个高级 #关键字来创建 私有字段 )。
超级函数调用也可能比较棘手——例如,你不能 this在调用 `.` 之前在构造函数中使用 super()`.`。哦,还有,别忘了传递构造函数参数。如果你定义了 `.` constructor方法,则必须手动传递参数。
当然,我们可以习惯。我们也确实习惯了。但这并不意味着这样做是对的。
class MyComponent extends CoolComponent {
constructor () {
...
// Change onClick method name and forget update it here 😆
this . onClick = this . onClick . bind ( this );
}
onClick () {
this . foo = ' bar ' ;
}
render () {
return < button onClick = { this . onClick } > ...</ button >;
}
}
Enter fullscreen mode
Exit fullscreen mode
类与这种语法紧密绑定 。在类中,方法 this代表类的一个实例。将方法定义传递给另一个实例并丢失上下文并非设计初衷。我知道库的作者们只是想尽可能地利用类语法,同时又想发挥创意。遗憾的是,绑定函数上下文并没有最佳解决方案。为了解决这个问题,我们将可以使用另一种新的语法—— 类字段 ,它简化了创建预先绑定到实例的方法的过程。
class MyComponent extends CoolComponent {
// this method uses current state 🤨
foo () {
this . currentValue += 1 ;
return this . currentValue ;
}
// this method depends on other instance method 👆
bar ( nextValue ) {
const value = this . foo ();
return value + nextValue ;
}
}
class OtherComponent extends MyComponent {
// Ups, this.bar() is broken now 😡
foo () {
return this . otherValue ;
}
}
Enter fullscreen mode
Exit fullscreen mode
类很难组合 。首先,有状态方法会存在问题。即使对于相同的输入(传递的参数),它们也会使用当前状态并返回不同的结果。其次,这是一个众所周知的“大猩猩-香蕉”问题。如果要重用类定义,就必须要么全部接受,要么全部拒绝。即使你知道父类包含哪些方法,它们将来也可能会发生变化,因此很容易破坏某些东西。
此外,几乎不可能从类定义中单独提取一个方法,并在另一个类中重用它。方法通常相互依赖,或者使用这种语法从类实例属性中获取值。没错,存在 mixins 模式,但它并没有提供一种简洁直接的类组合方式。如果你想了解更多,可以看看 mixwith 项目,以及 同一作者提出的 ES 提案。
有没有办法摆脱这些障碍? 尽管类带来了诸多不便,但它们无疑是当时Web开发的最佳发展方向。我们之前使用普通对象的方式并没有比类带来显著优势。正因如此,库的作者和用户毫不犹豫地转向了类。那么,是否有可能避免所有类带来的问题,同时创建一个既强大又易于使用的UI库呢?
过去两年,我一直在开发一个用于创建 Web 组件的库,我称之为 hybrids 。 顾名思义,它融合了两种理念——类和普通对象。然而,最终的解决方案并非一蹴而就。
最初,我遵循了与其他库类似的常见模式,在类的基础上构建了我的 API。尽管如此,该库的主要目标是将业务逻辑与自定义元素定义分离,并让用户避免一些类相关的问题(例如 ` extendsand` 和 super()`)。一年后,我的工作几乎完成,准备发布一个主要版本。唯一让我非常困扰的是缺少组合机制。与此同时,我开始学习函数式编程,并且非常喜欢它。那时我确信类语法是一个障碍。我尝试研究如何组合类,但所有解决方案在我看来都不够完善。
只有放弃现有的解决方案,转而创造新的方案,才能取得突破 。对我而言,这是一种定义组件方式的思维转变。所有这些问题都促使我重新启动这个过程,但这一次,我采用了完全不同的方法。我没有沿用现有的思路,而是从一份空白文件开始,尝试创建一个公共 API 示例来解决这些问题。最终,我得到了类似这样的结果:
import { html , define } from ' hybrids ' ;
function increaseCount ( host ) {
host . count += 1 ;
}
const SimpleCounter = {
count : 0 ,
render : ({ count }) => html `
<button onclick=" ${ increaseCount } ">
Count: ${ count }
</button>
` ,
};
define ( ' simple-counter ' , SimpleCounter );
Enter fullscreen mode
Exit fullscreen mode
这里既没有类也没有这种语法,只有简单值和纯函数,它们都定义在普通对象内部。此外,对象定义可以轻松组合,因为它们是独立属性的映射。自定义 define()函数会动态创建一个类,将属性定义应用到原型上,最后使用自定义元素 API 定义一个自定义元素。
起初,我认为以这种方式实现 API 是不可能的,它既要能够扩展,又要能够构建比简单计数按钮更复杂、逻辑更丰富的组件。尽管如此,我还是日复一日地尝试提出更好的想法和解决方案,力求让这一切成为可能。
辛勤付出终于有了回报。2018 年 5 月,我发布了该库的一个主要版本。您在上方看到的这段代码就是文档中的一个 完整示例 !这一切的实现得益于多种理念的结合运用,例如属性描述符、工厂模式、属性转换以及带有变更检测功能的缓存机制。
但是,标题提出的问题呢? 我的想法是答案吗?时间会证明一切。现在,我很乐意与您探讨这个话题💡。
卓越的 JavaScript UI 框架,具有独特的声明式和函数式架构
一个卓越的 JavaScript 框架,用于创建客户端 Web 应用程序、UI 组件库或具有独特混合声明式和函数式架构的单个 Web 组件。
Hybrids 提供了一套完整的功能,用于构建现代 Web 应用程序:
基于普通对象和纯函数的 组件模型
全局状态管理, 支持外部存储、离线缓存、关联关系等功能
基于视图图结构的 类应用路由
布局引擎 使 UI 布局开发速度更快
模板内容的 本地化及自动翻译
支持热模块更换, 无需任何额外配置
文档
项目文档可在 hybrids.js.org 网站上找到。
快速浏览
组件模型
它基于普通对象和纯函数 ¹ ,底层 仍然使用 Web Components API :
import { html , define } from "hybrids" ;
function increaseCount ( host ) {
host . count += 1 ;
}
export default define ( {
tag : …
Enter fullscreen mode
Exit fullscreen mode
想了解更多吗? 在接下来的文章中,我将详细解释混合库的所有核心概念。现在,我建议您先访问 项目主页 和 官方文档 。
您还可以观看我在 2018 年 10 月 ConFrontJS 大会上发表的题为 “用函数式 Web 组件品味未来” 的演讲,我在演讲中解释了我是如何产生这些想法的。
🙏 如何支持本项目? 请给 GitHub 代码库点个赞⭐️,在下方评论⬇️,并将混合动力汽车的资讯传播到世界各地📢!
👋 欢迎来到 dev.to 社区! 我叫 Dominik,这是我写的第一篇博客文章——欢迎任何形式的反馈❤️。
封面照片由 Zach Lucero 拍摄,来自 Unsplash。
文章来源:https://dev.to/smalluban/do-we-really-need-classes-in-javascript-after-all-91n