简体   繁体   English

ES6 类变量替代方案

[英]ES6 class variable alternatives

Currently in ES5 many of us are using the following pattern in frameworks to create classes and class variables, which is comfy:目前在 ES5 中,我们许多人在框架中使用以下模式来创建类和类变量,这很舒服:

// ES 5
FrameWork.Class({

    variable: 'string',
    variable2: true,

    init: function(){

    },

    addItem: function(){

    }

});

In ES6 you can create classes natively, but there is no option to have class variables:在 ES6 中,您可以本地创建类,但没有选择类变量的选项:

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

Sadly, the above won't work, as classes only can contain methods.遗憾的是,上述方法不起作用,因为类只能包含方法。

I understand that I can this.myVar = true in constructor …but I don't want to 'junk' my constructor, especially when I have 20-30+ params for a bigger class.我知道我可以在constructor this.myVar = true ……但我不想“垃圾”我的构造函数,尤其是当我有 20-30+ 个参数用于更大的类时。

I was thinking of many ways to handle this issue, but haven't yet found any good ones.我想了很多方法来处理这个问题,但还没有找到任何好的方法。 (For example: create a ClassConfig handler, and pass a parameter object, which is declared separately from the class. Then the handler would attach to the class. I was thinking about WeakMaps also to integrate, somehow.) (例如:创建一个ClassConfig处理程序,并传递一个parameter对象,该对象与类分开声明。然后处理程序将附加到类。我正在考虑以某种方式集成WeakMaps 。)

What kind of ideas would you have to handle this situation?你有什么样的想法来处理这种情况?

2018 update: 2018年更新:

There is now a stage 3 proposal - I am looking forward to make this answer obsolete in a few months.现在有一个第 3 阶段的提案 - 我期待在几个月内使这个答案过时。

In the meantime anyone using TypeScript or babel can use the syntax:同时,任何使用 TypeScript 或 babel 的人都可以使用以下语法:

varName = value

Inside a class declaration/expression body and it will define a variable.在类声明/表达式主体中,它将定义一个变量。 Hopefully in a few months/weeks I'll be able to post an update.希望在几个月/几周后,我将能够发布更新。

Update: Chrome 74 now ships with this syntax working.更新:Chrome 74 现在附带此语法。


The notes in the ES wiki for the proposal in ES6 ( maximally minimal classes ) note: 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.类定义中指定的属性被赋予与它们出现在对象文字中相同的属性。

This means that what you're asking for was considered, and explicitly decided against.这意味着您的要求已被考虑,并明确决定反对。

but... why?但为什么?

Good question.好问题。 The good people of TC39 want class declarations to declare and define the capabilities of a class. TC39 的好人希望类声明来声明和定义类的功能。 Not its members.不是它的成员。 An ES6 class declaration defines its contract for its user. ES6 类声明为其用户定义了它的契约。

Remember, a class definition defines prototype methods - defining variables on the prototype is generally not something you do.请记住,类定义定义了原型方法——在原型上定义变量通常不是您要做的。 You can, of course use:你当然可以使用:

constructor(){
    this.foo = bar
}

In the constructor like you suggested.在您建议的构造函数中。 Also see the summary of the consensus .另请参阅共识摘要

ES7 and beyond ES7 及更高版本

A new proposal for ES7 is being worked on that allows more concise instance variables through class declarations and expressions - https://esdiscuss.org/topic/es7-property-initializers ES7 的一项新提案正在制定中,通过类声明和表达式允许更简洁的实例变量 - https://esdiscuss.org/topic/es7-property-initializers

Just to add to Benjamin's answer — class variables are possible, but you wouldn't use prototype to set them.只是为了补充本杰明的答案 - 类变量是可能的,但您不会使用prototype来设置它们。

For a true class variable you'd want to do something like the following:对于真正的类变量,您需要执行以下操作:

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

From within a class method that variable can be accessed as this.constructor.foo (or MyClass.foo ).从一个类方法中,该变量可以作为this.constructor.foo (或MyClass.foo )访问。

These class properties would not usually be accessible from to the class instance.这些类属性通常不能从类实例访问。 ie MyClass.foo gives 'bar' but new MyClass().foo is undefinedMyClass.foo给出'bar'new MyClass().foo undefined

If you want to also have access to your class variable from an instance, you'll have to additionally define a getter:如果您还想从实例访问类变量,则必须另外定义一个 getter:

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

MyClass.foo = 'bar';

I've only tested this with Traceur, but I believe it will work the same in a standard implementation.我只用 Traceur 测试过这个,但我相信它在标准实现中也能正常工作。

JavaScript doesn't really have classes . JavaScript 并没有真正的类 Even with ES6 we're looking at an object- or prototype-based language rather than a class-based language.即使在 ES6 中,我们也在寻找一种基于对象或原型的语言,而不是一种基于类的语言。 In any function X () {} , X.prototype.constructor points back to X .在任何function X () {}X.prototype.constructor指向X When the new operator is used on X , a new object is created inheriting X.prototype .new运算符用于X ,会创建一个继承X.prototype的新对象。 Any undefined properties in that new object (including constructor ) are looked up from there.从那里查找该新对象(包括constructor )中的任何未定义属性。 We can think of this as generating object and class properties.我们可以将其视为生成对象和类属性。

Babel supports class variables in ESNext, check this example : 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'

In your example:在你的例子中:

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

Because of MY_CONST is primitive https://developer.mozilla.org/en-US/docs/Glossary/Primitive we can just do:由于 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

But if MY_CONST is reference type like static get MY_CONST() {return ['string'];} alert output is string, false .但是,如果MY_CONST是像static get MY_CONST() {return ['string'];}这样的引用类型static get MY_CONST() {return ['string'];}警报输出是string, false In such case delete operator can do the trick:在这种情况下, 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

And finally for class variable not const :最后对于类变量不是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

Since your issue is mostly stylistic (not wanting to fill up the constructor with a bunch of declarations) it can be solved stylistically as well.由于您的问题主要是文体问题(不想用一堆声明填充构造函数),因此也可以在文体上解决。

The way I view it, many class based languages have the constructor be a function named after the class name itself.在我看来,许多基于类的语言的构造函数是一个以类名本身命名的函数。 Stylistically we could use that that to make an ES6 class that stylistically still makes sense but does not group the typical actions taking place in the constructor with all the property declarations we're doing.在风格上,我们可以使用它来创建一个在风格上仍然有意义的 ES6 类,但不会将构造函数中发生的典型操作与我们正在执行的所有属性声明组合在一起。 We simply use the actual JS constructor as the "declaration area", then make a class named function that we otherwise treat as the "other constructor stuff" area, calling it at the end of the true constructor.我们简单地使用实际的 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();
    }
}

Both will be called as the new instance is constructed.两者都将在构造新实例时被调用。

Sorta like having 2 constructors where you separate out the declarations and the other constructor actions you want to take, and stylistically makes it not too hard to understand that's what is going on too.有点像拥有 2 个构造函数,您可以在其中将声明和其他要执行的构造函数操作分开,并且在风格上使人们不难理解这也是正在发生的事情。

I find it's a nice style to use when dealing with a lot of declarations and/or a lot of actions needing to happen on instantiation and wanting to keep the two ideas distinct from each other.我发现在处理大量声明和/或需要在实例化时发生的大量操作时使用它是一种很好的风格,并且希望使这两个想法彼此不同。


NOTE : I very purposefully do not use the typical idiomatic ideas of "initializing" (like an init() or initialize() method) because those are often used differently.注意:我非常有目的地不使用“初始化”的典型惯用想法(如init()initialize()方法),因为它们的使用方式通常不同。 There is a sort of presumed difference between the idea of constructing and initializing.构造和初始化的想法之间存在某种假定的差异。 Working with constructors people know that they're called automatically as part of instantiation.使用构造函数的人知道它们作为实例化的一部分被自动调用。 Seeing an init method many people are going to assume without a second glance that they need to be doing something along the form of var mc = MyClass(); mc.init();看到一个init方法,许多人会不假思索地假设他们需要按照var mc = MyClass(); mc.init();的形式做一些事情var mc = MyClass(); mc.init(); var mc = MyClass(); mc.init(); , because that's how you typically initialize. ,因为这就是您通常初始化的方式。 I'm not trying to add an initialization process for the user of the class, I'm trying to add to the construction process of the class itself.我不是在尝试为类的用户添加初始化过程,而是在尝试添加类本身的构建过程中。

While some people may do a double-take for a moment, that's actually the bit of the point: it communicates to them that the intent is part of construction, even if that makes them do a bit of a double take and go "that's not how ES6 constructors work" and take a second looking at the actual constructor to go "oh, they call it at the bottom, I see", that's far better than NOT communicating that intent (or incorrectly communicating it) and probably getting a lot of people using it wrong, trying to initialize it from the outside and junk.虽然有些人可能会在片刻重复拍摄,但这实际上是重点:它向他们传达了意图是构建的一部分,即使这让他们做了一些重复拍摄并继续“那不是ES6 构造函数是如何工作的”,然后再看一下实际的构造函数,“哦,他们在底部调用它,我明白了”,这比不传达该意图(或错误地传达它)要好得多,并且可能会得到很多人们使用它错误,试图从外部和垃圾初始化它。 That's very much intentional to the pattern I suggest.这对我建议的模式非常有意。


For those that don't want to follow that pattern, the exact opposite can work too.对于那些不想遵循这种模式的人,完全相反的方法也可以。 Farm the declarations out to another function at the beginning.在开始时将声明转移到另一个函数。 Maybe name it "properties" or "publicProperties" or something.也许将其命名为“properties”或“publicProperties”或其他名称。 Then put the rest of the stuff in the normal constructor.然后把剩下的东西放在普通的构造函数中。

"use strict";

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

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

Note that this second method may look cleaner but it also has an inherent problem where properties gets overridden as one class using this method extends another.请注意,这第二种方法可能看起来更简洁,但它也有一个固有的问题,即当使用此方法的一个类扩展另一个类时, properties会被覆盖。 You'd have to give more unique names to properties to avoid that.您必须为properties提供更多唯一名称以避免这种情况。 My first method does not have this problem because its fake half of the constructor is uniquely named after the class.我的第一种方法没有这个问题,因为它的构造函数的假一半是以类唯一命名的。

What about the oldschool way?老派的方式呢?

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

In constructor you mention only those vars which have to be computed.在构造函数中,您只提到那些必须计算的变量。 I like prototype inheritance for this feature -- it can help to save a lot of memory(in case if there are a lot of never-assigned vars).我喜欢这个特性的原型继承——它可以帮助节省大量内存(如果有很多从未分配的变量)。

As Benjamin said in his answer, TC39 explicitly decided not to include this feature at least for ES2015.正如 Benjamin 在他的回答中所说,TC39 明确决定至少在 ES2015 中不包含此功能。 However, the consensus seems to be that they will add it in ES2016.然而,共识似乎是他们将在 ES2016 中添加它。

The syntax hasn't been decided yet, but there's a preliminary proposal for ES2016 that will allow you to declare static properties on a class.语法尚未确定,但 ES2016 有一个初步建议,允许您在类上声明静态属性。

Thanks to the magic of babel, you can use this today.感谢 babel 的魔力,你今天可以使用它。 Enable the class properties transform according to these instructions and you're good to go.根据这些说明启用类属性转换,您就可以开始了。 Here's an example of the syntax:下面是一个语法示例:

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

This proposal is in a very early state, so be prepared to tweak your syntax as time goes on.该提案处于非常早期的状态,因此请准备好随着时间的推移调整您的语法。

[Long thread, not sure if its already listed as an option...]. [长篇大论,不确定它是否已作为选项列出...]。
A simple alternative for contsants only , would be defining the const outside of class.常量的一个简单替代方法是在类之外定义常量。 This will be accessible only from the module itself, unless accompanied with a getter.这只能从模块本身访问,除非附带一个吸气剂。
This way prototype isn't littered and you get the 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);
    }
}

ES7 class member syntax: ES7类成员语法:

ES7 has a solution for 'junking' your constructor function. ES7有一个“垃圾化”构造函数的解决方案。 Here is an example:下面是一个例子:

 class Car { wheels = 4; weight = 100; } const car = new Car(); console.log(car.wheels, car.weight);

The above example would look the following in ES6 :上面的例子在ES6看起来如下:

 class Car { constructor() { this.wheels = 4; this.weight = 100; } } const car = new Car(); console.log(car.wheels, car.weight);

Be aware when using this that this syntax might not be supported by all browsers and might have to be transpiled an earlier version of JS.使用此语法时请注意,并非所有浏览器都支持此语法,并且可能必须将其转译为早期版本的 JS。

Bonus: an object factory:奖励:对象工厂:

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

You can mimic es6 classes behaviour... and use your class variables :)您可以模仿 es6 类的行为......并使用您的类变量:)

Look mum... no classes!看妈妈...没有课!

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

I put it on GitHub我把它放在GitHub上

If its only the cluttering what gives the problem in the constructor why not implement a initialize method that intializes the variables.如果只有什么给出了这个问题的杂乱constructor为什么不实行initialize该方法intializes的变量。 This is a normal thing to do when the constructor gets to full with unnecessary stuff.当构造函数装满不必要的东西时,这是一件正常的事情。 Even in typed program languages like C# its normal convention to add an Initialize method to handle that.即使在像C#这样的类型化程序语言中,它的常规约定也是添加Initialize方法来处理它。

The way I solved this, which is another option (if you have jQuery available), was to Define the fields in an old-school object and then extend the class with that object.我解决这个问题的方法是另一种选择(如果您有可用的 jQuery),是在老式对象中定义字段,然后使用该对象扩展类。 I also didn't want to pepper the constructor with assignments, this appeared to be a neat solution.我也不想给构造函数添加赋值,这似乎是一个巧妙的解决方案。

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

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

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

-- Update Following Bergi's comment. - 更新以下贝尔吉的评论。

No JQuery Version:没有 JQuery 版本:

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

    }
}

You still do end up with 'fat' constructor, but at least its all in one class and assigned in one hit.你仍然会得到“胖”的构造函数,但至少它全部在一个类中并在一次命中中分配。

EDIT #2: I've now gone full circle and am now assigning values in the constructor, eg编辑#2:我现在已经绕了一圈,现在正在构造函数中分配值,例如

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

Why?为什么? Simple really, using the above plus some JSdoc comments, PHPStorm was able to perform code completion on the properties.真的很简单,使用上面的加上一些 JSdoc 注释,PHPStorm 能够对属性执行代码完成。 Assigning all the vars in one hit was nice, but the inability to code complete the properties, imo, isn't worth the (almost certainly minuscule) performance benefit.在一次命中中分配所有变量很好,但是无法编码完成属性,imo,不值得(几乎肯定是微不足道的)性能优势。

Well, you can declare variables inside the Constructor.好吧,您可以在构造函数中声明变量。

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

var foo = new Foo()

foo.method()

Still you can't declare any classes like in another programming languages.您仍然不能像在其他编程语言中那样声明任何类。 But you can create as many class variables.但是您可以创建尽可能多的类变量。 But problem is scope of class object.但问题是类对象的范围。 So According to me, Best way OOP Programming in ES6 Javascript:-因此,据我所知,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
   }
}

Just define a getter .只需定义一个getter

 class MyClass { get MY_CONST () { return 'string'; } constructor () { console.log ("MyClass MY_CONST:", this.MY_CONST); } } var obj = new MyClass();

Recent browsers as of 2021 (not IE, see MDN browser chart) implement Public class fields which seems to be what you're looking for:截至 2021 年的最新浏览器(不是 IE,请参阅 MDN 浏览器图表)实现了公共类字段,这似乎是您要查找的内容:

 class MyClass { static foo = 3; } console.log(MyClass.foo);

However apparently it's not possible to make this a const : Declaring static constants in ES6 classes?但是显然不可能使它成为const在 ES6 类中声明静态常量?

A static getter looks pretty close:静态 getter 看起来非常接近:

 class MyClass { static get CONST() { return 3; } } MyClass.CONST = 4; // property unaffected console.log(MyClass.CONST);

This is a bit hackish combo of static and get works for me这是一个有点hackish的静态组合,对我有用

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

elsewhere used在别处使用

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

Can you avoid the whole issue by using strong literals and a little library of templates logic run contained in a larger closure? 您可以通过使用较大的闭包中包含的强大文字和少量模板逻辑库来避免整个问题吗?

ignoring the closure for now 暂时忽略关闭

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

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

THIS is the simplest example I can offer from repository, The first 400 lines are a data library+ some basic utility functions. 这是我可以从存储库中提供的最简单的示例,前400行是数据库+一些基本的实用程序功能。 Plus a handful of utility constants. 加上一些实用程序常数。

After the boiler plate, which we are turning into a data uri--downloaded by app users--we have array templates, that have to be lifted and redeployed, but which can be composed to be anything from inputs, dropdowns, or 52 pages of questions and data. 在我们将其转变为数据uri(由应用程序用户下载)的样板之后,我们有了数组模板,必须将其提升和重新部署,但可以组成为输入,下拉菜单或52页中的任何内容问题和数据。

That is this second example: Eat an object, get inputs of various types, all using const as the base variable of library, being constructed. 这是第二个示例:吃一个对象,获取各种类型的输入,所有输入都使用const作为正在构造的库的基本变量。

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

Not exactly what you asked, but a clear showing that constant can be pretty dynamic. 并非完全符合您的要求,但清楚地表明该常数可能是非常动态的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM