【发布时间】:2019-09-23 05:06:02
【问题描述】:
有什么方法可以让下面的东西在 JavaScript 中工作?
var foo = {
a: 5,
b: 6,
c: this.a + this.b // Doesn't work
};
在当前的形式中,由于this 没有引用foo,这段代码显然会抛出一个引用错误。但是是否有什么方法可以让对象字面量的属性中的值依赖于之前声明的其他属性?
【问题讨论】:
有什么方法可以让下面的东西在 JavaScript 中工作?
var foo = {
a: 5,
b: 6,
c: this.a + this.b // Doesn't work
};
在当前的形式中,由于this 没有引用foo,这段代码显然会抛出一个引用错误。但是是否有什么方法可以让对象字面量的属性中的值依赖于之前声明的其他属性?
【问题讨论】:
嗯,我唯一能告诉你的就是getter:
var foo = {
a: 5,
b: 6,
get c() {
return this.a + this.b;
}
}
console.log(foo.c) // 11
这是 ECMAScript 第 5 版规范引入的语法扩展,大多数现代浏览器(包括 IE9)都支持该语法。
【讨论】:
foo.a 或foo.b 的值发生更改,则foo.c 的值也会同步更改。这可能是也可能不是所需要的。
this 绑定到最深的嵌套对象。例如:... x: { get c () { /*this is x, not foo*/ } } ...
foo被声明为一个变量并且c只会在它被调用的时候被评估,在foo里面使用c会起作用,而不是this(不过要小心)
你可以这样做:
var foo = {
a: 5,
b: 6,
init: function() {
this.c = this.a + this.b;
return this;
}
}.init();
这将是对象的某种一次性初始化。
请注意,您实际上是将init() 的返回值分配给foo,因此您必须为return this。
【讨论】:
return this之前delete this.init,这样foo就不会被污染了
init() 调用直接附加了字面量以保持单个表达式。但是当然你可以单独调用你想要的函数。
缺少明显、简单的答案,因此为了完整性:
但是是有没有办法让对象字面量的属性中的值依赖于之前声明的其他属性?
没有。这里的所有解决方案都将其推迟到创建对象之后(以各种方式),然后分配第三个属性。 最简单的方法就是这样做:
var foo = {
a: 5,
b: 6
};
foo.c = foo.a + foo.b;
所有其他方法只是做同样事情的更间接的方式。 (Felix 特别聪明,但需要创建和销毁一个临时函数,增加了复杂性;并且要么在对象上留下额外的属性,要么 [如果你 delete 该属性] impacts the performance 对该对象的后续属性访问。)
如果你需要它全部在一个表达式中,你可以在没有临时属性的情况下做到这一点:
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 仅适用于 methods表示对象,没有其他种类的属性。知道我在哪里可以找到吗?谢谢!
delete 快 10%,而 gecko : delete 仅慢 1%。
只需实例化一个匿名函数:
var foo = new function () {
this.a = 5;
this.b = 6;
this.c = this.a + this.b;
};
【讨论】:
new Point(x, y) 之类的参数没有什么不同,只是该函数不是为重用而命名的。
var foo = function() { this.…; return this; }.call({});,它在语法上并没有太大区别,但在语义上是合理的。
new 关键字。
现在在 ES6 中,您可以创建惰性缓存属性。首次使用时,该属性会评估一次以成为正常的静态属性。结果:第二次跳过了数学函数开销。
魔术在吸气剂中。
const foo = {
a: 5,
b: 6,
get c() {
delete this.c;
return this.c = this.a + this.b
}
};
在箭头获取器this 中选择surrounding lexical scope。
foo // {a: 5, b: 6}
foo.c // 11
foo // {a: 5, b: 6 , c: 11}
【讨论】:
Object.defineProperty(foo, 'c', {get:function() {...}}) 来定义它们。在这样的工厂中,这很容易以一种不显眼的方式完成。当然,如果您可以使用 get 糖,它的可读性更高,但功能已经存在。
delete this.c,但没有用
一些闭包应该解决这个问题;
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() 的属性。
【讨论】:
你可以这样做
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 将始终返回相同的对象,而不管函数运行的上下文。当使用 createMyObject() 的 self 版本时,上面的代码将运行良好并记录 0、1、2 和 3。
【讨论】:
为了完成,在 ES6 中我们有类(在撰写本文时仅支持最新的浏览器,但在 Babel、TypeScript 和其他转译器中可用)
class Foo {
constructor(){
this.a = 5;
this.b = 6;
this.c = this.a + this.b;
}
}
const foo = new Foo();
【讨论】:
只是为了思考 - 将对象的属性放在时间轴之外:
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);
【讨论】:
var foo = { get a(){return 5}, get b(){return 6}, get c(){return this.a + this.b} } 所以现在您可以使用 foo.c 而不是 foo.c() :) (请随意将其粘贴到您的答案中,以便格式化更好!)
您可以使用模块模式来做到这一点。就像:
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 对象。
【讨论】:
}();,它将自行执行并返回一个对象,而不是一个函数。此外,foo.c 是一个函数,因此写入它会破坏该函数,并且通过 fooObject.c() 进行的下一次调用将失败。也许这个fiddle 更接近你想要的(它也是一个单例,不是为了实例化而设计的)。
有几种方法可以做到这一点;这就是我要使用的:
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 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
【讨论】:
此处发布的其他答案更好,但这是另一种选择:
init() 或对象字面量之外的代码自执行匿名函数和窗口存储
var foo = {
bar:(function(){
window.temp = "qwert";
return window.temp;
})(),
baz: window.temp
};
顺序是guaranteed(bar 在baz 之前)。
它当然会污染window,但我无法想象有人编写需要window.temp 才能持久的脚本。如果你偏执,也许tempMyApp。
它也很丑,但偶尔有用。例如,当您使用的 API 具有严格的初始化条件并且不想重构,因此范围界定是正确的。
当然是干的。
【讨论】:
这一切的关键是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)
};
【讨论】:
如果您的对象被编写为返回对象的函数,并且您使用 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);
【讨论】:
我使用以下代码作为替代,它可以工作。变量也可以是数组。 (@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(); // 11
foo.e(40); // foo.d = [10,20,30,40]
【讨论】:
这是一个简洁的 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]);
【讨论】:
get 属性效果很好,您还可以为“昂贵”的函数使用绑定闭包,这些函数只能运行一次 (这只适用于 var,不适用于 const 或 let )
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)
})()
【讨论】:
这个解决方案怎么样,它也适用于带有数组的嵌套对象
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');
【讨论】:
加入一个选项,因为我没有看到这个确切的场景。如果您不希望在 a 或 b 更新时更新 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
【讨论】:
其他方法是先声明对象,然后再为其分配属性:
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 的引用,它指向有问题的对象。
如果你想使用原生 JS,其他答案提供了很好的解决方案。
但如果您愿意编写自引用对象,例如:
{
a: ...,
b: "${this.a + this.a}",
}
我编写了一个名为 self-referenced-object 的 npm 库,它支持该语法并返回一个原生对象。
【讨论】:
b 属性值应为:${this.a + this.a}。其次,但不太重要的是,您希望使用parseInt 之类的东西返回一个数字,而不是字符串。最后也是最重要的是,当我尝试这个示例时,它根本不起作用,原因与 OP 要求的原因相同。 this 在使用它自己的对象声明时返回 undefined。 @alex-e-leon
这是对象中“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
})()
},
}
【讨论】:
注意:此解决方案使用 Typescript(如果需要,您可以使用 TS 编译成的 vanilla 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';
};
}
}
【讨论】:
这里已经有很好的答案,我不是这方面的专家,但我是懒惰专家,在我的专家眼中,这些答案似乎不够懒惰。
答案与T.J. Crowder、Henry Wrightson 和Rafael Rocha 略有不同:
var foo = (() => {
// Paste in your original object
const foo = {
a: 5,
b: 6,
};
// Use their properties
foo.c = foo.a + foo.b;
// Do whatever else you want
// Finally, return object
return foo;
})();
console.log(foo);
这里的轻微优势只是粘贴原始对象,而不用担心参数等。(恕我直言,包装函数通过这种方式变得非常透明)。
如果您不需要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 setTimeout
console.log(foo);
// But if this isn't an issue, the value will be updated when time comes in the event loop
setTimeout( () => console.log(foo), 0);
【讨论】:
var x = {
a: (window.secreta = 5),
b: (window.secretb = 6),
c: window.secreta + window.secretb
};
这与@slicedtoad 的答案几乎相同,但不使用函数。
【讨论】: