Самостоятельные ссылки в объектных литералах / инициализаторах

Есть ли способ заставить что-то вроде следующего работать в 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 в соответствии с вашими потребностями.

http://jsfiddle.net/jPNxY/1/

В 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, которая поддерживает этот синтаксис и возвращает собственный объект.

Два ленивых решения

Здесь уже есть отличные ответы, и я не эксперт в этом, но я эксперт в том, чтобы быть ленивым, и на мой экспертный взгляд эти ответы не кажутся достаточно ленивыми.

Во-первых: вернуть объект из анонимной функции

Очень небольшая вариация ответов Ти Джея Краудера , Генри Райтсона и Рафаэля Роча :

Другие вопросы по тегам