ES6类可变替代品

win*_*ter 477 javascript class ecmascript-6

目前在ES5中,我们很多人在框架中使用以下模式来创建类和类变量,这很舒服:

// ES 5
FrameWork.Class({

    variable: 'string',
    variable2: true,

    init: function(){

    },

    addItem: function(){

    }

});
Run Code Online (Sandbox Code Playgroud)

在ES6中,您可以本机创建类,但没有选项可以使用类变量:

// ES6
class MyClass {
    const MY_CONST = 'string'; // <-- this is not possible in ES6
    constructor(){
        this.MY_CONST;
    }
}
Run Code Online (Sandbox Code Playgroud)

遗憾的是,上面的代码不起作用,因为类只能包含方法.

我知道我可以this.myVar = trueconstructor...但我不想"垃圾"我的构造,特别是当我有一个更大的类20-30 +参数.

我正在考虑处理这个问题的许多方法,但还没有找到任何好的方法.(例如:创建一个ClassConfig处理程序,并传递一个parameter与该类分开声明的对象.然后处理程序将附加到该类.我正在考虑WeakMaps以某种方式集成.)

你有什么样的想法来处理这种情况?

Ben*_*aum 497

2018年更新:

现在有一个第3阶段的提案 - 我期待在几个月内让这个答案过时.

在此期间,任何使用TypeScript或babel的人都可以使用以下语法:

varName = value
Run Code Online (Sandbox Code Playgroud)

在类声明/表达式主体内部,它将定义一个变量.希望在几个月/几周后,我将能够发布更新.


ES维基中关于ES6中提案的注释(最小最小类)注意:

(故意)没有直接声明方式来定义原型数据属性(方法除外)类属性或实例属性

需要在声明之外创建类属性和原型数据属性.

在类定义中指定的属性被赋予与它们出现在对象文字中相同的属性.

这意味着您要求的内容被考虑,并明确决定反对.

但为什么?

好问题.TC39的优秀人员希望类声明声明和定义类的功能.不是它的成员.ES6类声明定义了其用户的合同.

请记住,类定义定义了原型方法 - 在原型上定义变量通常不是你做的事情.你当然可以使用:

constructor(){
    this.foo = bar
}
Run Code Online (Sandbox Code Playgroud)

在像你建议的构造函数中.另见共识摘要.

ES7及以后

正在开发一个关于ES7的新提议,它允许通过类声明和表达式提供更简洁的实例变量 - https://esdiscuss.org/topic/es7-property-initializers

  • 也许TC39上的优秀人才应该将这个概念命名为"类"以外的东西,如果他们不希望它的行为像编程世界的其他人所期望的那样"类". (607认同)
  • 哎呀,脑屁.我忘记了`static`也适用于方法. (8认同)
  • 另请参阅"类字段和静态属性"提议(已在[Babel](http://babeljs.io/)中实现:https://github.com/jeffmo/es-class-fields-and-static-properties (7认同)
  • 你可能想提一下`static`属性 (5认同)
  • @wintercounter从中获取的重要一点是,允许定义属性会在_the prototype_上定义它们,就像方法一样,而不是在每个实例上定义它们.最小的类仍然是其核心原型继承.在您的情况下,您真正​​想要做的是共享结构并为每个实例分配成员.这根本不是ES6中的目标 - 共享功能.所以是的,对于共享结构,你必须坚持旧的语法.直到ES7至少:) (4认同)
  • 静态属性不是类的基本属性 - 许多语言都没有它们 - Python就是一个.不管怎样,每当Python做出这样简单的决定时,每个人都会欢呼并喜欢它,但是当JS做出同样的决定时,每个人都很生气,这与其他语言不完全相同.我猜这是世界上使用最广泛的语言,它带来了一些问题. (3认同)
  • @BenjaminGruenbaum Python类可以_absolutely_有类变量(即附加到_class本身的变量,而不是它的实例).Python甚至(在选择加入的基础上)更复杂的机制来定义元类.我想问的是,JS成为最常用的语言,因为它在正确的时间出现在正确的位置,但由于它是匆忙设计的,因此遭受了近20年的批评.直到最近才有经过深思熟虑的考虑设计才能将JS增加到一种更人性化的语言. (2认同)
  • 似乎"来自TC39的好人"既不知道接口也不知道OOP是什么. (2认同)
  • @Yuriy你怎么看?请注意,_current_状态类似于Python(在构造函数中定义的字段)。接口是关于功能(而不是数据)的-通过在对象上定义方法(在原型上定义它们)来完全支持它们(作为一个概念)。我在这里看不到与OOP概念的任何不兼容。 (2认同)
  • “在原型上定义变量通常不是你要做的事情”-任何人都可以解释为什么会这样吗? (2认同)

lys*_*ing 126

只是为了添加Benjamin的答案 - 类变量是可能的,但你不会prototype用来设置它们.

对于真正的类变量,您需要执行以下操作:

class MyClass {}
MyClass.foo = 'bar';
Run Code Online (Sandbox Code Playgroud)

从类方法中可以将变量作为this.constructor.foo(或MyClass.foo)访问.

通常无法从类实例访问这些类属性.即MyClass.foo给人'bar'new MyClass().fooundefined

如果您还想从实例访问您的类变量,则还必须另外定义一个getter:

class MyClass {
    get foo() {
        return this.constructor.foo;
    }
}

MyClass.foo = 'bar';
Run Code Online (Sandbox Code Playgroud)

我只用Traceur对它进行了测试,但我相信它在标准实现中也会有相同的效果.

JavaScript实际上没有类.即使使用ES6,我们也在寻找基于对象或原型的语言而不是基于类的语言.在任何function X () {},X.prototype.constructor回到点X.使用new运算符时X,将创建一个继承的新对象X.prototype.从那里查找该新对象(包括)中的任何未定义属性constructor.我们可以将其视为生成对象和类属性.

  • 您的积分(2)和(3)不正确.没有复制属性,并且``new`调用没有创建`.constructor`属性. (4认同)
  • 试试`var x = new X; x.hasOwnProperty("constructor")// false`.`.constructor`属性简单地继承自`X.prototype`(在创建构造函数时默认设置它); `new`运算符确实与它无关. (3认同)

Kos*_*ika 25

Babel支持ESNext中的类变量,请查看以下示例:

class Foo {
  bar = 2
  static iha = 'string'
}

const foo = new Foo();
console.log(foo.bar, foo.iha, Foo.bar, Foo.iha);
// 2, undefined, undefined, 'string'
Run Code Online (Sandbox Code Playgroud)

  • 您可以将 *bar* 设为 [private](https://tc39.github.io/proposal-class-fields/) 类变量,方法是用“#”伪装它,如下所示:#bar = 2; (2认同)

Ole*_*zko 24

在你的例子中:

class MyClass {
    const MY_CONST = 'string';
    constructor(){
        this.MY_CONST;
    }
}
Run Code Online (Sandbox Code Playgroud)

由于MY_CONST是原始的https://developer.mozilla.org/en-US/docs/Glossary/Primitive,我们可以这样做:

class MyClass {
    static get MY_CONST() {
        return 'string';
    }
    get MY_CONST() {
        return this.constructor.MY_CONST;
    }
    constructor() {
        alert(this.MY_CONST === this.constructor.MY_CONST);
    }
}
alert(MyClass.MY_CONST);
new MyClass

// alert: string ; true
Run Code Online (Sandbox Code Playgroud)

但是如果MY_CONST是类似static get MY_CONST() {return ['string'];}警报输出的引用类型是字符串,则为false.在这种情况下,delete操作员可以做到这一点:

class MyClass {
    static get MY_CONST() {
        delete MyClass.MY_CONST;
        return MyClass.MY_CONST = 'string';
    }
    get MY_CONST() {
        return this.constructor.MY_CONST;
    }
    constructor() {
        alert(this.MY_CONST === this.constructor.MY_CONST);
    }
}
alert(MyClass.MY_CONST);
new MyClass

// alert: string ; true
Run Code Online (Sandbox Code Playgroud)

最后对于类变量不是const:

class MyClass {
    static get MY_CONST() {
        delete MyClass.MY_CONST;
        return MyClass.MY_CONST = 'string';
    }
    static set U_YIN_YANG(value) {
      delete MyClass.MY_CONST;
      MyClass.MY_CONST = value;
    }
    get MY_CONST() {
        return this.constructor.MY_CONST;
    }
    set MY_CONST(value) {
        this.constructor.MY_CONST = value;
    }
    constructor() {
        alert(this.MY_CONST === this.constructor.MY_CONST);
    }
}
alert(MyClass.MY_CONST);
new MyClass
// alert: string, true
MyClass.MY_CONST = ['string, 42']
alert(MyClass.MY_CONST);
new MyClass
// alert: string, 42 ; true
Run Code Online (Sandbox Code Playgroud)

  • 这是一个黑客,它很可怕,声明常量作为方法的返回值......令人震惊. (13认同)
  • 如果出于性能原因,请单独使用`delete`运算符.你真正想要的是`Object.defineProperty`. (5认同)

Jim*_*nny 17

由于您的问题主要是风格(不想用一堆声明来填充构造函数),因此它也可以在风格上得到解决.

我查看它的方式,许多基于类的语言使构造函数成为以类名本身命名的函数.在风格上我们可以使用它来创建一个风格上仍然有意义的ES6类,但不会将构造函数中发生的典型操作与我们正在进行的所有属性声明分组.我们只是使用实际的JS构造函数作为"声明区域",然后创建一个名为function的类,否则我们将其视为"其他构造函数"区域,在真正的构造函数的末尾调用它.

"use strict";

class MyClass
{
    // only declare your properties and then call this.ClassName(); from here
    constructor(){
        this.prop1 = 'blah 1';
        this.prop2 = 'blah 2';
        this.prop3 = 'blah 3';
        this.MyClass();
    }

    // all sorts of other "constructor" stuff, no longer jumbled with declarations
    MyClass() {
        doWhatever();
    }
}

两者都将在构造新实例时被调用.

Sorta就像有两个构造函数,你可以将声明和你想要采取的其他构造函数分开,并且在风格上让人不难理解这也是正在发生的事情.

我发现在处理大量声明和/或需要在实例化时发生的许多动作并且希望保持这两个想法彼此不同时,使用它是一种很好的风格.


注意:我非常有目的地不使用"初始化"(例如init()initialize()方法)的典型惯用思想,因为它们通常以不同的方式使用.构造和初始化的想法之间存在一种假定的差异.与构造函数一起工作的人知道它们是作为实例化的一部分自动调用的.看到一种init方法很多人会毫不犹豫地假设他们需要做一些形式的事情var mc = MyClass(); mc.init();,因为这就是你通常初始化的方式.我并不想增加一个初始化过程之类的用户,我想添加类本身的建设进程.

虽然有些人可能会花一点时间,但这实际上有点重要:它向他们传达意图是构造的一部分,即使这使得他们做了一点双重并且去了"那不是ES6构造函数如何工作"并且第二次看实际的构造函数去"哦,他们在底部调用它,我看到",这比不传达那个意图(或者错误地传达它)要好得多,并且可能会得到很多人们使用它错了,试图从外面和垃圾中初始化它.这对我建议的模式非常有意.


对于那些不想遵循这种模式的人来说,恰恰相反也可以.在最开始将声明置于另一个函数中.可能将其命名为"properties"或"publicProperties"或其他东西.然后将其余的东西放在普通的构造函数中.

"use strict";

class MyClass
{
    properties() {
        this.prop1 = 'blah 1';
        this.prop2 = 'blah 2';
        this.prop3 = 'blah 3';
    }

    constructor() {
        this.properties();
        doWhatever();
    }
}

请注意,第二种方法可能看起来更干净,但它也有一个固有的问题,properties当使用此方法的一个类扩展另一个时,它会被覆盖.您必须提供更多唯一名称properties以避免这种情况.我的第一个方法没有这个问题,因为它的假构造函数的一半是在类之后唯一命名的.


zar*_*one 14

那些oldschool的方式呢?

class MyClass {
     constructor(count){ 
          this.countVar = 1 + count;
     }
}
MyClass.prototype.foo = "foo";
MyClass.prototype.countVar = 0;

// ... 

var o1 = new MyClass(2); o2 = new MyClass(3);
o1.foo = "newFoo";

console.log( o1.foo,o2.foo);
console.log( o1.countVar,o2.countVar);
Run Code Online (Sandbox Code Playgroud)

在构造函数中,您只提到那些必须计算的变量.我喜欢这个功能的原型继承 - 它可以帮助节省大量内存(如果有很多未分配的变量).

  • 这不会节省内存,只会使性能比在构造函数中声明它们更糟糕.http://codereview.stackexchange.com/a/28360/9258 (4认同)
  • @Kokodoko真正的oop - 你的意思是,就像在Java中?我同意,我注意到很多人对JS很生气,因为他们像Java一样习惯使用它,因为他们习惯了,因为JS语法看起来很相似,并且他们认为它的工作方式相同...但是从在内部,我们知道它是完全不同的. (4认同)
  • 此外,这首先打败了使用ES6类的目的.就目前而言,似乎几乎不可能像真正的oop类那样使用ES6类,这有点令人失望...... (2认同)
  • 这不仅仅是关于语言的语法.OOP理念非常适合编程 - 特别是在创建应用程序和游戏时.JS传统上是在构建网页时实现的,这是完全不同的.不知何故,这两种方法需要结合在一起,因为网络也越来越多地关注应用程序. (2认同)

Bon*_*Oak 13

正如本杰明在回答中所说,TC39明确决定不至少在ES2015中包含此功能.然而,共识似乎是他们将在ES2016中添加它.

语法尚未确定,但ES2016 的初步建议允许您在类上声明静态属性.

感谢babel的魔力,你今天可以使用它.根据这些说明启用类属性转换,你很高兴.这是一个语法示例:

class foo {
  static myProp = 'bar'
  someFunction() {
    console.log(this.myProp)
  }
}
Run Code Online (Sandbox Code Playgroud)

此提案处于非常早期的状态,因此随着时间的推移,请准备好调整语法.


Wil*_*een 10

ES7 类成员语法:

ES7有一个“垃圾化”构造函数的解决方案。下面是一个例子:

class Car {
  
  wheels = 4;
  weight = 100;

}

const car = new Car();
console.log(car.wheels, car.weight);
Run Code Online (Sandbox Code Playgroud)

上面的示例将如下所示ES6

class Car {

  constructor() {
    this.wheels = 4;
    this.weight = 100;
  }

}

const car = new Car();
console.log(car.wheels, car.weight);
Run Code Online (Sandbox Code Playgroud)

使用此语法时请注意,并非所有浏览器都支持此语法,并且可能必须将其转译为早期版本的 JS。

奖励:对象工厂:

function generateCar(wheels, weight) {

  class Car {

    constructor() {}

    wheels = wheels;
    weight = weight;

  }

  return new Car();

}


const car1 = generateCar(4, 50);
const car2 = generateCar(6, 100);

console.log(car1.wheels, car1.weight);
console.log(car2.wheels, car2.weight);
Run Code Online (Sandbox Code Playgroud)

  • 您已经完成了实例变量,而不是类变量。 (4认同)

Her*_*ess 9

[长线程,不确定它是否已作为选项列出...].
一个简单的替代方案是在类外定义const.除非附带吸气剂,否则只能从模块本身访问.
这种方式prototype不会乱七八糟,你得到了const.

// will be accessible only from the module itself
const MY_CONST = 'string'; 
class MyClass {

    // optional, if external access is desired
    static get MY_CONST(){return MY_CONST;}

    // access example
    static someMethod(){
        console.log(MY_CONST);
    }
}
Run Code Online (Sandbox Code Playgroud)


Rus*_*lan 5

你可以模仿es6类的行为......并使用你的类变量:)

看妈妈......没有课!

// Helper
const $constructor = Symbol();
const $extends = (parent, child) =>
  Object.assign(Object.create(parent), child);
const $new = (object, ...args) => {
  let instance = Object.create(object);
  instance[$constructor].call(instance, ...args);
  return instance;
}
const $super = (parent, context, ...args) => {
  parent[$constructor].call(context, ...args)
}
// class
var Foo = {
  classVariable: true,

  // constructor
  [$constructor](who){
    this.me = who;
    this.species = 'fufel';
  },

  // methods
  identify(){
    return 'I am ' + this.me;
  }
}

// class extends Foo
var Bar = $extends(Foo, {

  // constructor
  [$constructor](who){
    $super(Foo, this, who);
    this.subtype = 'barashek';
  },

  // methods
  speak(){
    console.log('Hello, ' + this.identify());
  },
  bark(num){
    console.log('Woof');
  }
});

var a1 = $new(Foo, 'a1');
var b1 = $new(Bar, 'b1');
console.log(a1, b1);
console.log('b1.classVariable', b1.classVariable);
Run Code Online (Sandbox Code Playgroud)

我把它放在GitHub上