Самостоятельные ссылки в объектных литералах / инициализаторах
Есть ли способ заставить что-то вроде следующего работать в JavaScript?
var foo = {
a: 5,
b: 6,
c: this.a + this.b // Doesn't work
};
В текущей форме этот код явно генерирует ошибку ссылки, так как this
не относится к foo
, Но есть ли способ, чтобы значения в свойствах литерала объекта зависели от других свойств, объявленных ранее?
33 ответа
Ну, единственное, о чем я могу вам рассказать - это геттеры:
var foo = {
a: 5,
b: 6,
get c () {
return this.a + this.b;
}
};
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
,
Очевидный, простой ответ отсутствует, поэтому для полноты:
Но есть ли способ, чтобы значения в свойствах литерала объекта зависели от других свойств, объявленных ранее?
Нет. Все решения здесь откладывают его до тех пор, пока объект не будет создан (различными способами), а затем присваивают третье свойство. Самый простой способ - просто сделать это:
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 = new function () {
this.a = 5;
this.b = 6;
this.c = this.a + this.b;
};
Теперь в ES6 вы можете создавать ленивые кешированные свойства. При первом использовании свойство оценивается один раз, чтобы стать обычным статическим свойством. Результат: второй раз, когда математические функции пропускаются.
Магия в добытчике.
const foo = {
a: 5,
b: 6,
get c() {
delete this.c;
return this.c = this.a + this.b
}
};
В стрелке добытчик this
подбирает окружающий лексический прицел.
foo // {a: 5, b: 6}
foo.c // 11
foo // {a: 5, b: 6 , c: 11}
Некоторое закрытие должно иметь дело с этим;
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
}
}
}
}
Определяя себя как объект, который содержит функцию печати, вы позволяете функции ссылаться на этот объект. Это означает, что вам не нужно будет "связывать" функцию печати с объектом, если вам нужно передать его куда-то еще.
Если бы вы вместо этого использовали 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()
,
Для завершения, в 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);
Есть несколько способов сделать это; это то, что я бы использовал:
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 = 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 в соответствии с вашими потребностями.
В
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)
})()
Создание новой функции на литерале вашего объекта и вызов конструктора кажется радикальным отходом от исходной проблемы, и в этом нет необходимости.
Вы не можете ссылаться на одноуровневое свойство во время инициализации литерала объекта.
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
Я использую следующий код в качестве альтернативы, и он работает. И переменная тоже может быть массивом. (@ Фаусто Р.)
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]
Ключ ко всему этому - ОБЛАСТЬ ПРИМЕНЕНИЯ.
Вам нужно инкапсулировать "родительский" (родительский объект) свойства, которое вы хотите определить как его собственный экземплярный объект, а затем вы можете делать ссылки на свойства-братья, используя ключевое слово 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)
};
Другие ответы, опубликованные здесь, лучше, но вот альтернатива, которая:
- Устанавливает значение при инициализации (не получатель, или производный, и т. Д.)
- Не требует какого-либо типа
init()
или код вне литерала объекта - Является литералом объекта, а не фабричной функцией или другой механикой создания объекта.
- Не должно влиять на производительность (кроме как при инициализации)
Самостоятельно выполняющиеся анонимные функции и хранилище окон
var foo = {
bar:(function(){
window.temp = "qwert";
return window.temp;
})(),
baz: window.temp
};
Заказ гарантирован (bar
до baz
).
Загрязняет window
конечно, но я не могу представить, чтобы кто-то писал сценарий, который требует window.temp
быть настойчивым. Может быть tempMyApp
если ты параноик
Это также некрасиво, но иногда полезно. Например, когда вы используете API с жесткими условиями инициализации и не чувствуете необходимость рефакторинга, значит, область видимости верна.
И сухо, конечно.
Вот пример поведения 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
})()
},
}
Если ваш объект написан как функция, которая возвращает объект, И вы используете объектный атрибут 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:
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]);
Альтернативный синтаксис с довольно хорошей ремонтопригодностью:
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 };
Я думаю, что следующий код является лучшим для удобства обслуживания, даже если он не в синтаксисе литерала объекта:
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
};
потому что нет необходимости вычислять эту сумму во время выполнения или даже во время компиляции.
Как насчет этого решения, это будет работать и с вложенными объектами с массивом
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');
Добавив вариант, так как я не видел этого точного сценария. Если не хочешь c
обновляется, когда a
или же b
обновите, тогда 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 SECOND = 1000
const MINUTE = 60 * SECOND
const HOUR = 60 * MINUTE
const DAY = 24 * HOUR
const WEEK = 7 * DAY
export const TimeInMS = {
SECOND,
MINUTE,
HOUR,
DAY,
WEEK
}
Недостатками этого метода являются:
- переменные определяются как константы, даже если они нам не нужны. Таким образом, ему нужна бесполезная память.
- каждое значение объекта должно быть объявлено как отдельная переменная
Другой подход, чтобы сделать вещи немного проще и не определять новые функции в объекте, состоит в том, чтобы сохранить исходный объект с его свойствами, создать второй объект с динамическими свойствами, которые используют свойства первого объекта, а затем объединить, используя распространение.
Так:
объект
var foo = {
a: 5,
b: 6,
};
Объект 2
var foo2 = {
c: foo.a + foo.b
};
сливаться
Object.assign(foo, foo2);
или же
foo = {...foo, ...foo2};
Результат foo:
{
"a":5,
"b":6,
"c":11
}
Все в том же JS, нет новых функций в объекте, только с использованием присвоения или распространения.
Другой подход - сначала объявить объект, прежде чем назначать ему свойства:
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
файл.
Если вы хотите использовать нативный JS, другие ответы предоставят хорошие решения.
Но если вы предпочитаете писать объекты, ссылающиеся на себя, например:
{
a: ...,
b: "${this.a + this.a}",
}
Я написал библиотеку npm под названием https://github.com/alex-e-leon/self-referenced-object, которая поддерживает этот синтаксис и возвращает собственный объект.
Два ленивых решения
Здесь уже есть отличные ответы, и я не эксперт в этом, но я эксперт в том, чтобы быть ленивым, и на мой экспертный взгляд эти ответы не кажутся достаточно ленивыми.
Во-первых: вернуть объект из анонимной функции
Очень небольшая вариация ответов Ти Джея Краудера , Генри Райтсона и Рафаэля Роча :