对象文字/初始化器中的自引用

有没有办法让下面的东西在JavaScript中工作?

var foo = {a: 5,b: 6,c: this.a + this.b  // Doesn't work};

在当前形式中,这段代码显然抛出了一个引用错误,因为this不引用foo。但是有没有办法让对象文字属性中的值依赖于之前声明的其他属性?

192665 次浏览

好吧,我唯一能告诉你的是吸气剂

var foo = {a: 5,b: 6,get c() {return this.a + this.b;}}
console.log(foo.c) // 11

这是ECMAScript第5版规范引入的语法扩展,大多数现代浏览器(包括IE9)都支持该语法。

你可以这样做:

var foo = {a: 5,b: 6,init: function() {this.c = this.a + this.b;return this;}}.init();

这将是对象的某种一次性初始化。

请注意,您实际上是将init()的返回值分配给foo,因此您必须return this

有几种方法可以做到这一点;这就是我要使用的:

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

显而易见的,简单的答案是缺失的,所以为了完整性:

但是有没有办法让对象文字的属性中的值依赖于前面声明的其他属性?

不。这里的所有解决方案都将其推迟到创建对象之后(以各种方式),然后分配第三个属性。最简单的方法是这样做:

var foo = {a: 5,b: 6};foo.c = foo.a + foo.b;

所有其他的只是做同样的事情的更间接的方式。(费利克斯的特别聪明,但需要创建和破坏一个临时函数,增加了复杂性;要么在对象上留下一个额外的属性,要么[如果你delete该属性]对该对象进行后续属性访问的影响性能。)

如果你需要它都在一个表达式中,你可以在没有临时属性的情况下做到这一点:

var foo = function(o) {o.c = o.a + o.b;return o;}({a: 5, b: 6});

当然,如果您需要多次执行此操作:

function buildFoo(a, b) {var o = {a: a, b: b};o.c = o.a + o.b;return o;}

那么你需要在哪里使用它:

var foo = buildFoo(5, 6);

你可以使用模块模式来完成它。就像:

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/

简单地实例化一个匿名函数:

var foo = new function () {this.a = 5;this.b = 6;this.c = this.a + this.b;};

你可以像这样做

var a, bvar foo = {a: a = 5,b: b = 6,c: a + b}

当我不得不引用最初声明函数的对象时,该方法对我很有用。以下是我如何使用它的最小示例:

function createMyObject() {var count = 0, selfreturn {a: self = {log: function() {console.log(count++)return self}}}}

通过将self定义为包含print函数的对象,您允许该函数引用该对象。这意味着如果您需要将打印函数传递到其他地方,您不必将打印函数“绑定”到对象。

如果您愿意,请使用this,如下所示

function createMyObject() {var count = 0return {a: {log: function() {console.log(count++)return this}}}}

然后以下代码将记录0、1、2,然后给出错误

var o = createMyObject()var log = o.a.logo.a.log().log() // this refers to the o.a object so the chaining workslog().log() // this refers to the window object so the chaining fails!

通过使用self方法,您可以保证print将始终返回相同的对象,而不管函数运行的上下文如何。使用createMyObject()的self版本时,上面的代码运行良好并记录0、1、2和3。

一些闭包应该处理这个问题;

var foo = function() {var a = 5;var b = 6;var c = a + b;
return {a: a,b: b,c: c}}();

所有在foo中声明的变量对foo都是私有的,正如你对任何函数声明所期望的那样,而且因为它们都在范围内,所以它们都可以相互访问,而无需引用this,就像你对函数所期望的那样。不同的是,这个函数返回一个公开私有变量的对象,并将该对象分配给foo。最后,你只返回你想用return {}语句公开为对象的接口。

然后使用()在最后执行该函数,这会导致整个foo对象被评估,实例化中的所有变量并将返回对象添加为foo()的属性。

这一切的关键是范围

您需要将要定义的属性的“父”(父对象)封装为它自己的实例化对象,然后您可以使用关键字this引用兄弟属性

非常非常重要要记住,如果你引用this而没有首先这样做,那么this将引用外部范围……这将是window对象。

var x = 9   //this is really window.xvar bar = {x: 1,y: 2,foo: new function(){this.a = 5, //assign valuethis.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)};

在对象字面量上创建新函数并调用构造函数似乎与原始问题完全不同,这是不必要的。

在对象文字初始化期间不能引用兄弟属性。

var x = { a: 1, b: 2, c: a + b } // not definedvar y = { a: 1, b: 2, c: y.a + y.b } // not defined

计算属性的最简单解决方案如下(没有堆,没有函数,没有构造函数):

var x = { a: 1, b: 2 };
x.c = x.a + x.b; // apply computed property

这里发布的其他答案更好,但这里有一个替代方案:

  • 设置初始化时的值(不是getter或派生等)
  • 不需要任何类型的init()或对象文字之外的代码
  • 是对象文字,而不是工厂函数或其他对象创建机制。
  • 不应该有任何性能影响(除了初始化)

自执行匿名函数和窗口存储

var foo = {bar:(function(){window.temp = "qwert";return window.temp;})(),baz: window.temp};

顺序是保证barbaz之前)。

当然,它会污染window,但我无法想象有人编写一个要求window.temp坚持不懈的脚本。如果你是偏执狂,也许是tempMyApp

它也很丑,但偶尔有用。一个例子是,当您使用具有严格初始化条件的API并且不喜欢重构时,因此范围是正确的。

它是干燥的,当然。

只是为了从时间轴中提取思想位置对象的属性:

var foo = {a: function(){return 5}(),b: function(){return 6}(),c: function(){return this.a + this.b}}
console.log(foo.c())

上面也有更好的答案。这就是我修改你质疑的示例代码的方式。

更新:

var foo = {get a(){return 5},get b(){return 6},get c(){return this.a + this.b}}// console.log(foo.c);

为了完成,在ES6中我们有类(在撰写本文时仅受最新浏览器的支持,但在Babel,TypeScript和其他转译器中可用)

class Foo {constructor(){this.a = 5;this.b = 6;this.c = this.a + this.b;}}
const foo = new Foo();

我使用以下代码作为替代,它有效。变量也可以是数组。(@Fausto R.)

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(); // 11foo.e(40); // foo.d = [10,20,30,40]

如果您的对象被编写为返回对象的函数,并且您使用ES6对象属性“方法”,那么可能:

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中,您可以创建延迟缓存属性。第一次使用时,该属性计算一次以成为正常的静态属性。结果:第二次跳过数学函数开销。

魔法就在吸气剂里。

const foo = {a: 5,b: 6,get c() {delete this.c;return this.c = this.a + this.b}};

在箭头中,getterthis选择围词范围

foo     // {a: 5, b: 6}foo.c   // 11foo     // {a: 5, b: 6 , c: 11}

这个解决方案怎么样,这也适用于带有数组的嵌套对象

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

抛出一个选项,因为我没有看到这个确切的场景。如果您希望在ab更新时更新c,那么ES6 IIFE效果很好。

var foo = ((a,b) => ({a,b,c: a + b}))(a,b);

为了我的需要,我有一个与数组相关的对象,该数组最终将在循环中使用,所以我只想计算一些常见的设置一次,所以这就是我所拥有的:

let processingState = ((indexOfSelectedTier) => ({selectedTier,indexOfSelectedTier,hasUpperTierSelection: tiers.slice(0,indexOfSelectedTier).some(t => pendingSelectedFiltersState[t.name]),}))(tiers.indexOf(selectedTier));

由于我需要为indexOfSelectedTier设置一个属性,并且在设置hasUpperTierSelection属性时需要使用该值,因此我首先计算该值并将其作为参数传递给IIFE

这里有一个简洁的ES6方法:

var foo = (o => ({...o,c: o.a + o.b}))({a: 5,b: 6});  
console.log(foo);

我用它来做这样的事情:

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

备注:此解决方案使用TypeScript(如果需要,您可以使用TS编译的vanilla JS)

class asd {def = new class {ads= 'asd';qwe= this.ads + '123';};
// this method is just to check/test this solutioncheck(){console.log(this.def.qwe);}}
// these two lines are just to checklet instance = new asd();instance.check();

这里使用类表达式来获得我们想要的嵌套对象文字接口。这是在创建过程中能够引用对象属性的下一个最好的事情。

需要注意的主要事情是,在使用此解决方案时,您拥有与对象文字完全相同的界面。并且语法非常接近对象文字本身(与使用函数等相比)。

比较以下

我提出的解决方案

class asd {def = new class {ads= 'asd';qwe= this.ads + '123';};

如果对象字面量就足够了,则解决方案

var asd = {def : {ads:'asd',qwe: this.ads + '123';, //ILLEGAL CODE; just to show ideal scenario}}

另一个例子

在这个类中,您可以在它们之间组合多个相对路径,这对于对象文字是不可能的。

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';};}}

如果你想使用原生JS,其他答案提供了很好的解决方案。

但是,如果您愿意编写自引用对象,例如:

{a: ...,b: "${this.a + this.a}",}

我编写了一个名为自引用对象的npm库,它支持该语法并返回一个本机对象。

另一种方法是在将属性分配给对象之前先声明对象:

const foo = {};foo.a = 5;foo.b = 6;foo.c = foo.a + foo.b;  // Does workfoo.getSum = () => foo.a + foo.b + foo.c;  // foo.getSum() === 22

这样,您就可以使用对象变量名称来访问已经分配的值。
最适合config.js文件。

var x = {a: (window.secreta = 5),b: (window.secretb = 6),c: window.secreta + window.secretb};

这与@slicedtoad的答案几乎相同,但不使用函数。

get属性工作得很好,您还可以对应该只运行一次的“昂贵”函数使用绑定闭包(这只适用于#1,不适用于#2或#3)

var info = {address: (function() {return databaseLookup(this.id)}).bind(info)(),
get fullName() {console.log('computing fullName...')return `${this.first} ${this.last}`},
id: '555-22-9999',first: 'First',last: 'Last',}
function databaseLookup() {console.log('fetching address from remote server (runs once)...')return Promise.resolve(`22 Main St, City, Country`)}
// test(async () => {console.log(info.fullName)console.log(info.fullName)console.log(await info.address)console.log(await info.address)console.log(await info.address)console.log(await info.address)})()

两种懒惰的解决方案

这里已经有了很好的答案,我不是这方面的专家,但我是懒惰的专家,在我的专家眼中,这些答案似乎还不够懒惰。

第一:从匿名函数返回对象

T. J.克劳德Henry Wrightsonrafaelrocha答案非常轻微的变化:

var foo = (() => {
// Paste in your original objectconst foo = {a: 5,b: 6,};  
// Use their propertiesfoo.c = foo.a + foo.b;
// Do whatever else you want
// Finally, return objectreturn foo;})();
console.log(foo);

这里的小优点是只需按原样粘贴原始对象,而不必担心参数等(IMHO包装函数通过这种方式变得非常透明)。

第二:使用setTimeout

如果你不需要foo.c,这里可能会起作用:

var foo = {a: 5,b: 6,c: setTimeout(() => foo.c = foo.a + foo.b, 0)};
// Though, at first, foo.c will be the integer returned by setTimeoutconsole.log(foo);// But if this isn't an issue, the value will be updated when time comes in the event loopsetTimeout( () => console.log(foo), 0);

这是对象中'this'的行为示例。

this.prop = 'external';global.prop = 'global.prop';const that = this;
const a = {prop: 'internal',prop1: this.prop, //external
log() {return this.prop //internal},log1: () => {return this.prop //external},log2: () => {return function () {return this.prop; //'global.prop' in node; 'external' in chrome}()},log3: function () {return (() => {return this.prop; //internal})()},}

只是为了大家的娱乐:

var foo = (                        (This={a: 5,b: 6,                          })=>({...This,c: This.a + This.b             }))();
console.log(foo);

好的,我想到了另一个解决方案。在这里,我想初始化一个表示每个时间单位的毫秒数的对象。事实证明,在我的情况下不能使用打字稿中的枚举,所以我声明了多个变量,我分配给一个对象如下:

const SECOND = 1000const MINUTE = 60 * SECONDconst HOUR = 60 * MINUTEconst DAY = 24 * HOURconst WEEK = 7 * DAY
export const TimeInMS = {SECOND,MINUTE,HOUR,DAY,WEEK}

这种方法的缺点是:

  • 变量被定义为常量,即使我们不需要它们。因此它需要无用的内存。
  • 对象的每个值都必须声明为独立变量

我认为以下是可维护性的最佳代码,即使它不是对象文字语法:

var foo = function() {this.a = 5;this.b = 6;this.c = this.a + this.b;return this;}.call({});

这将创建一个带有{}的新空对象,然后使用匿名函数设置其属性(使用call()执行)。我认为唯一不好的部分是需要return this,这感觉就像额外的一行代码。不幸的是,我找不到任何更好的方法来将对新创建的匿名对象的引用移动到foo

我认为这比语法var foo = new function() {...}更好,因为它没有在原型链中创建一个额外的级别,正如@Bergi在现有答案之一的注释中解释的那样。

但是,如果这是真正的文字,没有任何其他逻辑,只有一个添加,那么只写

const foo = {a:5,b:6,c:11, // sum of a + b};

因为在运行时甚至编译时都不需要计算这个总和。

具有良好可维护性的替代语法:

let a = 5;let b = 6;let foo = {a,b,c: a+b,};

这之所以有效,是因为如果您没有显式指定名称,JavaScript将使用变量名称作为新创建对象的属性名称。对于这样的短数组,如果这是在函数内部,我个人会使用return的单行语法:

let a = 5;let b = 6;return { a, b, c:a+b };