首页 文章

ES6类可变替代品

提问于
浏览
423

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

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

我知道我可以 constructorconstructor ...但我不是我的构造函数,特别是当我有一个20-30个参数更大的 class 时 .

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

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

14 回答

  • 14

    在你的例子中:

    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'];} 警报输出是字符串,则为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
    
  • 456

    [长线程,不确定它是否已作为选项列出...] .
    一个简单的替代方案是在类外定义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);
        }
    }
    
  • 0

    你可以通过使用强大的文字和一个包含在更大的闭包中的一些模板逻辑库来避免整个问题吗?

    暂时忽略关闭

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

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

    这是我可以从存储库中提供的最简单的示例,前400行是数据库的一些基本实用功能 . 加上一些实用常量 .

    在锅炉板(我们正在变成数据uri - 由app用户下载)之后,我们有阵列模板,必须被提升和重新部署,但可以组成任何来自输入,下拉列表或52页的内容问题和数据 .

    这是第二个例子:吃一个对象,得到各种类型的输入,都使用const作为库的基本变量,正在构造 .

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

    不完全是你问的问题,但清楚地表明常数可以非常动态 .

  • 13

    这是静态的一点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;
            }
    }
    

    别的用过

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

    我解决这个问题的方法是另一种选择(如果你有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(),
            });
    
        }
    }
    

    你仍然最终得到'胖'构造函数,但至少它在一个类中并且在一次命中中分配 .

    EDIT #2: 我现在已经完全循环,现在正在构造函数中分配值,例如

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

    为什么?很简单,使用上面加上一些JSdoc注释,PHPStorm能够在属性上执行代码完成 . 在一次命中中分配所有变量是很好的,但是无法编写完成属性imo,不值得(几乎肯定是微不足道的)性能优势 .

  • 6

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

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

    只是添加到Benjamin 's answer — class variables are possible, but you wouldn' t使用 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 )中的任何未定义属性 . 我们可以将其视为生成对象和类属性 .

  • 21

    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'
    
  • 0

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

  • 5

    2018年更新:

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

    在此期间,任何使用TypeScript或babel的人都可以使用以下语法:

    varName = value
    

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


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

    (故意)没有直接声明方式来定义原型数据属性(除了方法)类属性,或者实例属性需要在声明之外创建类属性和原型数据属性 . 在类定义中指定的属性被赋予与它们出现在对象文字中相同的属性 .

    这意味着 what you're asking for was considered, and explicitly decided against.

    但是......为什么?

    好问题 . TC39的优秀人员希望类声明声明和定义类的功能 . 不是它的成员 . ES6类声明定义了其用户的 Contract .

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

    constructor(){
        this.foo = bar
    }
    

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

    ES7及以上

    正在开发一个针对ES7的新提案,它允许通过类声明和表达式提供更简洁的实例变量 - https://esdiscuss.org/topic/es7-property-initializers

  • 114

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

    我查看它的方式,许多基于类的语言使构造函数成为以类名本身命名的函数 . 在风格上,我们可以使用它来创建一个风格上仍然有意义的ES6类,但不会将构造函数中发生的典型操作与我们正在进行的所有属性声明分组 . 我们只是使用实际的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();
        }
    }
    

    在构造新实例时将调用它们 .

    Sorta就像有两个构造函数,你可以将声明和你想要采取的其他构造函数分开,并且在风格上使它不太难理解这也是正在发生的事情 .

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


    NOTE :我非常有目的地不使用"initializing"的典型惯用思想(如 init()initialize() 方法)因为它们的使用方式不同 . 构造和初始化的想法之间存在一种假定的差异 . 使用构造函数的人知道它们是作为实例化的一部分自动调用的 . 看到 init 方法很多人会毫不犹豫地假设他们需要按照 var mc = MyClass(); mc.init(); 的形式做一些事情,因为's how you typically initialize. I'm没有尝试为类的用户添加初始化过程,我正在尝试添加 to 类本身的构造过程 .

    虽然有些人可能暂时做一次双重拍摄,但这实际上有点重要:它向他们传达意图是构造的一部分,即使这使他们做了一些双重拍摄并且去了“那不是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 提供更多唯一名称以避免这种情况 . 我的第一个方法没有这个问题,因为它的假构造函数的一半是在类之后唯一命名的 .

  • 0

    那些oldschool的方式呢?

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

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

  • 0

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

    对于ES2016,语法没有't been decided yet, but there' s preliminary proposal,它允许您在类上声明静态属性 .

    感谢巴贝尔的魔力,你今天就可以使用它 . 根据these instructions启用类属性转换,并且're good to go. Here'是语法的示例:

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

    此提案处于非常早期的状态,因此请随时准备调整语法上 .

  • -1

    ES7类成员语法:

    ES7 有'junking'构造函数的解决方案 . 这是一个例子:

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

相关问题