【问题标题】:How to make ES6 class final (non-subclassible)如何使 ES6 类最终(不可子类化)
【发布时间】:2016-12-09 10:54:25
【问题描述】:

假设我们有:

class FinalClass {
  ...
}

如何修改来制作

class WrongClass extends FinalClass {
  ...
}

new WrongClass(...)

产生异常?也许最明显的解决方案是在 FinalClass 的构造函数中执行以下操作:

if (this.constructor !== FinalClass) {
    throw new Error('Subclassing is not allowed');
}

有没有人有更简洁的解决方案,而不是在每个应该是最终的类中重复这些行(可能使用装饰器)?

【问题讨论】:

  • 有趣的问题。您尝试这样做有什么原因吗?
  • 由于最终这一切都被编译成 Javascript (
  • 更强烈地表达@gcampbell 所暗示的观点,这让我觉得这是一个坏主意,近乎可怕。例如,Java 中的字符串在很大程度上很糟糕,因为该类缺少许多有用的方法,并且被finaled 视为过早的性能黑客。
  • @deceze 如果我们谈论的是 ES 5+,那么可以使用 Object.freezeObject.defineProperty 等,即使需要递归(以“深度冻结”)。我只是不知道为什么你会,一般来说,想要对一个 class 而不是性能这样做,它必须是一个地狱般的性能改进。
  • @gcampbell 关于是否使用类和继承的争论由来已久。我或多或少同意以下和解的声明:类应该被设计为可扩展的(用文档描述它应该如何完成)或最终的,以防止继承的类与其父类发生令人惊讶和不可预测的交互。我喜欢 ES6 类语法,并想找到一种方法来继续使用它来定义不可扩展的类,而不是像对象文字 + 工厂这样的模式。

标签: javascript ecmascript-6 ecmascript-next


【解决方案1】:

检查FinalClass的构造函数中的this.constructor,如果不是自己就抛出。 (借用@Patrick Roberts 对this.constructor 而非this.constructor.name 的检查。)

class FinalClass {
  constructor () {
    if (this.constructor !== FinalClass) {
      throw new Error('Subclassing is not allowed')
    }
    console.log('Hooray!')
  }
}

class WrongClass extends FinalClass {}

new FinalClass() //=> Hooray!

new WrongClass() //=> Uncaught Error: Subclassing is not allowed

或者,在支持下,使用new.target。谢谢@loganfsmyth。

class FinalClass {
  constructor () {
    if (new.target !== FinalClass) {
      throw new Error('Subclassing is not allowed')
    }
    console.log('Hooray!')
  }
}

class WrongClass extends FinalClass {}

new FinalClass() //=> Hooray!

new WrongClass() //=> Uncaught Error: Subclassing is not allowed

______

正如您所说,您也可以使用装饰器来实现此行为。

function final () {
  return (target) => class {
    constructor () {
      if (this.constructor !== target) {
        throw new Error('Subclassing is not allowed')
      }
    }
  }
}

const Final = final(class A {})()

class B extends Final {}

new B() //=> Uncaught Error: Subclassing is not allowed

正如 Patrick Roberts 在 cmets 中分享的那样,装饰器语法 @final 仍在提议中。它适用于 Babel 和 babel-plugin-transform-decorators-legacy

【讨论】:

  • 或者更好的是,如果您不进行转译并且支持它,请执行new.target !== target,这样可以避免this.constructor 可能被重新分配的极端情况。
  • @loganfsmyth new.target 也不是万无一失的,例如Reflect.construct(WrongClass, [], FinalClass)
【解决方案2】:

constructor.name 很容易被欺骗。只需将子类与超类同名即可:

class FinalClass {
  constructor () {
    if (this.constructor.name !== 'FinalClass') {
      throw new Error('Subclassing is not allowed')
    }
    console.log('Hooray!')
  }
}

const OopsClass = FinalClass

;(function () {
  class FinalClass extends OopsClass {}

  const WrongClass = FinalClass

  new OopsClass //=> Hooray!

  new WrongClass //=> Hooray!
}())

最好检查constructor 本身:

class FinalClass {
  constructor () {
    if (this.constructor !== FinalClass) {
      throw new Error('Subclassing is not allowed')
    }
    console.log('Hooray!')
  }
}

const OopsClass = FinalClass

;(function () {
  class FinalClass extends OopsClass {}

  const WrongClass = FinalClass

  new OopsClass //=> Hooray!

  new WrongClass //=> Uncaught Error: Subclassing is not allowed
}())

【讨论】:

  • //=> Hooray! :-)
  • 虽然这在技术上是正确的,但new 中的分号错误和缺少括号并不是真正应该鼓励的做法。如果您重新排列其中的任何一个,两者都会使代码模棱两可并且很容易被破坏。
  • @ssube new 缺少括号很烦人(new Foo.bar()new Foo().bar()),但没有分号的样式我懒得纠正; (双关语)它用于大型项目(例如 npm),并且在 JS 中有 33.00000000000004 其他需要抱怨的事情。 (随之而来的火焰战争)
  • 这个也很容易绕过:class WrongClass extends FinalClass {} WrongClass.prototype.constructor = FinalClass;
  • @Oriol 当然,但是您没有使用扩展的构造函数进行调用,这似乎没有什么好处。此外,像这样的安全 JavaScript(例如私有变量、final 类)目前还无法实现。
猜你喜欢
  • 2017-01-03
  • 2019-05-15
  • 2018-03-24
  • 1970-01-01
  • 2016-07-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多