【大前端】js装饰器的10年难产之路

  js装饰器最初是在ECMAScript2015(ES6)规范中提出的一个Stage 1的提案,为了应对不断增加的元编程功能的需求,希望能够更方便地操作类和对象的行为,同时减少重复代码和简化复杂功能的实现。

装饰器是一种结构设计模式,通过将对象置于包含行为的特殊包装器对象中,可以将新的行为附加到对象上。

  从2015年的首次提出至今近10年依然处于Stage 3阶段,依然还没有成为规范的一部分,那么js装饰器这10年间到底经历了什么变迁!

以下内容翻译自 ECMAScript Decorators. The Ones That are Real

原文作者:Vladyslav Zubko

2014年4月 - 阶段0

  装饰器是由Yehuda Katz提出的,最初打算成为ECMAScript2016(ES7)的一部分。

type Decorator = (
  target: DecoratedClass,
  propertyKey: string,
  descriptor: PropertyDescriptor
) => PropertyDescriptor | void

function debounce(delay: number): PropertyDescriptor {
  return (target, propertyKey, descriptor) => {
    let lastTimeout: number
    const method = descriptor.value

    descriptor.value = (...args: unknown[]) => {
      clearInterval(lastTimeout)

      lastTimeout = setTimeout(() => method.call(null, ...args), delay)
    }

    return descriptor
  }
}

  在这个阶段,你已经可以看到装饰器API为什么会在后来经历如此重大的变化之一。装饰器的第一个参数是整个类,即使你只是装饰其中的一个成员。此外,它假定开发人员可以改变这个类。JavaScript引擎总是努力尽可能地进行优化,在这种情况下,开发人员对整个类的改变削弱了引擎提供的大量优化。后来,我们会看到,这确实是装饰器API多次重写的一个重要原因,几乎是从头开始。

2015-03 – 阶段1

  在没有重大变化的情况下,该提案进入了第二阶段。然而,发生了一件显著影响该提案进一步发展的事件:TypeScript1.5发布了,它支持装饰器。尽管装饰器被标记为实验性的(–experimentalDecorators),像AngularMobX这样的项目开始积极地使用它们。此外,这些项目的整体工作流程假定专门使用装饰器。由于这些项目的流行,许多开发人员错误地认为装饰器已经成为官方JS标准的一部分。

  这为TC39委员会带来了额外的挑战,因为他们不得不考虑开发者社区的期望和要求以及语言引擎中的优化问题。

2016-07 – 阶段2

  在装饰器提案达到第二阶段后,其API开始经历重大变化。此外,该提案曾一度被称为“JavaScriptESnext类特性”。在其开发过程中,有许多关于装饰器应该如何结构化的想法。

type Decorator = (args: {
  kind: 'method' | 'property' | 'field',
  key: string | symbol,
  isStatic: boolean,
  descriptor: PropertyDescriptor
}) => {
  kind: 'method' | 'property' | 'field',
  key: string | symbol,
  isStatic: boolean,
  descriptor: PropertyDescriptor,
  extras: unknown[]
}

  在第二阶段结束时,装饰器API的形式如下所示:

type Decorator = (
  value: DecoratedValue,
  context: {
    kind: 'class' | 'method' | 'getter' | 'setter' | 'field' | 'accessor',
    name: string | symbol,
    access?: {
      get?: () => unknown,
      set?: (value: unknown) => void
    },
    private?: boolean,
    static?: boolean,
    addInitializer?: (initializer: () => void) => void
  }
) => UpdatedDecoratedValue | void

function debounce(delay: number): UpdatedDecoratedValue {
  return (value, context) => {
    let lastTimeout = null

    return (...args) => {
      clearInterval(lastTimeout)

      lastTimeout = setTimeout(() => value.call(null, ...args), delay)
    }
  }
}

  第二阶段历时6年,期间装饰器API经历了重大变化。然而,正如我们从上面的代码中可以看到的,变异被排除在外。这使得该提案对于JS引擎以及各种平台、框架和库更加可接受。但装饰器的发展历史并未结束。

2022年3月 - 阶段3

  经过多年的变化和完善,装饰器终于达到了第三阶段。在第二阶段的广泛调整和完善的基础上,第三阶段开始时并没有出现重大变化。一个特别的亮点是创建了一个名为装饰器元数据的新提案。

2022年8月 - SpiderMonkey Newsletter

  Firefox使用的浏览器引擎SpiderMonkey成为第一个开始致力于实现装饰器的引擎。这样的实现表明该提案已准备好成为规范的完整组成部分。

2022年9月 - Babel7.19.0

  第3阶段装饰器。在编译器中添加支持对于任何提案来说都是非常重要的更新。大多数提案在其标准化计划中都有类似的项目,装饰器提案也不例外。

2022-11 – 宣布TypeScript4.9

  ECMAScript装饰器已列在TS4.9迭代计划中。 然而,一段时间后,TS团队决定将装饰器移至5.0版本。 以下是作者的评论:

虽然装饰器已经进入第3阶段,但我们发现规范中的一些行为需要与支持者讨论。在解决这个问题和审查更改之间,我们预计装饰器将在下一个版本中实现。

  总体而言,这个决定是有道理的,因为他们不想冒险过早地将某个功能纳入TS,尤其是如果它没有成为标准的一部分。这种情况总是有可能发生。尽管在这种情况下,它可能没有第一次实现那么重要。

  在TS4.9中,仅包含了装饰器规范的一小部分-类自动访问器。装饰器规范的这一补充是对实施初期普遍存在的变异的修正。其背后的原因是人们通常希望使属性具有响应性,这意味着当属性发生变化时应该发生某些效果,例如UI重新渲染,例如:

class Dashboard extends HTMLElement {
  @reactive
  tab = DashboardTab.USERS
}

  在旧实现中,使用reactive装饰器时,您必须通过添加额外的访问器来改变类set以get实现所需的行为。使用自动访问器后,此行为现在会更明确地发生,从而允许引擎更好地对其进行优化。

  另一件有趣的事情是装饰器应该如何工作。由于TS团队无法删除在--experimentalDecorators标志下工作的旧实现,他们决定采用以下方法:如果--experimentalDecorators配置中存在该标志,则将使用旧实现。如果不存在此标志,则将使用新实现。

2023-03 – 发布TypeScript5.0

  按照承诺,TS团队TS5.0中发布了装饰器规范的完整版本。

2023-03 – Deno1.32

  尽管在Deno1.32版本中支持TS5.0,但他们决定推迟与装饰器相关的功能。

请注意,ES装饰器尚不受支持,但我们将努力在未来版本中默认启用它们。

2023-05 – Angular v16现已发布

  Angular 16还增加了对ECMAScript装饰器的支持。然而,一些其他围绕装饰器构建的框架表示他们暂时不会对ECMAScript装饰器做出更改。对于其中许多框架来说,两个重要方面是元数据参数装饰器

Kamil Mysliwiec(NextJS的创建者)认为,在实现元数据支持和参数装饰器之前,我们不会支持 JS 装饰器。

2023-08 – 宣布TypeScript5.2

  在TS5.2中,添加了另一项补充装饰器规范的标准–装饰器元数据。该提案背后的主要思想是简化装饰器对使用它们的类元数据的访问。关于语法和用法存在如此多争论的另一个原因是作者必须为此目的创建一个完全独立的提案。

总结

  装饰器提案经过了长达10年的漫长考虑,这确实看起来是一段很长的时间。确实,领先的框架和库早期采用装饰器在发现初始实现的缺点方面发挥了作用。然而,这种早期采用也是一种宝贵的学习经验,凸显了与网络平台协调的重要性,以及开发一种与平台和开发者社区相一致的解决方案的重要性,同时保留装饰器的本质。花在完善提案上的时间最终有助于使其成为JavaScript语言中更强大、更周到的补充。

  装饰器将对我们今天编写应用程序的方式带来重大改变。也许不会立即发生,因为当前规范主要关注类,但随着所有新增功能和正在进行的工作,许多应用程序中的JavaScript代码很快就会看起来不同。我们现在比以往任何时候都更接近最终在规范中看到那些真正的装饰器的那一刻。这是一个令人兴奋的发展,有望增强JavaScript应用程序的表现力和功能。

上一篇:大数据实验4-HBase


下一篇:Vue 项目中引入 iconfont 图标