ES6 类变量替代方案

IT技术 javascript class ecmascript-6
2021-02-04 00:01:04

目前在 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;
    }
}

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

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

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

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

6个回答

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

另请参阅“类字段和静态属性”提案(已在Babel 中实现github.com/jeffmo/es-class-fields-and-static-properties
2021-03-16 00:01:04
您可能想提及static属性
2021-03-23 00:01:04
oop,脑残。我忘了它也static只适用于方法。
2021-03-28 00:01:04
@wintercounter 重要的一点是,允许定义属性会像方法一样在原型上定义它们,而不是在每个实例上。最大最小类仍然是其最核心的原型继承。在您的案例中,您真正想做的是共享结构并为每个实例分配成员。这根本不是 ES6 中的类的目标 - 共享功能。所以是的,对于共享结构,您必须坚持旧语法。至少要到 ES7 :)
2021-04-03 00:01:04
如果 TC39 的好人不希望它表现得像编程世界的其他人对名为“类”的东西所期望的那样,那么他们应该将这个概念命名为“类”以外的其他名称。
2021-04-07 00:01:04

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

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

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

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

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

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

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

MyClass.foo = 'bar';

我只用 Traceur 测试过这个,但我相信它在标准实现中也能正常工作。

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

我在这里不明白一件事 - 我认为类语法和无法将变量直接放在那里是为了防止将变量放在原型中(上面的一个答案表明原型不应该是故意放置的地方)存储变量)。然而,当我们MyClass.foo = 'bar';按照你的建议去做的时候难道它不正是类限制应该阻止我们做的事情 - 即在原型上放置变量吗?
2021-03-29 00:01:04

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'
你可以通过像这样用“#”伪装bar来使它成为一个私有类变量:#bar = 2;
2021-04-08 00:01:04

在你的例子中:

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_CONST是像static get MY_CONST() {return ['string'];}警报输出这样的引用类型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

最后对于类变量不是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
delete如果出于性能原因单独操作,请避开操作员。你在这里真正想要的是Object.defineProperty.
2021-03-27 00:01:04
@shinzou 我也在想同样的事情。不过,不一定是 OP 的错;确实是这种语言缺乏以反映其现实世界关系的方式表示数据的适当机制。
2021-04-07 00:01:04

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

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

“严格使用”;

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

    // 各种其他“构造函数”的东西,不再与声明混淆
    我的课() {
        做任何事情();
    }
}

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

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

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


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

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


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

“严格使用”;

我的课堂
{
    特性() {
        this.prop1 = '等等 1';
        this.prop2 = '等等 2';
        this.prop3 = '等等 3';
    }

    构造函数(){
        this.properties();
        做任何事情();
    }
}

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

请不要使用以类本身命名的原型方法。这在 JS 中是不惯用的,不要试图让它看起来像另一种语言。如果您真的想使用这种方法,则该方法的规范名称是init.
2021-03-12 00:01:04
@Bergi -init是一种经常使用的模式,通常意味着当外部用户想要初始化时从类外部调用,即var b = new Thing(); b.init();. 这是一个 100% 风格的选择,我更愿意传达它是第二个函数,通过利用其他语言中的模式自动调用。不太可能有人看到这个并假设他们需要MyClass从外部调用该方法,更有可能他们意识到意图是在构造中起作用的第二种方法(即在实例化时由自身调用)。
2021-03-15 00:01:04
我更喜欢您使用该properties()功能的最后一个选项但是,在扩展类时事情会变得有点复杂,所以我建议如果您properties()在所有类中使用一个函数来声明您的类属性,请在方法名称前加上类名(IE:MyClassProperties()为了避免意外覆盖子类中的函数调用。另外,请记住,任何调用都super()必须首先在类构造函数中声明。
2021-03-29 00:01:04
@Bergi - 从另一种语言中获取模式并将其应用到 JS 中,这种方式在技术上不是正在发生的事情,但仍然有效并非完全没有先例。您不可能告诉我没有人注意到$myvar引用旨在保存 jQuery 对象的变量标准方式与许多 PHP 程序员习惯的在变量开头使用 $ 的模式并不方便地相似。只是暗示“是的,它不是完全相同的东西......但是看......它仍然是一个变量,因为这是在某些语言中完成变量的一种方式!” 有帮助。
2021-03-30 00:01:04
嗯,我可能会通过查看构造函数而不是方法名称来意识到这一点MyClass
2021-04-08 00:01:04