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

Есть ли способ сделать что-то вроде следующего для работы в JavaScript?

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

В текущей форме этот код явно генерирует опорную ошибку, так как this не относится к foo. Но есть ли способ иметь значения в объектных свойствах литерала в зависимости от других свойств, объявленных ранее?

Ответ 1

Ну, единственное, что я могу вам рассказать, это getters:

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

foo.c; // 11

Это синтаксическое расширение, введенное спецификацией ECMAScript 5th Edition, синтаксис поддерживается большинством современных браузеров (включая IE9).

Ответ 2

Вы можете сделать что-то вроде:

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

Это будет своего рода однократная инициализация объекта.

Обратите внимание, что вы фактически присваиваете возвращаемое значение от init() до foo, поэтому вы должны return this.

Ответ 3

Очевидный простой ответ отсутствует, поэтому для полноты:

Но есть ли способ иметь значения в свойствах литерала объекта в зависимости от других объявленных ранее свойств?

Нет. Все решения здесь откладывают до тех пор, пока объект не будет создан (по-разному), а затем назначит третье свойство. Самый простой способ - просто сделать это:

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

Ответ 4

Просто создайте анонимную функцию:

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

Ответ 5

Некоторое закрытие должно иметь дело с этим:

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

Ответ 6

Теперь в 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}  

Ответ 7

Вы можете сделать это вот так:

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, вы гарантируете, что печать всегда будет возвращать один и тот же объект независимо от контекста, в котором выполняется функция. При использовании собственной версии createMyObject() приведенный выше код будет работать нормально и log 0, 1, 2 и 3.

Ответ 8

Для завершения в ES6 у нас есть классы (поддерживаемые на момент написания этого только в последних браузерах, но доступные в Babel, TypeScript и других транспилерах)

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

const foo = new Foo();

Ответ 9

Существует несколько способов сделать это; это то, что я буду использовать:

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

Ответ 10

Вы можете сделать это, используя шаблон модуля. Также как:

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/

Ответ 11

только для того, чтобы мыслить объекты объекта на временной шкале:

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

console.log(foo.c())

есть и более качественные ответы. Вот как я модифицировал пример кода, с которым вы допросили.

UPDATE:

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

Ответ 12

Создание новой функции в вашем литерале объекта и вызов конструктора кажется радикальным отходом от исходной проблемы, и это не нужно.

Вы не можете ссылаться на свойство sibling во время инициализации литерала объекта.

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

Ответ 13

Ключом ко всему этому является 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)
};

Ответ 14

Другие ответы, размещенные здесь, лучше, но вот альтернатива, которая:

  • Устанавливает значение при инициализации (не получателя, не производного и т.д.)
  • Не требует никакого типа init() или кода вне литерала объекта
  • Является ли объект литералом, а не функцией factory или другим механиком создания объекта.
  • Не должно влиять на производительность (кроме как при инициализации)

Самозаверяющие анонимные функции и хранилище окон

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

Заказ гарантирован (bar до baz).

Он загрязняет window, конечно, но я не могу себе представить, что кто-то пишет script, для которого требуется window.temp быть постоянным. Может быть, tempMyApp, если вы параноик.

Это также уродливо, но иногда полезно. Например, когда вы используете API с жесткими условиями инициализации и не чувствуете себя рефакторингом, поэтому область обзора верна.

И это, конечно, сухо.

Ответ 15

если ваш объект написан как функция, возвращающая объект, и вы используете методы-атрибуты объекта 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);

Ответ 16

Здесь аккуратный путь ES6:

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

Ответ 17

Я использую следующий код в качестве альтернативы, и он работает. И переменная также может быть массивом. (@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]

Ответ 18

Как об этом решении это будет работать с вложенными объектами с массивом, а также

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

Ответ 19

Бросьте опцию, так как я не видел этого точного сценария. Если вы не хотите, чтобы 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

Ответ 20

Лучший способ оптимизировать это заключается в следующем:

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

console.log(foo.c())
// 11

Ответ 21

Другой подход заключается в том, чтобы сначала объявить объект, прежде чем присваивать ему свойства:

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.

Ответ 22

Другой подход, чтобы сделать вещи немного проще и не определять новые функции в объекте, состоит в том, чтобы сохранить исходный объект с его свойствами, создать второй объект с динамическими свойствами, которые используют свойства первого объекта, а затем объединить, используя распространение.

Так:

объект

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, нет новых функций в объекте, только с использованием назначить или распространение.

Ответ 23

Если вы хотите использовать нативный JS, другие ответы предоставят хорошие решения.

Но если вы предпочитаете писать объекты, ссылающиеся на себя, например:

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

Я написал библиотеку npm под названием self-referenced-object, которая поддерживает этот синтаксис и возвращает собственный объект.

Ответ 24

Примечание. Это решение использует TypScript (вы можете использовать vanilla JS, который TS компилирует, если необходимо)

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