Инициализаторы объектов и необычные свободы, которые они предоставляют программисту

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

var myObj = {
  '':   1,
  "":   2,
  [[]]: 3,
  ['']: 4,
  1:    5,
  1.0:  6,
  '1':  7,
  [1]:  8,
  [[1]]:9,
  _:    10
};

Как сказано на соответствующей странице MDN:

Инициализатор объекта — это разделенный запятыми список из нуля или более пар имен свойств и связанных значений объекта, заключенных в фигурные скобки ({}).

На самом деле это немного преуменьшение, как показано чуть ниже в списке возможных форм, которые может принимать пара свойство-значение:

o = {
  a: "foo",
  b: 42,
  c: {},
  1: "number literal property",
  "foo:bar": "string literal property",

  shorthandProperty,

  method(parameters) {
    // …
  },

  get property() {},
  set property(value) {},

  [expression]: "computed property",

  __proto__: prototype,

  ...spreadProperty,
};

Если вы сравните этот список возможных форматов с нашим примером, то обнаружите, что мы использовали в качестве свойств
— обычный идентификатор: _(да, символ _ является допустимым идентификатором в JS)
- два числа: 1 и 1.0
- три строковых литерала: '', "", '1'
 — три вычисляемых имени свойства: [''], [[]], [1], [[1]]

Итак, каков ответ? Сколько и какие свойства есть у myObj?

Ответ на викторину

Я не думаю, что легко быть уверенным в правильном ответе, не попробовав его вживую. На самом деле, конкретный способ, которым JavaScript интерпретирует «имена» свойств объекта, не сразу очевиден, в случаях, созданных так, чтобы быть странными, как этот.

Это содержимое объекта myObj, определенного выше:

{
    "1": 9,
    "":  4,
    "_": 10
}

// verify
console.log(myObj[1]);  // 9
console.log(myObj['']); // 4
console.log(myObj._);   // 10

Последнее свойство (_) самое простое: это обычный идентификатор (т. е. строка, допустимая в качестве имени переменной JavaScript), точно так же, как и такие имена, как $, $$. , ___, $_ù_$ и т. д.

Свойство "1" несколько более удивительно: оно является результатом следующих пяти пунктов в определении:

1:    5,
1.0:  6,
'1':  7,
[1]:  8,
[[1]]:9,

Для JavaScript все эти значения приводят к строке «1», поскольку для всех них неявно выполняется .toString(). Обратите внимание, что последний определяет вычисляемое свойство как массив, содержащий число 1, а метод по умолчанию .toString() для этого массива просто возвращает строку «1».

Поскольку все 5 определений создают одно и то же имя свойства, JavaScript применяет их по порядку, и, таким образом, в результате свойство 1 имеет последнее связанное с ним значение, т. е. 9.

Свойство "" (пустая строка), пожалуй, самое странное, так как можно было бы подумать, что имя свойства должно быть чем-то "видимым", но это не так. Определение присваивает значение 4 из-за эквивалентности следующих четырех элементов в инициализаторе:

'':   1,
"":   2,
[[]]: 3,
['']: 4,

Причина, по которой все четыре строки определяют одно и то же свойство, та же, что и раньше, а именно неявное применение метода .toString() для создания имени свойства.

Дополнительные странности

Теперь, когда мы изучили основы, другие психически больные комбинации становятся почти понятными:

var myObj = {
  [Date]:       Date,
  [new Date()]: new Date(),
  [{}]:         [{}],
  null:         null,
  undefined:    undefined,
  ':':          ':',
  [Math.PI]:    Math.PI,
  ààà:          'ààà'
};

// List property-value couples
for (p in myObj) console.log('"'+p+'":', myObj[p]);

/* Output:
  "function Date() { [native code] }": ƒ Date() { [native code] }
  "Tue May 16 2023 10:46:35 GMT+0200 (Ora legale dell’Europa centrale)": Tue May 16 2023 10:46:35 GMT+0200 (Ora legale dell’Europa centrale)
  "[object Object]": [{…}]
  "null": null
  "undefined": undefined
  ":": :
  "3.141592653589793": 3.141592653589793
  "ààà": ààà
*/

Обратите внимание на то, как JavaScript создает имя свойства в виде строки для функций (дата), общих объектов ([{}]) и экземпляров классов благодаря специальному методу .toString() (новая дата). Обратите также внимание на то, что свойство можно назвать любым символом (:), буквами с диакритическими знаками (ààà) и даже ключевыми словами, которые нельзя использовать в качестве идентификаторов (null). , не определено).

Может ли все это быть полезным?

Маловероятно, что в обычном проекте фронтенд-разработки вы столкнетесь с определением объектов с такими нестандартными именованными свойствами. Но пока мы на этом, я предлагаю два варианта использования.

Случай 1: Статистика использования пунктуации

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

// Read the text of Ulysses by Joice
fetch('https://www.gutenberg.org/files/4300/4300-0.txt')
.then( response => response.text() )
.then( data => {
    // Parse the punctuation symbols and count them
    let result = {};
    data.match(/[-,.;:!?"']/g).forEach( char => {
        result[char] = (result[char]??0) +1;
    } );
    console.log(JSON.stringify(result));
});

Результат:

{
    ",": 16521,
    ".": 22487,
    "-": 311,
    ":": 2584,
    "!": 1575,
    "?": 2233,
    ";": 32,       
    "\"": 22,
    "'": 7
}

Сравнивая этот результат с таким же в Гамлете У. Шекспира, кажется, что точка с запятой была в то время более модной:

{
    ",": 3361,
    ".": 3483,
    "-": 183,
    ":": 108,
    "?": 448,
    "!": 201,
    ";": 387,
    "\"": 22,
    "'": 7
}

Случай 2: конфигурации по умолчанию

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

var currency =  {
    us: 'USD',
    fr: 'EUR',
    it: 'EUR',
    undefined: 'unknown (missing)',
    '': 'unknown (empty)'
};

let trans1 = {fromCountry: 'fr'};
let trans2 = {fromCountry: 'us', toCountry: ''};
console.log(currency[trans1.fromCountry]); // EUR
console.log(currency[trans1.toCountry]);   // unknown (missing)
console.log(currency[trans2.toCountry]);   // unknown (empty)

Выводы

Мы показали особый аспект синтаксиса JavaScript, связанный с тем, как свойства объекта могут принимать имена, которые не являются реальными именами, что делает объект чем-то более похожим на хэш. table на других языках программирования.

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

Спасибо за внимание.