关于javascript:ES6类变量替代品

ES6 class variable alternatives

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ES 5
FrameWork.Class({

    variable: 'string',
    variable2: true,

    init: function(){

    },

    addItem: function(){

    }

});

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

1
2
3
4
5
6
7
// ES6
class MyClass {
    const MY_CONST = 'string'; // <-- this is not possible in ES6
    constructor(){
        this.MY_CONST;
    }
}

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

我知道我可以在constructor中使用this.myVar = true,但我不想"垃圾"我的构造函数,特别是当我有20-30+个参数用于更大的类时。

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

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


2018更新:

现在有一个第3阶段的建议-我期待在几个月内使这个答案过时。

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

1
varName = value

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

更新:Chrome74现在可以使用这种语法了。

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

There is (intentionally) no direct declarative way to define either prototype data properties (other than methods) class properties, or instance property

Class properties and prototype data properties need be created outside the declaration.

Properties specified in a class definition are assigned the same attributes as if they appeared in an object literal.

这意味着你所要求的是被考虑的,并明确决定反对的。

但是…为什么?

问得好。TC39的好人希望类声明声明声明和定义类的功能。不是它的成员。ES6类声明为其用户定义其契约。

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

1
2
3
constructor(){
    this.foo = bar
}

就像你建议的那样。另见共识概要。

ES7及其超越

正在研究ES7的一个新提议,它允许通过类声明和表达式更简洁的实例变量-https://esdiscus.org/topic/es7-property-initials


只需添加本杰明的答案类变量是可能的,但您不会使用prototype来设置它们。

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

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

在类方法中,变量可以作为this.constructor.fooMyClass.foo访问。

这些类属性通常不能从类实例访问。也就是说,MyClass.foo'bar'但是new MyClass().fooundefined

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

1
2
3
4
5
6
7
class MyClass {
    get foo() {
        return this.constructor.foo;
    }
}

MyClass.foo = 'bar';

我只用tracer测试过这个,但我相信它在标准实现中也会起到同样的作用。

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


在您的示例中:

1
2
3
4
5
6
class MyClass {
    const MY_CONST = 'string';
    constructor(){
        this.MY_CONST;
    }
}

由于我的"const is primitive"https://developer.mozilla.org/en-us/docs/glossary/primitive,我们可以这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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_CONST是引用类型,如static get MY_CONST() {return ['string'];}警报输出为字符串,则为假。在这种情况下,delete操作员可以做到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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

最后,对于非const类变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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


babel支持esnext中的类变量,请检查以下示例:

1
2
3
4
5
6
7
8
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'


因为您的问题主要是风格化的(不想用一堆声明填充构造函数),所以也可以通过风格化的方式来解决。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
"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();
    }
}

这两个都将在构造新实例时调用。

有两个构造器,你把声明和你想做的其他构造器动作分开,从风格上来说,这也让你不难理解正在发生的事情。

我发现在处理大量声明和/或需要在实例化时执行的大量操作时使用它是一种很好的风格,并且希望使这两个概念彼此保持不同。

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

虽然有些人可能会做一段时间的重复,但这实际上是要点所在:它告诉他们意图是构建的一部分,即使这让他们做了一点重复的重复"这不是ES6构造函数的工作方式",然后再看一眼实际的构造函数"哦,他们在底部称之为它,我明白了。"这远比不传达这一意图(或不正确地传达它)要好得多,而且可能会让很多人错误地使用它,试图从外部和垃圾中初始化它。我建议的模式是非常有意的。

对于那些不想遵循这种模式的人,完全相反的模式也可以起作用。在开始时将声明传递给另一个函数。可以把它命名为"属性"或"公共属性"之类的。然后将其余的内容放入正常的构造函数中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"use strict";

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

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

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


那老派的方式呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);

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


正如本杰明在回答中所说,TC39明确决定至少在ES2015中不包括这一功能。然而,人们的共识似乎是,他们将在ES2016中增加这一点。

语法还没有决定,但是有一个ES2016的初步建议,允许您声明类上的静态属性。

多亏了巴别塔的魔力,你今天就可以用这个了。根据这些说明启用类属性转换,您就可以开始了。以下是语法示例:

1
2
3
4
5
6
class foo {
  static myProp = 'bar'
  someFunction() {
    console.log(this.myProp)
  }
}

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


[长线程,不确定是否已将其列为选项…]。对于Contsant来说,一个简单的替代方法就是在类外定义const。这只能从模块本身访问,除非附带getter。这样,prototype就不会乱扔,你就可以得到const了。

1
2
3
4
5
6
7
8
9
10
11
12
// 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);
    }
}


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

Look mum... no classes!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 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上


ES7类成员语法:

ES7有一个解决方案可以"连接"您的构造函数函数。下面是一个例子:

1
2
3
4
5
6
7
8
9
class Car {
 
  wheels = 4;
  weight = 100;

}

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

上述示例将在ES6中查看以下内容:

1
2
3
4
5
6
7
8
9
10
11
class Car {

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

}

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

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

奖励:对象工厂:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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);


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

1
2
3
4
5
6
7
8
9
10
11
12
class Foo {
    constructor() {
        var name ="foo"
        this.method = function() {
            return name
        }
    }
}

var foo = new Foo()

foo.method()


我解决这个问题的方法,这是另一个选项(如果您有jquery可用),它是在一个旧的school对象中定义字段,然后用该对象扩展类。我也不想在构造器中添加任务,这似乎是一个很好的解决方案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function MyClassFields(){
    this.createdAt = new Date();
}

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

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

--根据Bergi的评论更新。

没有jquery版本:

1
2
3
4
5
6
7
8
9
10
11
class SavedSearch  {
    constructor() {
        Object.assign(this,{
            id : '',
            type : '',
            title : '',
            createdAt: new Date(),
        });

    }
}

您最终还是得到了"fat"构造函数,但至少它都在一个类中,并且在一次命中中被分配。

编辑第2页:我现在已经转了一整圈,现在正在构造函数中分配值,例如

1
2
3
4
5
6
7
8
class SavedSearch  {
    constructor() {
        this.id = '';
        this.type = '';
        this.title = '';
        this.createdAt = new Date();
    }
}

为什么?实际上很简单,使用上面的加上一些JSDoc注释,phpsorm能够对属性执行代码完成。在一次命中中分配所有var是很好的,但是无法完成属性的代码,IMO,不值得(几乎可以肯定是微不足道的)性能优势。


您是否可以通过使用包含在更大的闭包中的强文本和少量模板库逻辑来避免整个问题?

暂时忽略关闭

1
const myDynamicInputs=(items)=>\backtick -${ items.map((item, I, array)=>'${do tons of junk}').join('')}';

http://codepen.io/jfrazz/pen/bqjpbz/

这是我可以从存储库中提供的最简单的示例,前400行是一个数据库+一些基本的实用程序函数。加上一些实用常数。

在我们将其转换为数据URI(由应用程序用户下载)的锅炉板之后,我们有数组模板,这些模板必须被提升和重新部署,但可以由输入、下拉列表或52页问题和数据组成。

这就是第二个例子:吃掉一个对象,获取各种类型的输入,所有这些输入都使用const作为正在构建的库的基变量。

http://codepen.io/jfrazz/pen/rwprvr/

不完全是你所要求的,但是清楚地表明这个常数是非常动态的。


这是静态和GeT的组合,有点老土。

1
2
3
4
5
6
7
8
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;
        }
}

别处使用

1
2
var conf = ConstantThingy.NO_REENTER__INIT;
if(conf.init)...