Самопосилання в об'єктних літералах / ініціалізаторах


705

Чи є якийсь спосіб, щоб щось на зразок наступного працювало в 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

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


32
Дуже корисна відповідь. Більше інформації про "отримати" можна знайти тут: 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буде оцінюватися лише під час його виклику, використання fooвнутрішньої частини 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не бути напівзв’язаним
Біллі Мун,

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%, а геккон: 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
@Bergi, чому? Тому що хтось може створити з нього інший той самий об’єкт? Це не так, як вони не можуть просто клонувати об'єкт буквально. Це не відрізняється від передачі такого аргументу, new Point(x, y)за винятком того, що функція не названа для повторного використання.
zzzzBov

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

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

1
@ Bergi, якщо ти вважаєш, що це так важливо, чому б не додати власну відповідь до суміші?
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цукор, він є більш читабельним, але можливість є.
Aluan Haddad

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

Визначаючи self як об'єкт, що містить функцію print, ви дозволяєте функції посилатися на цей об’єкт. Це означає, що вам не доведеться «прив’язувати» функцію друку до об’єкта, якщо вам потрібно передати його десь в іншому місці.

Якщо ви замість цього використовуєте, 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, ви гарантуєте, що друк завжди поверне той самий об'єкт незалежно від контексту, в якому виконується функція. Код, наведений вище, буде виконуватись добре і входитиме 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
"Шаблон модуля спочатку визначався як спосіб надання як приватного, так і публічного інкапсуляції для класів із звичайної інженерії програмного забезпечення". Від: Вивчення шаблонів дизайну 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 object-attribute 'method', то це можливо:

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 (ви можете використовувати ванільний 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();

Тут використовували вирази класу, щоб отримати вкладений інтерфейс буквального інтерфейсу, який ми хотіли б. Це наступне найкраще, що IMHO може мати посилання на властивості об'єкта під час створення.

Головне, що слід зазначити, під час використання цього рішення ви маєте такий самий інтерфейс, який ви мали б з об’єкта-літералу. А синтаксис досить близький до самого буквеного об’єкта (проти використання функції тощо).

Порівняйте наступне

Я запропонував рішення

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

@Manngo дякую, що вказав на це. Чесно кажучи, у мене те саме питання, що і в ОП, і я використовую запропоноване нами рішення. Не впевнені, чому це вважається неактуальним. Якщо у вас є час, будь ласка, поясніть, щоб я міг зробити відповідь кращою або принаймні знати, де я помиляюся. Я, на жаль, не розумію, чому це не розумне рішення.
Dheeraj Bhaskar

-2

Якщо ви хочете використовувати рідний JS, інші відповіді пропонують хороші рішення.

Але якщо ви готові писати об'єкти, що самі посилаються на зразок:

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

Я написав бібліотеку npm під назвою " self-referenced-object", яка підтримує цей синтаксис і повертає нативний об'єкт.


1
Будь ласка, уникайте лише відповідей на посилання . Відповіді, які є "ледь більше, ніж посилання на зовнішній сайт", можуть бути видалені .
Квентін,

@Quentin Чи є у вас якісь пропозиції щодо того, як я можу покращити свою відповідь? Інші відповіді на це питання стосуються того, як ви, можливо, зможете писати об'єкти, що самостійно посилаються, на власний javascript, але якщо ви хочете писати об'єкти, що самі посилаються, із синтаксисом, подібним до синтаксису, в оригінальному записі плакатів, я думаю, що бібліотека, яка Я писав, може бути корисним іншим, хто шукає рішення. Раді отримати відгуки.
alex-e-leon

Тут можна покращити кілька речей. По-перше, і, очевидно, ви використовуєте синтаксис буквального шаблону без зворотних кліщів. Ваше bЗначення властивості має бути: ${this.a + this.a}. По-друге, але менш важливо, ви хочете повернути число, а не рядок, використовуючи щось подібне parseInt. Останнє і НАЙБІЛЬШЕ головне, коли я спробував цей приклад, він просто не працює, з тієї ж причини, про що запитує ОП. У thisповертає невизначене значення при використанні його власний опис об'єкта. @ alex-e-leon
Алек Дональд Матер

@AlecDonaldMather - спасибі за те, що знайшли час для ознайомлення та надання відгуку! Якщо ви зацікавлені в проекті, можливо, краще перенести цю дискусію до github, але відповісти на деякі ваші відгуки: - Використання зворотних посилань: Як було сказано в попередніх коментарях, цей синтаксис не підтримується JS, а тому замість нього використовуються рядки Зворотних посилань тут потрібно, щоб уникнути js, що намагається вирішити "це" до того, як obj буде визначено - повертаючи число, це повинно працювати, якщо а + b - це вже числа, оскільки a + b поверне число, якщо і a, і b вже числа.
alex-e-leon

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