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


706

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

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

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

Ответы:


744

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

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

console.log(foo.c) // 11

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


32
Очень полезный ответ. Более подробную информацию о 'get' можно найти здесь: developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/…
Джейк

5
@FaustoR. Да, это так.
Чарли Мартин

48
Помните, что с этим решением, если значения foo.aили foo.bизменены, то значение foo.cтакже будет меняться синхронно. Это может или не может быть то, что требуется.
HBP

8
Обратите внимание, что thisсвязывается с самым глубоким вложенным объектом. Например:... x: { get c () { /*this is x, not foo*/ } } ...
З. Кулла

3
Чтобы завершить приведенное выше утверждение, поскольку fooоно объявляется как переменная и cбудет оцениваться только в тот момент, когда оно вызывается, использование fooinside cбудет работать, в отличие от this(будьте осторожны)
З. Хулла,

316

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

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

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

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


95
Вы также можете delete this.initраньше, return thisтак что fooэто не poluted
Билли Мун

15
@BillyMoon: Да, действительно, хотя это влияет на производительность всех последующих обращений к свойствам этого объекта, на многих движках (например, V8).
TJ Crowder

8
@MuhammadUmer: Не уверен, насколько классы ES6 имеют отношение к этому вопросу.
Феликс Клинг

8
@MuhammadUmer: классы - просто синтаксический сахар для функций конструктора, поэтому они не предоставляют ничего нового. В любом случае, основное внимание в этом вопросе уделяется объектным литералам.
Феликс Клинг

3
@akantoword: Отлично :) Так как объектные литералы являются одним выражением, к init()вызову был непосредственно добавлен литерал, чтобы сохранить его в одном выражении. Но, конечно, вы можете вызывать функцию отдельно от того, что хотите.
Феликс Клинг

181

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

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

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

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

Для собственного здравого смысла я пытаюсь найти какую-то официальную документацию, в которой говорится в основном то же самое - что объект thisдоступен только для методов указанного объекта, но никаких других свойств. Есть идеи, где я могу найти это? Спасибо!
Дэвид Кеннелл

1
@DavidKennell: не становится более официальным, чем спецификация . :-) Вы, вероятно, начнете здесь и выполните его до конца. Это довольно странный язык, но в основном вы увидите в различных подпунктах оценки определения свойств, что объект недоступен операциям, определяющим значения инициализаторов свойств.
TJ Crowder

Я не могу видеть результаты просмотра в браузере здесь , но это больше не так, верно? В моей среде 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
@ Берги, почему? Потому что кто-то может создать экземпляр того же объекта из этого? Не то чтобы они не могли просто клонировать объектный литерал. Это ничем не отличается от передачи аргумента, new Point(x, y)за исключением того, что функция не названа для повторного использования.
zzzzBov

1
@zzzzBov: Конечно, они могут просто клонировать объект, но по сравнению с решением IEFE (как в ответе TJCrowder) ваше решение пропускает функцию конструктора и создает лишний объект-прототип.
Берги

5
@zzzzBov: просто используйте, var foo = function() { this.…; return this; }.call({});что синтаксически не сильно отличается, но семантически вменяемым.
Берги

1
@ Берги, если ты чувствуешь, что это так важно, почему бы не добавить свой собственный ответ в микс?
zzzzBov

3
Вы получили это. Я действительно не заметил newключевое слово.
Рэнди

26

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

1
У es5 также есть свойства, которые вам просто нужно использовать Object.defineProperty(foo, 'c', {get:function() {...}})для их определения. Это легко сделать ненавязчивым способом на такой фабрике, как эта. Конечно, если вы можете использовать getсахар, он будет более читабельным, но возможность была там.
Алуан Хаддад

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
Смущать и вводить в заблуждение называть это «закрытием». Хотя мнения относительно точного значения, возвращающего значение ojbect из функции, не являются закрытием в чьей-либо книге.

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

Определяя себя как объект, который содержит функцию печати, вы позволяете функции ссылаться на этот объект. Это означает, что вам не нужно будет «связывать» функцию печати с объектом, если вам нужно передать его куда-то еще.

Если вы используете вместо этого, 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
Хорошие примеры, за исключением того, что вы пропустили все точки с запятой.

1
Никаких точек с запятой - это нормально !
Керем Байдоган

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()произойдет сбой. Может быть, эта скрипка ближе к тому, что вы собираетесь (это также синглтон, не предназначенный для реализации).
Холлистер

2
«Шаблон Module был изначально определен как способ обеспечить как частную, так и публичную инкапсуляцию для классов в традиционной разработке программного обеспечения». Из: Изучение шаблонов дизайна JavaScript . Этот объект следует шаблону модуля, описанному выше, но, возможно, это не лучший способ объяснить это, потому что он не показывает публичные и частные свойства / методы. Этот jsfiddle.net/9nnR5/2 - это один и тот же объект с общими и частными свойствами / методами. Так что они оба следуют этой схеме
Рафаэль Роча

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

Правда, извините, я пропустил объектно-буквальный тег изначально. Я в основном использую объектные литералы только для структур данных, и каждый раз, когда мне нужна дополнительная логика (которая может напоминать класс), я создаю объект как результат функции по этой самой причине.
Кен

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

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

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

Самостоятельно выполняющиеся анонимные функции и хранилище окон

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

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

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

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

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


3

Ключ ко всему этому - ОБЛАСТЬ ПРИМЕНЕНИЯ .

Вам необходимо инкапсулировать «родительский» (родительский объект) свойства, которое вы хотите определить как его собственный экземплярный объект, а затем вы можете делать ссылки на свойства-братья, используя ключевое слово 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

Я использую следующий код в качестве альтернативы, и он работает. И переменная тоже может быть массивом. (@ Фаусто Р.)

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обновлять когда 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.


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 (вы можете использовать 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';
        };
    }
}

2
Может быть, потому что ваш ответ совершенно не имеет значения? Я согласен, что нижестоящие люди должны объяснить, но ваш четкий ответ не имеет ничего общего с вопросом ...
Маннго

@Manngo спасибо за указание на это. Честно говоря, я бы задал тот же вопрос, что и OP, и использую предложенное мной решение. Не уверен, почему это считается неактуальным. Если у вас есть время, пожалуйста, объясните, чтобы я мог лучше ответить или хотя бы знать, в чем я не прав. Я, к сожалению, не понимаю, почему это не разумное решение.
Дхирадж Бхаскар

-2

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

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

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

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


1
Пожалуйста, избегайте ссылки только ответы . Ответы, которые «едва ли больше, чем ссылка на внешний сайт», могут быть удалены .
Квентин,

@Quentin у вас есть предложения, как я могу улучшить свой ответ? Другие ответы на этот вопрос охватывают, как вы могли бы писать самоссылающиеся объекты в нативном javascript, но если вы хотите писать самоссылающиеся объекты с синтаксисом, аналогичным синтаксису исходного вопроса постеров, я думаю, что библиотека, которая Я написал, может быть полезным для других, ищущих решение. Рад получить обратную связь.
alex-e-leon

Пара вещей, чтобы улучшить здесь. Во-первых, и, что наиболее очевидно, вы используете шаблонный синтаксис без обратных тиков. Ваше bЗначение свойства должно быть: ${this.a + this.a}. Во-вторых, но что не менее важно, вы захотите вернуть число, а не строку, используя что-то вроде parseInt. Последнее и самое главное, когда я попробовал этот пример, он просто не работает, по той же причине, по которой спрашивает OP. В thisвозвращает неопределенное значение при использовании его собственное описание объекта. @ alex-e-leon
Алек Дональд Мазер

@AlecDonaldMather - спасибо, что нашли время, чтобы посмотреть и оставить отзыв! Если вы заинтересованы в проекте, возможно, было бы лучше перенести это обсуждение на github, но ответить на некоторые ваши отзывы: - Использование обратных галочек: как упоминалось в предыдущих комментариях, этот синтаксис не поддерживается JS, и поэтому вместо него используются строки здесь необходимо использовать backticks, чтобы js не пытался разрешить «this» до того, как obj был определен - возвращая число, это должно работать, если a + b уже являются числами, так как a + b вернет число, если оба a и b уже номера.
alex-e-leon

Если это возвращение не определено, можете ли вы объяснить, как вы пытались использовать библиотеку? Этого не должно случиться, но, может быть, есть крайний случай, который я пропустил? Тем не менее, эта библиотека не решает проблему полностью и имеет свой собственный набор компромиссов, но если вы заинтересованы в том, чтобы помочь мне улучшить / использовать ее, дайте мне знать!
alex-e-leon
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.