ES6类variables的替代品
目前在ES5中,我们中的很多人在框架中使用以下模式来创build类和类variables,这是很舒服的:
// ES 5 FrameWork.Class({ variable: 'string', variable2: true, init: function(){ }, addItem: function(){ } });
在ES6中,你可以在本地创build类,但是没有select类variables:
// ES6 class MyClass { const MY_CONST = 'string'; // <-- this is not possible in ES6 constructor(){ this.MY_CONST; } }
可悲的是,上面的方法是行不通的,因为类只能包含方法。
我明白,我可以在constructor
this.myVar = true
…但我不想'垃圾'我的构造函数,特别是当我有一个更大的类20-30 + params。
我想了很多办法来解决这个问题,但是还没有find什么好的方法。 (例如:创build一个ClassConfig
处理函数,然后传递一个parameter
对象,这个parameter
对象和这个类是分开声明的,然后这个处理函数将会附加到这个类上,我也想到了WeakMaps
也可以用来进行整合。
你有什么样的想法来处理这种情况?
ES维基上ES6( 最小类 )提案中的注释:
有(有意)没有直接声明的方式来定义原型数据属性(方法除外)类属性或实例属性
类属性和原型数据属性需要在声明之外创build。
在类定义中指定的属性被赋予与在对象字面值中出现的属性相同的属性。
这意味着你所要求的是被考虑的,并且被明确地反对。
但为什么?
好问题。 TC39的好人要类声明来声明和定义一个类的能力。 不是它的成员。 ES6类声明为其用户定义了它的合约。
请记住,类定义定义了原型方法 – 在原型上定义variables通常不是你所做的。 你当然可以使用:
constructor(){ this.foo = bar }
在你build议的构造函数中。 另见共识的总结 。
ES7及以后
正在开发一个新的ES7提案,它允许通过类声明和expression式来实现更简洁的实例variables – https://esdiscuss.org/topic/es7-property-initializers
为了增加Benjamin的答案 – 类variables是可能的,但是你不会使用prototype
来设置它们。
对于一个真正的类variables,你想要做如下的事情:
class MyClass {} MyClass.foo = 'bar';
从一个类的方法中,该variables可以作为this.constructor.foo
(或MyClass.foo
)访问。
这些类属性通常不能从类实例中访问。 即MyClass.foo
给出'bar'
但new MyClass().foo
是undefined
如果你也想从一个实例访问你的类variables,你必须另外定义一个getter:
class MyClass { get foo() { return this.constructor.foo; } } MyClass.foo = 'bar';
我只用Traceurtesting过,但是我相信它在标准的实现中也是一样的。
JavaScript并没有真正的类 。 即使在ES6中,我们也在寻找基于对象或原型的语言,而不是基于类的语言。 在任何function X () {}
, X.prototype.constructor
指向X
当在X
上使用new
运算符时,会创build一个inheritanceX.prototype
的新对象。 在那个新的对象(包括constructor
)中的任何未定义的属性从那里查找。 我们可以把它看作生成对象和类属性。
在你的例子中:
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'];}
引用types,请static get MY_CONST() {return ['string'];}
alert output is 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
最后,对于类variables不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
Babel支持ESNext中的类variables,请查看这个例子 – http://esnextb.in/?gist=ad168ed095aeac5ce098
老学校的方式呢?
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);
在构造函数中,你只提到那些必须计算的variables。 我喜欢这个function的原型inheritance – 它可以帮助节省大量的内存(如果有很多从未分配的variables)。
正如本杰明在回答中所说的那样,TC39明确地决定至less在ES2015中不包含这个function。 然而,共识似乎是他们会把它joinES2016。
语法尚未确定,但ES2016有一个初步build议 ,允许您在类上声明静态属性。
感谢babel的魔力,今天你可以使用它。 根据这些说明启用类属性转换,你很好去。 这是一个语法的例子:
class foo { static myProp = 'bar' someFunction() { console.log(this.myProp) } }
这个提案处于非常早的状态,所以随着时间的推移,准备好调整你的语法。
由于你的问题主要是风格化的(不想用一堆声明填充构造函数),它也可以在风格上得到解决。
我查看它的方式,许多基于类的语言的构造函数是一个以类名称本身命名的函数。 在风格上,我们可以使用它来创build一个风格上仍然有意义的ES6类,但是不会将发生在构造函数中的典型动作与我们正在做的所有属性声明分组在一起。 我们简单地使用实际的JS构造函数作为“声明区域”,然后创build一个名为function的类,否则我们把它视为“其他构造函数的东西”区域,在真正的构造函数的末尾调用它。
“严格使用”; 类MyClass { //只声明你的属性,然后调用this.ClassName(); 从这里 构造(){ this.prop1 ='blah 1'; this.prop2 ='blah 2'; this.prop3 ='blah 3'; this.MyClass(); } //各种其他“构造函数”的东西,不再与声明混杂在一起 我的课() { doWhatever(); } }
这两个将被称为新实例构造。
Sorta就像有2个构造函数,你可以将声明和其他构造函数分离出来,并且在风格上使得它不难理解。
我发现在处理大量声明和/或需要在实例化时发生的大量操作时,使用它是一种很好的风格,并且希望保持两个想法彼此不同。
注 :我非常有目的地不使用“初始化”(如init()
或initialize()
方法)的典型习惯性的想法,因为这些经常使用不同的方式。 在构build和初始化的想法之间有一种推测的区别。 使用构造函数的人们知道,他们被自动调用为实例化的一部分。 看到一个init
方法,很多人都会不经意地假设他们需要沿着var mc = MyClass(); mc.init();
的forms做一些事情var mc = MyClass(); mc.init();
var mc = MyClass(); mc.init();
,因为这是你通常如何初始化。 我不想为类的用户添加初始化过程,我试图添加到类本身的构build过程。
虽然有些人可能会花一点时间,但实际上这只是一点点:它告诉他们,意图是build设的一部分,即使这使得他们做了一个双重的带走和离开“这不是ES6的构造函数是如何工作的“,然后花一点时间看看实际的构造函数”哦,他们把它叫做底层,我明白了“,这远比不传达意图(或不正确的沟通)要好得多,人们使用它错误,试图从外部和垃圾初始化它。 这对我所build议的模式非常有意义。
对于那些不想遵循这种模式的人来说,恰恰相反也可以起作用。 开始时将声明放到另一个函数中。 也许将其命名为“属性”或“publicProperties”等。 然后把其余的东西放在正常的构造函数中。
“严格使用”; 类MyClass { properties(){ this.prop1 ='blah 1'; this.prop2 ='blah 2'; this.prop3 ='blah 3'; } constructor(){ this.properties(); doWhatever(); } }
请注意,这第二种方法可能看起来更清洁,但它也有一个固有的问题,其中properties
被重写,因为使用此方法的一个类扩展了另一个类。 你必须给properties
更多的唯一名称,以避免这种情况。 我的第一个方法没有这个问题,因为它的假的一半的构造函数是唯一命名的类。
你可以模仿es6类的行为…并使用你的类variables:)
看妈妈…没有课!
// 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上
你可以通过使用强文字和一个更大的封闭中包含的模板逻辑运行的小库来避免整个问题?
现在忽略closures
const myDynamicInputs=(items)=>\backtick -${ items.map((item, I, array)=>'${do tons of junk}').join('')}';
http://codepen.io/jfrazz/pen/BQJPBZ/
这是我可以从存储库提供的最简单的例子。前400行是一个数据库+一些基本的实用function。 再加上一些实用常数。
在应用程序用户下载数据之后,我们有arrays模板,需要提取和重新部署,但是可以由input,下拉或52页组成的问题和数据。
这就是第二个例子:吃一个对象,得到各种types的input,全部使用const作为库的基本variables,被构造。
http://codepen.io/jfrazz/pen/rWprVR/
不完全是你问的,但清楚地表明,常数可以是非常dynamic的。
我解决这个问题的方法是另一种select(如果你有jQuery可用的话),就是定义老派对象中的字段,然后用该对象扩展类。 我也不想胡乱构造与任务,这似乎是一个整洁的解决scheme。
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(), }); } }
你仍然以“胖”构造函数结束,但至less它在一个类中的所有,并在一击中分配。
编辑#2:我现在已经完整的圈子,现在在构造函数中赋值,例如
class SavedSearch { constructor() { this.id = ''; this.type = ''; this.title = ''; this.createdAt = new Date(); } }
为什么? 真的很简单,使用上面加上一些JSdoc的意见,PHPStorm能够执行代码完成的属性。 在一个命中中分配所有的variables是很好的,但是不能完成属性的编程(imo)不值得(几乎肯定是极小的)性能优势。
这是一个静态的一些黑客组合,并为我工作
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)...