【问题标题】:ES6 class variable alternativesES6 类变量替代方案
【发布时间】:2017-05-13 01:56:56
【问题描述】:

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

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

我知道我可以在 constructor 中使用 this.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

标签: javascript class ecmascript-6


【解决方案1】:

2018 年更新:

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

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

varName = value

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

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


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

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

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

类定义中指定的属性被赋予与对象字面量中相同的属性。

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

但是……为什么?

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

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

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中实现
【解决方案2】:

只是为了补充本杰明的答案——类变量是可能的,但你不会使用 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。 当new 运算符用于X 时,将创建一个继承X.prototype 的新对象。从那里查找该新对象(包括constructor)中的任何未定义 属性。我们可以将其视为生成对象和类属性。

【讨论】:

  • 我在这里不明白一件事 - 我认为类语法和无法将变量直接放在其中是为了防止将变量放在原型中(上面的一个答案表明这是故意的原型不应该是存储变量的地方)。然而,当我们按照您的建议执行 MyClass.foo = 'bar'; 时。它不正是在做类限制应该阻止我们做的事情——即在原型上放置变量吗?
【解决方案3】:

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;
【解决方案4】:

在你的例子中:

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'];},则警报输出为字符串,错误。在这种情况下,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的错;确实是这种语言缺乏适当的机制来以反映其真实世界关系的方式表示数据。
【解决方案5】:

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

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

“使用严格”; 我的班级 { // 只声明你的属性然后调用 this.ClassName();从这里 构造函数(){ this.prop1 = '废话 1'; this.prop2 = '废话 2'; this.prop3 = '废话 3'; this.MyClass(); } // 各种其他“构造函数”的东西,不再与声明混为一谈 我的课() { 随便(); } }

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

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

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


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

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

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

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

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

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

【讨论】:

  • 是的,这不是 ES6。除非你知道它是什么,否则你不应该使用它。
【解决方案7】:

那老派的方式呢?

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 real oop - 你的意思是,就像在 Java 中一样?我同意,我注意到很多人对 JS 很生气,因为他们尝试像 Java 一样使用它,就像他们习惯的那样,因为 JS 语法看起来很相似,并且他们认为它的工作方式相同......但是从正如我们所知,内部是完全不同的。
  • 这不仅仅是语言语法。 OOP 哲学非常适合一般编程——尤其是在创建应用程序和游戏时。 JS 传统上是在构建网页时实现的,这是完全不同的。不知何故,这两种方法需要结合起来,因为网络也越来越关注应用程序。
  • @Kokodoko 仅仅因为它是一个不同的 OOP 并不意味着它不是 OOP。原型是 100% 有效的 OOP 方法;没有人会称 Self 为“非 OOP”,因为它使用原型。不匹配另一个 OOP 范式意味着:它是不同的。
【解决方案8】:

[长线程,不确定它是否已经列为选项...]。
contsants only 的一个简单的替代,将在类之外定义 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,而不是标题中的问题。
【解决方案9】:

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

【讨论】:

  • 你已经完成了实例变量,而不是类变量。
【解决方案10】:

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

【讨论】:

  • 整件事情看起来很乱。
【解决方案11】:

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

【讨论】:

    【解决方案12】:

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

    【讨论】:

      【解决方案13】:

      我解决这个问题的方法是另一种选择(如果你有 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 cmets,PHPStorm 能够对属性执行代码完成。一次性分配所有变量很好,但是无法编写完整的属性,imo,不值得(几乎可以肯定是微不足道的)性能优势。

      【讨论】:

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

      嗯,你可以在构造函数中声明变量。

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

      【讨论】:

      • 你需要创建这个类的一个实例来使用这个变量。静态函数无法访问该变量
      【解决方案15】:

      只需定义一个getter

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

      【讨论】:

        【解决方案16】:

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

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

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

        静态 getter 看起来非常接近:

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

        【讨论】:

          【解决方案17】:

          这是一个有点老套的静态组合,并为我工作

          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 作为属性名称,以便每个人都了解您在此处所做的工作。
          猜你喜欢
          • 2018-01-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-12-24
          相关资源
          最近更新 更多