【问题标题】:Cannot define prototype properties within ES6 class definition无法在 ES6 类定义中定义原型属性
【发布时间】:2016-11-13 16:30:13
【问题描述】:

我在尝试 ES6 语法时发现我无法在类定义中定义原型属性或实例属性,为什么要禁止它?

我之前用的是MyClass.prototype.prop=1,下面用babel编译器试试ES7,还是不能定义prototype属性。

class MyClass{
  prop=1;
  static sProp=1;
}

我不认为定义实例属性有什么危险,我自己的浏览器游戏中有两种情况需要原型属性:

  1. 子类实例需要从基类继承相同的属性值:

    var Building=function(){...}
    Building.prototype.sight=350;
    TerranBuilding.CommandCenter=...(CommandCenter extends Building)
    TerranBuilding.Barracks=...(Barracks extends Building)
    

所以 CommandCenter 和 Barracks 的建筑视野都与 350 相同。

new CommandCenter().sight===new Barracks().sight//All buildings have same sight
  1. 缓冲区效果覆盖原始属性并删除缓冲区

    Marine.prototype.speed=20
    var unit=new Marine()
    unit.speed===20//get unit.__proto__.speed 20
    unit.speed=5//Buffer:slow down speed, unit.speed will override unit.__proto__.speed
    delete unit.speed//Remove buffer
    unit.speed===20//true, speed restore
    

所以我认为它应该添加一种设置原型属性的方法而不是完全禁止它,或者您可以提供一些其他解决方案来处理上述两种情况?

【问题讨论】:

  • 你能用getter代替吗? class MyClass{ get prop() { return 1;} }
  • @JuanMendes 为每种单位类型定义了许多属性,例如大小、hp、mp 和损坏,为所有这些属性编写 get/set 很累;对于我的案例2,有时由于缓冲区效应,我需要覆盖默认属性值,并且可以通过删除 unit.prop 来删除该缓冲区以禁止覆盖默认 unit.__proto__.prop,您的 get/set 无法以简单的方式支持这一点。
  • 在原型上具有原子值通常被认为是一种反模式。原型主要用于方法。在实例上保留原子值。
  • 这不是 ES7,这是一个提案。
  • @torazaburo 我不知道为什么它会是一种反模式。原型适用于对象实例共享的任何东西。正如我所看到的,危险在于原型上有可变对象,因为您可以对其进行变异,并且该更改将反映在所有实例上。如果您在原型上有一个原语,并且您执行this.prim = 5,这将在对象本身上设置一个值,并且不会影响原型上的值。请发布一个链接,解释为什么“它通常被认为是一种反模式”

标签: javascript ecmascript-6 babeljs


【解决方案1】:

这些都不会出现在类原型上。

class Foo { bar = 1; } 语法将为class instance 分配一个值,以便使用this.bar 访问。

class Foo { static bar = 1; } 语法将为class constructor 分配一个值,以便使用Foo.bar 访问。

在这种情况下没有太多理由使用原型。它只会使谁实际拥有该财产变得复杂,并且在几个不同的类别中分配一个数字只会产生很少的开销。

我建议使用类实例属性,只需在需要的任何地方使用this.sight

【讨论】:

  • 一个有效的用例是 OP 确实希望继承该属性,您的建议无济于事。也就是说,他们可以在对象级别覆盖它,并通过 delete obj.shadowingProperty 从原型中取回它
【解决方案2】:

在类主体中向原型添加属性的最简单方法是使用原型赋值作为虚拟静态属性的“值”:

class MyClass {
    static _dummy = MyClass.prototype.prop1 = <expression1>
    static _dummy = MyClass.prototype.prop2 = <expression2>
    // or
    static _dummy = this.prototype.prop2 = <expression2>
}

(它可以不带括号工作,因为= 是右关联的,并且可以为每个原型分配重用相同的虚拟属性)

如果您想对值进行更有趣(多行)的计算,初始化程序可以是立即执行的函数表达式,在这种情况下,您基本上已经创建了一个 static constructor,您可以将所有初始化用于其中的原型和类对象。

【讨论】:

    【解决方案3】:

    我认为其他答案没有抓住这个问题的重点。继承的全部意义在于您可以决定何时何地覆盖某些东西。如果有人认为这是一种开销,那你为什么要使用 OOP 语言呢?

    毕竟我不知道为什么它是“禁止的”,但我可以分享一些想法。 我很确定没有办法用 'class' 关键字定义 Prototype 属性。任何定义都将安装在“hasOwnProperty”上。一个巨大的挫折是在构造函数中,没有办法让任何父构造函数与被覆盖的属性进行交互。

    就推理而言,它实际上是由另一个特性驱逐的:您可以使用 表达式 将属性分配给 this

    class A extends B { sight = this.getSight() * 3 }
    

    当一个表达式原谅时,它要么使用 instance 运行 - 使用构造函数创建,要么在类声明处运行 - 当创建 prototype 时。

    访问器和方法没有这个问题。它们在原型定义时定义并在实例运行时调用

    用带有“=”的表达式定义的属性是表达式的返回值。它在定义后立即被原谅 - 应该是 实例创建时间,否则 this 无法使用。

    所以这与模式无关。这是关于有表达或有继承。我绝对更喜欢继承,当您可以将它们直接写入构造函数时,表达式是如此毫无意义。

    class A extends B { constructor() { this.sight = this.getSight() * 3 } 
    

    使用装饰器是一种很好的解决方法。你总是可以在 javascript 中对原型做一些事情:

    @B({sight:2}) class A {};
    

    装饰者 B 是:

    function(option) {return function(clazz) {clazz.prototype.sight = option.sight; return clazz}}
    

    【讨论】:

      【解决方案4】:

      以下是我在 javascript 中遵循的典型模式。原生,没有 babel 等等。

      它反映了 java 使用的静态块样式。目前有一个Stage 3 Proposal 对此开放,因此我预计它将在不久的将来标准化(与 TC-39 委员会对第 3 阶段提案的期望一致)。

      提案会是什么样子

      class MyClass {
          static {
              // Any code here is executed directly after the initialization
              // of MyClass. You can add prototype stuff here. The function
              // is called bound to `MyClass`.
          }
      }
      

      今天可以使用静态 iife 来完成

      它们的功能完全相同。

      class MyClass {
          // Using private properties is not required, it is just an option. Make
          // sure to use an arrow function so that `this` refers to `MyClass`,
          // Note that `MyClass` will still be in the functions closure.
          static #_ = (() => {
              // 'Almost' how functions are typically added. ES6 style
              // is always recommended over this.
              this.prototype.myFunc = function myFunc() {
                  console.log(":D");
              };
      
              // ES6 would actually do this (approximately) so that the function is
              // non-enumerable in the prototype.
              Reflect.defineProperty(this.prototype, "myFunc", {
                  // enumerable: false,  // defaults 'false'
                  writable: true,
                  configurable: true,
      
                  // I'm intentionally not using the shorthand for the function
                  // so that it is named 'myFunc'.
                  value: function myFunc() {
                      console.log(":D");
                  }
              });
      
              // Note that all children of MyClass will refer to this exact
              // object if put in the prototype, i.e. not a copy of it.
              // Also, this property will be non-enumerable on the children
              // (but enumerable on the prototype itself unless you
              // use `defineProperty` as above).
              this.prototype.sharedProperty = { name: "Gerald" };
          })();
      }
      

      【讨论】:

        【解决方案5】:
        class MyClass {
           constructor() {
            MyClass.prototype.prop2 = "Marry";
          }
        }
        
        const mc = new MyClass()
        mc.__proto__ // { prop2: "Marry" }
        

        【讨论】: