发布于 2026-01-06 0 阅读
0

JavaScript 中真的还需要类吗?

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 月,我发布了该库的一个主要版本。您在上方看到的这段代码就是文档中的一个完整示例!这一切的实现得益于多种理念的结合运用,例如属性描述符、工厂模式、属性转换以及带有变更检测功能的缓存机制。

但是,标题提出的问题呢?我的想法是答案吗?时间会证明一切。现在,我很乐意与您探讨这个话题💡。

GitHub 标志 hybridsjs / hybrids

卓越的 JavaScript UI 框架,具有独特的声明式和函数式架构

杂交种

构建状态 覆盖状态 npm 版本

一个卓越的 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