Self-references in object literals / initializers
有没有办法在JavaScript中使用以下内容?
1 2 3 4 5 | var foo = { a: 5, b: 6, c: this.a + this.b // Doesn't work }; |
在当前形式中,此代码显然会引发引用错误,因为
好吧,我唯一可以告诉你的是吸气剂:
1 2 3 4 5 6 7 8 9 | var foo = { a: 5, b: 6, get c() { return this.a + this.b; } } console.log(foo.c) // 11 |
这是ECMAScript第5版规范引入的语法扩展,大多数现代浏览器(包括IE9)都支持该语法。
你可以这样做:
1 2 3 4 5 6 7 8 | var foo = { a: 5, b: 6, init: function() { this.c = this.a + this.b; return this; } }.init(); |
这将是对象的某种一次初始化。
请注意,您实际上是将
缺少明显的简单答案,所以为了完整性:
But is there any way to have values in an object literal's properties depend on other properties declared earlier?
不会。这里的所有解决方案都会推迟到创建对象之后(以各种方式),然后分配第三个属性。最简单的方法就是这样做:
1 2 3 4 5 | var foo = { a: 5, b: 6 }; foo.c = foo.a + foo.b; |
所有其他人只是更间接的方式来做同样的事情。 (Felix's特别聪明,但需要创建和销毁临时函数,增加复杂性;要么在对象上留下额外的属性,要么[如果你
如果你需要它在一个表达式中,你可以在没有临时属性的情况下做到这一点:
1 2 3 4 | var foo = function(o) { o.c = o.a + o.b; return o; }({a: 5, b: 6}); |
或者当然,如果您需要多次执行此操作:
1 2 3 4 5 | function buildFoo(a, b) { var o = {a: a, b: b}; o.c = o.a + o.b; return o; } |
然后你需要使用它:
1 | var foo = buildFoo(5, 6); |
只需实例化一个匿名函数:
1 2 3 4 5 | var foo = new function () { this.a = 5; this.b = 6; this.c = this.a + this.b; }; |
现在在ES6中,您可以创建惰性缓存属性。首次使用时,属性将评估一次以成为正常的静态属性。结果:第二次跳过数学函数开销。
魔法在吸气剂中。
1 2 3 4 5 6 7 8 | const foo = { a: 5, b: 6, get c() { delete this.c; return this.c = this.a + this.b } }; |
在箭头getter
1 2 3 | foo // {a: 5, b: 6} foo.c // 11 foo // {a: 5, b: 6 , c: 11} |
有些关闭应该处理这个问题;
1 2 3 4 5 6 7 8 9 10 11 | var foo = function() { var a = 5; var b = 6; var c = a + b; return { a: a, b: b, c: c } }(); |
在
然后使用
你可以这样做
1 2 3 4 5 6 | var a, b var foo = { a: a = 5, b: b = 6, c: a + b } |
当我不得不引用最初声明函数的对象时,该方法对我有用。以下是我如何使用它的最小例子:
1 2 3 4 5 6 7 8 9 10 11 | function createMyObject() { var count = 0, self return { a: self = { log: function() { console.log(count++) return self } } } } |
通过将self定义为包含print函数的对象,您可以让函数引用该对象。这意味着如果您需要将打印功能传递给其他地方,则不必将打印功能"绑定"到对象上。
相反,如果您愿意,请使用
1 2 3 4 5 6 7 8 9 10 11 | function createMyObject() { var count = 0 return { a: { log: function() { console.log(count++) return this } } } } |
然后,以下代码将记录0,1,2,然后给出错误
1 2 3 4 | var o = createMyObject() var log = o.a.log o.a.log().log() // this refers to the o.a object so the chaining works log().log() // this refers to the window object so the chaining fails! |
通过使用self方法,您可以保证print始终返回相同的对象,而不管运行函数的上下文。当使用
为了完成,在ES6中我们有类(在撰写时仅支持最新的浏览器,但在Babel,TypeScript和其他转换器中可用)
1 2 3 4 5 6 7 8 9 | class Foo { constructor(){ this.a = 5; this.b = 6; this.c = this.a + this.b; } } const foo = new Foo(); |
有几种方法可以实现这一目标;这是我会用的:
1 2 3 4 5 6 7 8 | function Obj() { this.a = 5; this.b = this.a + 1; // return this; // commented out because this happens automatically } var o = new Obj(); o.b; // === 6 |
您可以使用模块模式执行此操作。就像:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | var foo = function() { var that = {}; that.a = 7; that.b = 6; that.c = function() { return that.a + that.b; } return that; }; var fooObject = foo(); fooObject.c(); //13 |
使用此模式,您可以根据需要实例化几个foo对象。
http://jsfiddle.net/jPNxY/1/
只是为了思考 - 在时间轴之外放置对象的属性:
1 2 3 4 5 6 7 | var foo = { a: function(){return 5}(), b: function(){return 6}(), c: function(){return this.a + this.b} } console.log(foo.c()) |
上面也有更好的答案。这就是我修改你所质疑的示例代码的方法。
更新:
1 2 3 4 5 6 | var foo = { get a(){return 5}, get b(){return 6}, get c(){return this.a + this.b} } // console.log(foo.c); |
在对象文字上创建新函数并调用构造函数似乎与原始问题完全不同,并且它是不必要的。
在对象文字初始化期间,您无法引用同级属性。
1 2 | var x = { a: 1, b: 2, c: a + b } // not defined var y = { a: 1, b: 2, c: y.a + y.b } // not defined |
计算属性的最简单解决方案如下(没有堆,没有函数,没有构造函数):
1 2 3 | var x = { a: 1, b: 2 }; x.c = x.a + x.b; // apply computed property |
所有这一切的关键是SCOPE。
您需要封装要定义的属性的"父"(父对象)作为它自己的实例化对象,然后您可以使用关键字
非常非常重要的是要记住,如果你在没有这么做的情况下引用
1 2 3 4 5 6 7 8 9 10 11 | var x = 9 //this is really window.x var bar = { x: 1, y: 2, foo: new function(){ this.a = 5, //assign value this.b = 6, this.c = this.a + this.b; // 11 }, z: this.x // 9 (not 1 as you might expect, b/c *this* refers `window` object) }; |
如果您的对象被写为返回对象的函数,并且您使用ES6对象属性'methods',则可能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | const module = (state) => ({ a: 1, oneThing() { state.b = state.b + this.a }, anotherThing() { this.oneThing(); state.c = state.b + this.a }, }); const store = {b: 10}; const root = module(store); root.oneThing(); console.log(store); root.anotherThing(); console.log(store); console.log(root, Object.keys(root), root.prototype); |
这是一个整洁的ES6方式:
1 2 3 4 5 6 7 8 9 | var foo = (o => ({ ...o, c: o.a + o.b }))({ a: 5, b: 6 }); console.log(foo); |
我用它来做这样的事情:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | const constants = Object.freeze( (_ => ({ ..._, flag_data: { [_.a_flag]: 'foo', [_.b_flag]: 'bar', [_.c_flag]: 'oof' } }))({ a_flag: 5, b_flag: 6, c_flag: 7, }) ); console.log(constants.flag_data[constants.b_flag]); |
这里发布的其他答案更好,但这里有一个替代方案:
- 设置初始化时的值(不是getter或派生等)
-
不需要任何类型的
init() 或对象文字之外的代码 - 是一个对象文字而不是工厂函数或其他对象创建机制。
- 不应该有任何性能影响(初始化除外)
自执行匿名功能和窗口存储
1 2 3 4 5 6 7 | var foo = { bar:(function(){ window.temp ="qwert"; return window.temp; })(), baz: window.temp }; |
订单有保证(
它当然会污染
它也很难看,但偶尔也很有用。例如,当您使用具有严格初始化条件的API并且不想重构时,因此范围是正确的。
当然,它很干燥。
其他方法是在为其分配属性之前先声明对象:
1 2 3 4 5 | const foo = {}; foo.a = 5; foo.b = 6; foo.c = foo.a + foo.b; // Does work foo.getSum = () => foo.a + foo.b + foo.c; // foo.getSum() === 22 |
这样,您可以使用对象变量名称来访问已分配的值。
最适合
我使用以下代码作为替代,它的工作原理。变量也可以是数组。 (@ Fausto R.)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | var foo = { a: 5, b: 6, c: function() { return this.a + this.b; }, d: [10,20,30], e: function(x) { this.d.push(x); return this.d; } }; foo.c(); // 11 foo.e(40); // foo.d = [10,20,30,40] |
这个解决方案怎么样也适用于带有数组的嵌套对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | Object.prototype.assignOwnProVal = function (to,from){ function compose(obj,string){ var parts = string.split('.'); var newObj = obj[parts[0]]; if(parts[1]){ parts.splice(0,1); var newString = parts.join('.'); return compose(newObj,newString); } return newObj; } this[to] = compose(this,from); } var obj = { name : 'Gaurav', temp : {id : [10,20], city: {street:'Brunswick'}} } obj.assignOwnProVal('street','temp.city.street'); obj.assignOwnProVal('myid','temp.id.1'); |
投入选项,因为我没有看到这个确切的情况。如果您不希望在
1 2 3 4 5 | var foo = ((a,b) => ({ a, b, c: a + b }))(a,b); |
根据我的需要,我有一个与最终用于循环的数组有关的对象,所以我只想计算一次常用设置,所以这就是我所拥有的:
1 2 3 4 5 6 | let processingState = ((indexOfSelectedTier) => ({ selectedTier, indexOfSelectedTier, hasUpperTierSelection: tiers.slice(0,indexOfSelectedTier) .some(t => pendingSelectedFiltersState[t.name]), }))(tiers.indexOf(selectedTier)); |
由于我需要为
为了使事情更简单并且不在对象中定义新函数,另一种方法是使原始对象保持其属性,使用动态属性创建第二个对象,该动态属性使用第一个对象的属性,然后使用spread进行合并。
所以:
宾语
1 2 3 4 | var foo = { a: 5, b: 6, }; |
对象2
1 2 3 | var foo2 = { c: foo.a + foo.b }; |
合并
1 | Object.assign(foo, foo2); |
要么
1 | foo = {...foo, ...foo2}; |
foo的结果:
1 2 3 4 5 | { "a":5, "b":6, "c":11 } |
所有在同一个js内,对象内没有新功能,只使用赋值或传播。
如果你想使用原生JS,其他答案提供了很好的解决方案。
但是,如果您更喜欢编写自引用对象,例如:
1 2 3 4 | { a: ..., b:"${this.a + this.a}", } |
我写了一个名为self-referenced-object的npm库,它支持该语法并返回一个本机对象。
Note: This solution uses Typescript (you can use the vanilla JS which TS compiles to if needed)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class asd { def = new class { ads= 'asd'; qwe= this.ads + '123'; }; // this method is just to check/test this solution check(){ console.log(this.def.qwe); } } // these two lines are just to check let instance = new asd(); instance.check(); |
这里使用类表达式来获取我们想要的嵌套对象文字界面。这是IMHO在创建过程中能够引用对象属性的下一个最好的事情。
需要注意的主要事项是在使用此解决方案时,您拥有与对象文字完全相同的界面。语法非常接近对象文字本身(与使用函数等)。
比较以下内容
解决方案我提出了
1 2 3 4 5 | class asd { def = new class { ads= 'asd'; qwe= this.ads + '123'; }; |
解决方案,如果对象文字已经足够了
1 2 3 4 5 6 | var asd = { def : { ads:'asd', qwe: this.ads + '123';, //ILLEGAL CODE; just to show ideal scenario } } |
另一个例子
在这个类中,您可以在它们之间组合多个相对路径,这对于对象文字是不可能的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class CONSTANT { static readonly PATH = new class { /** private visibility because these relative paths don't make sense for direct access, they're only useful to path class * */ private readonly RELATIVE = new class { readonly AFTER_EFFECTS_TEMPLATE_BINARY_VERSION: fs.PathLike = '\\assets\\aep-template\\src\\video-template.aep'; readonly AFTER_EFFECTS_TEMPLATE_XML_VERSION: fs.PathLike = '\\assets\\aep-template\\intermediates\\video-template.aepx'; readonly RELATIVE_PATH_TO_AFTER_EFFECTS: fs.PathLike = '\\Adobe\\Adobe After Effects CC 2018\\Support Files\\AfterFX.exe'; readonly OUTPUT_DIRECTORY_NAME: fs.PathLike = '\\output'; readonly INPUT_DIRECTORY_NAME: fs.PathLike = '\\input'; readonly ASSETS_DIRECTORY_NAME: fs.PathLike = '\\assets'; }; } } |