ChatGPT解决这个技术问题 Extra ChatGPT

ES6 类变量替代方案

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

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

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

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

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

您的主要问题是您将在构造函数中使用 20-30 个参数重复 this.member = member
你不能在课堂下使用public variable2 = true吗?这将在原型上定义它。
@Θεόφιλος Μουρατίδης:是的,而且我想将我的构造函数用于初始化过程,而不是用于变量声明。
@derylius:这是主要问题,它没有这样的功能。在 ES6 草案中甚至还没有决定公共/私人使用。试一试:es6fiddle.net
根据最新的,它有这个功能:wiki.ecmascript.org/doku.php?id=harmony:classes

C
Community

2018年更新:

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

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

varName = value

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

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

ES wiki 中关于 ES6 (maximally minimal classes) 提案的注释说明:

(故意)没有直接的声明方式来定义原型数据属性(除了方法)类属性,或者实例属性类属性和原型数据属性需要在声明之外创建。类定义中指定的属性被分配了相同的属性,就好像它们出现在对象字面量中一样。

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

但为什么?

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

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

constructor(){
    this.foo = bar
}

在你建议的构造函数中。另见the summary of the consensus

ES7 及更高版本

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


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

只是为了补充本杰明的答案-类变量是可能的,但您不会使用 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 doesn't really have classes。即使使用 ES6,我们也正在研究基于对象或原型的语言,而不是基于类的语言。在任何 function X () {} 中,X.prototype.constructor 都指向 X。在 X 上使用 new 运算符时,将创建一个继承 X.prototype 的新对象。从那里查找该新对象(包括 constructor)中的任何 undefined 属性。我们可以将其视为生成对象和类属性。


我在这里不明白一件事-我认为类语法和无法将变量直接放置在其中是为了防止将变量放置在原型中(上面的一个答案表明,原型不应该是故意的存储变量)。但是,当我们按照您的建议执行 MyClass.foo = 'bar'; 时。它不正是在做类限制应该阻止我们做的事情——即在原型上放置变量吗?
@tikej 这里的变量不是放在原型上,而是放在构造函数上
O
Oleg Mazko

在您的示例中:

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
@shinzou 我也在想同样的事情。不过,不一定是OP的错;它确实是一种语言缺乏适当的机制来以反映其真实世界关系的方式表示数据。
N
Nil

Babel 支持 ESNext 中的类变量,请检查此 example

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 设为 private 类变量,方法是使用“#”将其伪装成这样:#bar = 2;
J
Jimbo Jonny

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

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

"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 就像拥有 2 个构造函数,您可以在其中将声明和您想要执行的其他构造函数操作分开,并且从风格上来说,理解这也是正在发生的事情并不难。

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

注意:我非常有目的地不使用“初始化”的典型惯用思想(如 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 提供更多唯一名称以避免这种情况。我的第一个方法没有这个问题,因为它的构造函数的假一半是唯一以类命名的。


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

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

语法尚未确定,但 ES2016 有一个 preliminary proposal,可让您在类上声明静态属性。

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

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

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


是的,那不是 ES6。除非你知道它是什么,否则你不应该使用它。
z
zarkone

老派的方式呢?

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

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


这不会节省内存,而且只会使性能比在构造函数中声明它们时要差得多。 codereview.stackexchange.com/a/28360/9258
此外,这首先违背了使用 ES6 类的目的。就目前而言,似乎几乎不可能像真正的 oop 类那样使用 ES6 类,这有点令人失望……
@Kokodoko 真正的 oop - 你的意思是,就像 Java 中的 ie 一样?我同意,我注意到很多人对 JS 很生气,因为他们尝试像 Java 一样使用它,就像他们习惯的那样,因为 JS 语法看起来很相似,并且他们认为它的工作方式相同......但是从正如我们所知,在内部,它是完全不同的。
不过,这不仅仅是关于语言语法。 OOP 哲学非常适合一般编程——尤其是在创建应用程序和游戏时。 JS 传统上是在构建网页时实现的,这是完全不同的。不知何故,这两种方法需要结合在一起,因为网络也越来越关注应用程序。
@Kokodoko 仅仅因为它是不同的 OOP 并不意味着它不是 OOP。原型是 100% 有效的 OOP 方法;没有人会称 Self 为“非 OOP”,因为它使用原型。不匹配另一个 OOP 范式意味着:它是不同的。
H
Hertzel Guinness

[长线程,不确定它是否已经列为一个选项...]。
contsants only 的简单 alternative 将在类之外定义 const .这只能从模块本身访问,除非带有 getter。
这样 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);
    }
}

如果您 a) 使用 var 而不是 const 2) 使用此类的多个实例,会发生什么情况?然后外部变量将随着类的每个实例而改变
失败点@nickcarraway,我的提议仅代表 const,而不是标题中的问题。
W
Willem van der Veen

ES7 类成员语法:

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

类汽车 { 车轮 = 4;重量 = 100; } const car = new Car(); console.log(car.wheels, car.weight);

上面的示例在 ES6 中将如下所示:

类汽车 { 构造函数() { this.wheels = 4; this.weight = 100; } } const car = new Car(); console.log(car.wheels, car.weight);

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

奖励:对象工厂:

函数 generateCar(wheels, weight) { class Car { constructor() {} Wheels = Wheels;重量=重量; } 返回新车(); } const car1 = generateCar(4, 50); const car2 = generateCar(6, 100); console.log(car1.wheels, car1.weight); console.log(car2.wheels, car2.weight);


您已经完成了实例变量,而不是类变量。
R
Ruslan

您可以模仿 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


整个事情看起来非常混乱。
T
Tyler

您仍然不能像在其他编程语言中那样声明任何类。但是您可以创建尽可能多的类变量。但问题是类对象的范围。所以据我说,在 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
   }
}

H
Herman Van Der Blom

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


S
Steve Childs

我解决这个问题的方法是另一种选择(如果您有可用的 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,不值得(几乎可以肯定是微不足道的)性能优势。


如果您可以使用 class 语法,那么您也可以使用 Object.assign。不需要 jQuery。
我认为将 MyClassFields 设为构造函数没有任何意义——它没有任何方法。你能详细说明你为什么这样做(而不是一个简单的返回对象文字的工厂函数)吗?
@Bergi - 老实说,可能是习惯的力量,而不是其他任何东西。关于 Object.assign 的电话也很好 - 不知道!
O
Osama Xäwãñz

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

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

var foo = new Foo()

foo.method()

您需要创建此类的一个实例才能使用此变量。静态函数无法访问该变量
c
ceving

只需定义一个 getter

类 MyClass { get MY_CONST () { return 'string'; } 构造函数 () { console.log ("MyClass MY_CONST:", this.MY_CONST); } } var obj = new MyClass();


q
qwr

截至 2021 年的最新浏览器(不是 IE,请参阅 MDN 浏览器图表)实现了 Public class fields,这似乎是您正在寻找的:

类 MyClass { 静态 foo = 3; } console.log(MyClass.foo);

但显然不可能将其设为 constDeclaring static constants in ES6 classes?

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

类 MyClass { 静态获取 CONST() { 返回 3; } } MyClass.CONST = 4; // 属性不受影响 console.log(MyClass.CONST);


T
TroyWorks

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

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

我建议将 singleton_instance 作为属性名称,以便每个人都了解您在此处所做的事情。