对象常量/初始化程序中的自引用


705

有什么办法可以使以下内容在JavaScript中起作用?

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

在当前形式下,此代码显然会引发引用错误,因为this未引用foo。但是,是否有任何方法可以使对象文字的属性值取决于先前声明的其他属性?

Answers:


741

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

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

console.log(foo.c) // 11

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


32
很有帮助的答案。可以在这里找到有关“获取”的更多信息:developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/…–
jake

5
@FaustoR。是的,它确实。
查理·马丁

48
请注意,使用此解决方案时,如果foo.a或的值foo.b被更改,则的值foo.c也将同步更改。这可能是必需的,也可能不是。
HBP

8
请注意,它this绑定到最深的嵌套对象。例如:... x: { get c () { /*this is x, not foo*/ } } ...
Z. Khullah '18

3
为了完成我的上述声明,由于foobeeing被声明为变量,并且c仅在调用它时才被评估,因此使用fooinside c可以起作用,而不是this(尽管要小心)
Z. Khullah

316

您可以执行以下操作:

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

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

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


95
你也可以delete this.init之前return this,这样foo是不是poluted
比利月

15
@BillyMoon:是的,的确如此,尽管这样做会影响该对象(例如V8)上对该对象的所有后续属性访问的性能
TJ Crowder 2014年

8
@MuhammadUmer:不确定ES6类与该问题如何相关。
Felix Kling 2015年

8
@MuhammadUmer:类只是构造函数的语法糖,因此它们并没有真正提供任何新功能。无论哪种方式,这个问题的主要焦点都是对象文字。
菲利克斯·克林

3
@akantoword:太棒了:)由于对象文字是单个表达式,因此将init()调用直接附加到文字之后以使其保持单个表达式。但是当然您可以根据需要单独调用该函数。
菲利克斯·克林

181

缺少显而易见的简单答案,因此出于完整性考虑:

但是,是否有任何方法可以使对象文字的属性值取决于先前声明的其他属性?

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

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

所有其他方法只是做同一件事的更间接的方法。(Felix的技巧特别聪明,但是需要创建和销毁一个临时函数,从而增加了复杂性;并且要么在对象上留下额外的属性,要么[如果您使用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);

为了我自己的理智,我试图找到某种官方文件,它们说的基本上是同一件事-一个对象的this只能用于该对象的方法,而不能使用其他类型的属性。知道在哪里可以找到吗?谢谢!
大卫·肯纳尔

1
@DavidKennell:没有比规范更正式的。:-)您可能会从这里开始并逐步进行。这是一种相当棘手的语言,但是基本上,您会在“ 属性定义评估”的各个子句中看到,对象无法用于确定属性初始值设定项的操作。
TJ Crowder

在这里看不到browserscope的结果,但是情况不再正确了吗?在我的环境中,v8:delete快10%,gecko:delete慢1%。
TheMaster

1
@TheMaster-是的,我认为BrowserScope不再是真正的东西。看起来删除并不像以前那样糟糕,至少在V8(Chrome等)或SpiderMonkey中没有。仍然比较慢,但是只有一点点,这些天来这些事情发展得很快。
TJ Crowder

63

只需实例化一个匿名函数:

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

1
@Bergi,为什么?因为有人可能实例化同一对象的另一个对象?并不是说他们不能只是克隆对象文字。与传递类似这样的参数没有什么不同,new Point(x, y)除了函数没有为重用而命名。
zzzzBov 2015年

1
@zzzzBov:当然,他们只能克隆对象,但是与IEFE解决方案(如TJCrowder的回答一样)相比,您的解决方案会泄漏构造函数并创建多余的原型对象。
Bergi

5
@zzzzBov:只需使用var foo = function() { this.…; return this; }.call({});语法上没有太大区别但在语义上理智的即可。
Bergi

1
@Bergi,如果您觉得那很重要,为什么不添加自己的答案呢?
zzzzBov

3
你有这个 我确实没有注意到new关键字。
兰迪

26

现在,在ES6中,您可以创建惰性缓存的属性。首次使用时,该属性会评估一次,成为普通的静态属性。结果:第二次跳过数学函数开销。

魔术是吸气剂。

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

箭头中的getter this拾取了周围的词汇范围

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

1
es5还具有您只需要Object.defineProperty(foo, 'c', {get:function() {...}})用来定义它们的属性。在这样的工厂中,这很容易以不干扰的方式完成。当然,如果可以使用get糖,它的可读性更高,但功能已经存在。
Aluan Haddad

21

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

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


14
将其称为“关闭”是令人困惑和误导的。尽管对于从函数返回对象值的确切含义存在不同意见,但这并不构成任何人的著作的闭包。

16

你可以这样

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

当我不得不引用最初声明函数的对象时,该方法对我来说非常有用。以下是我的用法的最小示例:

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

通过将self定义为包含打印功能的对象,可以允许该功能引用该对象。这意味着如果您需要将打印功能传递到其他对象,则无需将其“绑定”到对象。

如果可以的话,请this按如下所示使用

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

然后下面的代码将记录0、1、2,然后给出一个错误

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始终返回相同的对象,而不管函数在哪个上下文中运行。当使用的自我版本时,上面的代码可以正常运行并记录0、1、2和3 createMyObject()


3
很好的例子,除了您省略了所有分号。


9

为了完成,在ES6中我们有一些类(仅在最新的浏览器中才支持,但在Babel,TypeScript和其他编译器中可用)

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

const foo = new Foo();

7

您可以使用模块模式来实现。就像:

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/


2
这不是模块模式的示例,而只是一个函数。如果foo定义的最后一行是}();,它将自行执行并返回一个对象,而不是一个函数。同样,它foo.c是一个函数,因此向其写入该函数和通过的下一次调用fooObject.c()将失败。也许这个小提琴与您要使用的东西更接近(它也是单例,并非旨在实例化)。
Hollister

2
“模块模式最初被定义为为常规软件工程中的类提供私有和公共封装的一种方式”。来自:学习JavaScript设计模式。该对象遵循上述模块模式,但可能不是最好的解释,因为它没有显示公共和私有属性/方法。这个jsfiddle.net/9nnR5/2是具有公共和私有属性/方法的同一对象。所以他们俩都遵循这种模式
Rafael Rocha 2014年

6

有几种方法可以做到这一点。这就是我会用的:

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

2
这有效,但是消除了对象文字表示法的优点。
kpozin 2011年

是的,很抱歉,我最初错过了对象文字标签。我通常只将对象文字用于数据结构,并且每当我想要任何其他逻辑(可能类似于类)时,由于这个原因,我将对象作为函数的结果来创建。

6

只是为了考虑-将对象的属性放在时间轴之外:

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

2
在ES6中,您可以使这种通用方法更加优雅:var foo = { get a(){return 5}, get b(){return 6}, get c(){return this.a + this.b} }因此,您现在可以foo.c代替foo.c():)(随意将其粘贴到您的答案中,以便更好地格式化!)

5

在对象文字上创建新函数并调用构造函数似乎与原始问题大相径庭,这是不必要的。

在对象文字初始化期间,您不能引用同级属性。

var x = { a: 1, b: 2, c: a + b } // not defined 
var 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

3

此处发布的其他答案更好,但这是一个替代方法:

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

自执行匿名功能和窗口存储

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

订单得到保证bar之前baz)。

window当然会污染,但我无法想象有人编写需要window.temp持久化的脚本。也许tempMyApp你是偏执狂。

它也很丑陋,但偶尔有用。一个示例是当您使用具有严格初始化条件的API且不想进行重构时,因此作用域是正确的。

当然是干的。


3

这一切的关键是SCOPE

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

要记住,非常重要的一点是,如果您this不先进行this引用便会引用外部范围……这就是window对象。

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

2
这甚至不是有效的JS。

2

如果将您的对象编写为返回对象的函数,并且使用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);

2

我使用以下代码作为替代,并且可以正常工作。并且变量也可以是数组。(@福斯托·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(); // 11
foo.e(40); // foo.d = [10,20,30,40]

2

这是一种整洁的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]);


1

这个解决方案如何也可以用于带有数组的嵌套对象

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

0

因为我没有看到这个确切的场景,所以选择了一个选项。如果你不做c更新时ab更新,那么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


0

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

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

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


那不是自我引用,而是对foo指向所讨论对象的已声明变量的引用。
德里克·亨德森

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

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


-1

注意:此解决方案使用Typescript(如果需要,您可以使用TS编译到的原始JS)

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

这里使用类表达式来获取我们想要的嵌套对象文字接口。恕我直言,这是在创建过程中能够引用对象属性的第二件事。

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

比较以下

我提出的解决方案

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

2
可能是因为您的答案完全不相关吗?我同意
下降投票者

@Manngo感谢您指出这一点。老实说,我的问题与OP相同,我使用的是我建议的解决方案。不确定为何将其视为无关紧要。如果您有时间,请解释一下,这样我可以使答案更好,或者至少知道我错了。不幸的是,我不理解为什么这不是一个合理的解决方案。
Dheeraj Bhaskar '18

-2

如果要使用本机JS,则其他答案将提供良好的解决方案。

但是,如果您愿意编写类似以下内容的自引用对象:

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

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


1
避免仅链接答案。答案是“勉强超过一个链接到外部网站” 可能会被删除
昆汀

@Quentin您对我如何改善答案有任何建议吗?该问题的其他答案包括您如何能够使用本机javascript编写自引用对象,但是如果您要使用与张贴者原始问题中的语法相似的语法编写自引用对象,我认为该库我写的文章可能对其他寻求解决方案的人有用。很高兴获得一些反馈。
alex-e-leon

这里有几处事情可以改善。首先,最明显的是,您使用的模板字面量语法没有反斜线。您的b财产的价值应为:${this.a + this.a}。其次,但更不重要的是,您想使用来返回数字,而不是字符串parseInt。最后一点,也是最重要的一点,当我尝试这个示例时,由于OP询问的相同原因,它根本不起作用。this使用自己的对象声明时,返回undefined。@ alex-e-leon
亚历克·唐纳德·马瑟

@AlecDonaldMather-感谢您抽出宝贵时间来看看并提供一些反馈!如果您对该项目感兴趣,最好将讨论移至github,但要回答您的一些反馈:-使用反引号:如前面的评论所述,此语法不受JS支持,因此可以使用字符串代替这里需要使用反引号,以避免js在定义obj之前尝试解析“ this”-返回数字,如果a + b已经是数字,这应该可以工作,因为如果a和b都为a时,a + b将返回数字已经编号。
alex-e-leon

关于返回的未定义,您能解释一下如何尝试使用该库吗?这不应该发生,但是也许我有一个极端的情况?就是说,这个库并不能完全解决问题,而是要权衡取舍,但是如果您有兴趣帮助我进行改进/使用,请告诉我!
alex-e-leon
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.