529

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

// ES 5
FrameWork.Class({

    variable: 'string',
    variable2: true,

    init: function(){

    },

    addItem: function(){

    }

});

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

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

可悲的是,上述方法不起作用,因为类只能包含方法。

我知道我可以......但我不想“垃圾”我的构造函数,尤其是当我有 20-30 多个参数用于更大的类时this.myVar = trueconstructor

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

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

4

17 回答 17

542

2018年更新:

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

与此同时,任何使用 TypeScript 或 babel 的人都可以使用以下语法:

varName = value

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

更新:Chrome 74 现在可以使用此语法。


ES wiki 中关于 ES6 提案的注释(最大最小类)注释:

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

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

类定义中指定的属性被分配了相同的属性,就好像它们出现在对象字面量中一样。

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

但为什么?

好问题。TC39 的好人希望类声明能够声明和定义类的功能。不是它的成员。一个 ES6 类声明为它的用户定义了它的契约。

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

constructor(){
    this.foo = bar
}

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

ES7 及更高版本

正在制定 ES7 的新提案,通过类声明和表达式允许更简洁的实例变量 - https://esdiscuss.org/topic/es7-property-initializers

于 2014-04-10T11:20:03.470 回答
137

只是为了补充本杰明的答案——类变量是可能的,但你不会prototype用来设置它们。

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

class MyClass {}
MyClass.foo = 'bar';

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

这些类属性通常不能从类实例访问。即MyClass.foo给出'bar'但是new MyClass().foo_undefined

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

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

MyClass.foo = 'bar';

我只用 Traceur 对此进行了测试,但我相信它在标准实现中也能正常工作。

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

于 2014-07-30T11:51:31.870 回答
31

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'
于 2016-02-15T14:41:38.547 回答
31

在您的示例中:

class MyClass {
    const MY_CONST = 'string';
    constructor(){
        this.MY_CONST;
    }
}

由于 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

但是如果MY_CONSTstatic 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

最后对于类变量 not 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
于 2015-10-15T07:35:21.213 回答
19

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

在我看来,许多基于类的语言的构造函数都是以类名本身命名的函数。从风格上讲,我们可以使用它来制作一个风格上仍然有意义的 ES6 类,但不会将构造函数中发生的典型动作与我们正在做的所有属性声明组合在一起。我们简单地将实际的 JS 构造函数用作“声明区”,然后创建一个名为函数的类,否则我们将其视为“其他构造函数”区域,在真正的构造函数的末尾调用它。

“使用严格”;

我的班级
{
    // 只声明你的属性然后调用 this.ClassName(); 从这里
    构造函数(){
        this.prop1 = '废话 1';
        this.prop2 = '废话 2';
        this.prop3 = '废话 3';
        this.MyClass();
    }

    // 各种其他“构造函数”的东西,不再与声明混为一谈
    我的课() {
        随便();
    }
}

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

有点像拥有 2 个构造函数,您可以在其中将声明和您想要执行的其他构造函数操作分开,并且从风格上来说,这也使您不难理解正在发生的事情。

我发现在处理大量声明和/或需要在实例化时发生的大量动作并希望使这两个想法彼此不同时使用它是一种不错的风格。


注意:我非常有目的地不使用“初始化”的典型惯用想法(如init()orinitialize()方法),因为它们的使用方式通常不同。构造和初始化的想法之间存在一种假定的差异。使用构造函数的人都知道,它们是作为实例化的一部分自动调用的。看到一种init方法,许多人会不假思索地认为他们需要按照 的形式做一些事情var mc = MyClass(); mc.init();,因为这就是您通常初始化的方式。我不是要为类的用户添加初始化过程,而是要添加类本身的构造过程中。

虽然有些人可能会做一时的双重考虑,但这实际上是重点:它向他们传达意图是构建的一部分,即使这让他们做了一些双重考虑然后去“那不是ES6 构造函数是如何工作的”,然后再看看实际的构造函数去“哦,他们在底部调用它,我明白了”,这比不传达那个意图(或错误传达它)要好得多,而且可能会得到很多人们使用它错误,试图从外部初始化它和垃圾。这对我建议的模式非常有意。


对于那些不想遵循这种模式的人来说,完全相反的方法也可以。在开始时将声明移植到另一个函数。也许将其命名为“properties”或“publicProperties”之类的。然后把剩下的东西放在普通的构造函数中。

“使用严格”;

我的班级
{
    特性() {
        this.prop1 = '废话 1';
        this.prop2 = '废话 2';
        this.prop3 = '废话 3';
    }

    构造函数(){
        this.properties();
        随便();
    }
}

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

于 2015-12-22T07:21:05.397 回答
14

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

语法尚未确定,但 ES2016 有一个初步提案,允许您在类上声明静态属性。

感谢 babel 的魔力,你今天可以使用它。根据这些说明启用类属性转换,一切顺利。下面是一个语法示例:

class foo {
  static myProp = 'bar'
  someFunction() {
    console.log(this.myProp)
  }
}

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

于 2015-12-16T21:36:41.983 回答
13

老派的方式呢?

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);

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

于 2014-04-10T12:33:32.427 回答
11

[长线程,不确定它是否已列为选项...]。仅用于contsants
的一个简单替代方法是在类之外定义 const。这只能从模块本身访问,除非带有 getter。 这种方式不会乱扔垃圾,你会得到.
prototypeconst

// 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);
    }
}
于 2018-02-21T07:35:45.133 回答
10

ES7类成员语法:

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

class Car {
  
  wheels = 4;
  weight = 100;

}

const car = new Car();
console.log(car.wheels, car.weight);

上面的示例将如下所示ES6

class Car {

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

}

const car = new Car();
console.log(car.wheels, car.weight);

请注意,使用此语法时,可能并非所有浏览器都支持此语法,并且可能必须将其转换为早期版本的 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);

于 2018-11-20T18:28:11.377 回答
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);

我把它放在GitHub 上

于 2017-05-29T02:14:20.403 回答
1

如果它只是杂乱无章的问题,constructor为什么不实现变量的initialize方法。intializes当构造函数装满不必要的东西时,这是很正常的事情。即使在像C#其正常约定那样的类型化程序语言中,也可以添加一种Initialize方法来处理它。

于 2020-09-01T09:15:32.837 回答
1

您仍然不能像在其他编程语言中那样声明任何类。但是您可以创建尽可能多的类变量。但问题是类对象的范围。所以据我说,在 ES6 Javascript 中进行 OOP 编程的最佳方式:-

class foo{
   constructor(){
     //decalre your all variables
     this.MY_CONST = 3.14;
     this.x = 5;
     this.y = 7;
     // or call another method to declare more variables outside from constructor.
     // now create method level object reference and public level property
     this.MySelf = this;
     // you can also use var modifier rather than property but that is not working good
     let self = this.MySelf;
     //code ......... 
   }
   set MySelf(v){
      this.mySelf = v;
   }
   get MySelf(v){
      return this.mySelf;
   }
   myMethod(cd){
      // now use as object reference it in any method of class
      let self = this.MySelf;
      // now use self as object reference in code
   }
}
于 2020-04-18T03:51:06.017 回答
0

只需定义一个getter

class MyClass
{
  get MY_CONST () { return 'string'; }

  constructor ()
  {
    console.log ("MyClass MY_CONST:", this.MY_CONST);
  }
}

var obj = new MyClass();

于 2020-12-14T14:30:15.897 回答
0

截至 2021 年的最新浏览器(不是 IE,请参阅 MDN 浏览器图表)实现了公共类字段,这似乎是您正在寻找的内容:

class MyClass {
  static foo = 3;
}

console.log(MyClass.foo);

但是显然不可能做到这一点constDeclaring static constants in ES6 classes?

静态吸气剂看起来非常接近:

class MyClass {
  static get CONST() {
    return 3;
  }
}

MyClass.CONST = 4; // property unaffected
console.log(MyClass.CONST);

于 2021-08-17T03:51:34.953 回答
0

好吧,您可以在构造函数中声明变量。

class Foo {
    constructor() {
        var name = "foo"
        this.method = function() {
            return name
        }
    }
}

var foo = new Foo()

foo.method()
于 2018-06-21T08:17:02.063 回答
0

我解决这个问题的方法是另一种选择(如果您有可用的 jQuery),是在老式对象中定义字段,然后使用该对象扩展类。我也不想给构造函数加上赋值,这似乎是一个很好的解决方案。

function MyClassFields(){
    this.createdAt = new Date();
}

MyClassFields.prototype = {
    id : '',
    type : '',
    title : '',
    createdAt : null,
};

class MyClass {
    constructor() {
        $.extend(this,new MyClassFields());
    }
};

- 更新以下 Bergi 的评论。

没有 JQuery 版本:

class SavedSearch  {
    constructor() {
        Object.assign(this,{
            id : '',
            type : '',
            title : '',
            createdAt: new Date(),
        });

    }
}

您最终仍然会使用“胖”构造函数,但至少它全部在一个类中并一键分配。

编辑#2: 我现在已经完成了一个完整的循环,现在正在构造函数中分配值,例如

class SavedSearch  {
    constructor() {
        this.id = '';
        this.type = '';
        this.title = '';
        this.createdAt = new Date();
    }
}

为什么?真的很简单,使用上面加上一些 JSdoc 注释,PHPStorm 能够对属性执行代码完成。一次性分配所有变量很好,但是无法编写完整的属性,imo,不值得(几乎可以肯定是微不足道的)性能优势。

于 2017-05-10T22:13:03.627 回答
-1

这是一个有点骇人听闻的静态组合,并为我工作

class ConstantThingy{
        static get NO_REENTER__INIT() {
            if(ConstantThingy._NO_REENTER__INIT== null){
                ConstantThingy._NO_REENTER__INIT = new ConstantThingy(false,true);
            }
            return ConstantThingy._NO_REENTER__INIT;
        }
}

在别处使用

var conf = ConstantThingy.NO_REENTER__INIT;
if(conf.init)...
于 2015-09-12T02:57:49.280 回答