Почему JavaScript преобразует массив из одной строки в строку при использовании в качестве ключа объекта?
Я столкнулся со сценарием, в котором JavaScript ведет себя так, что это несколько сбивает с толку.
Допустим, у нас есть объект с двумя ключами foo & бар.
a = { foo: 1, bar: 2 }
Затем у меня есть массив строк, в данном случае один 'foo'
b = ['foo']
Я бы ожидал следующего:
a[b] == undefined
a[b[0]] == 1
НО, вот что происходит:
a[b] == 1
a[b[0]] == 1
Почему JavaScript преобразует ['foo'] -> 'foo'
при использовании в качестве ключа?
Кто-нибудь там знает причину?
Как это можно предотвратить?
let a = { foo: 1, bar: 2 }
let b = ['foo']
console.log(a[b] == 1) // expected a[b] to be undefined
console.log(a[b[0]] == 1) // expected a[b] to be 1
Ответы
Ответ 1
Все ключи объекта являются строковыми, поэтому он в конечном итоге преобразует все, что вы помещаете в [] (Bracket notation)
, в строку, если он является выражением, он вычисляет выражение и преобразует его значение в строку и использует в качестве ключа
console.log(['foo'].toString())
Ответ 2
При использовании массива в качестве ключа javascript вызывает метод toString() этого массива, а затем пытается найти строковую версию массива в качестве ключа. И если вы вызываете ['foo'].toString()
, вы видите, что этот метод возвращает "foo"
.
Ответ 3
Почему JavaScript преобразует ['foo'] → 'foo' при использовании в качестве ключа?
Кто-нибудь знает причину?
В любое время возникает путаница в отношении того, почему JavaScript работает непредсказуемым образом, поэтому взгляд на определение языка - верный способ точно выяснить, что произошло.
https://www.ecma-international.org/ecma-262/10.0/ самое актуальное определение языка на момент публикации.
Сначала вы захотите найти область, относящуюся к доступу к массиву. Это на языке жаргона, хотя.
12.3.2.1 Семантика времени выполнения: оценка
MemberExpression: MemberExpression [ Выражение ]
...
3. Пусть propertyNameReference будет результатом вычисления Expression.
4. Пусть propertyNameValue будет? ПолучитьЗначение (propertyNameReference).
6. Пусть propertyKey будет? ToPropertyKey (propertyNameValue).
Итак, здесь происходит то, что вы обращаетесь к своему массиву (MemberExpression), используя []
с выражением.
Чтобы получить доступ к []
, выражение будет оценено, а затем будет вызван GetValue. Затем будет вызван ToPropertyKey.
- propertyNameReference = Оценивать выражение
b
= b
- propertyNameValue = GetValue (propertyNameReference) =
['foo']
- propertyKey = ToPropertyKey (propertyNameValue) =
'foo'
В нашем случае ToPropertyKey приводит к ToPrimitive, а затем к ToOrdinaryPrimitive, в котором говорится, что мы должны вызвать "toString" для аргумента (['foo']
в нашем случае).
Это где реализация вступает в игру. На стороне реализации,
Объект Array переопределяет метод toString объекта. Для объектов Array метод toString присоединяется к массиву и возвращает одну строку, содержащую каждый элемент массива, разделенный запятыми " MDN - Array toString
Если в массиве только одно значение, результатом будет просто это значение.
Как это можно предотвратить?
Это текущий способ реализации. Чтобы изменить это, вы должны либо изменить реализацию по умолчанию, либо использовать обнаружение для предотвращения вызова, либо использовать руководство для предотвращения вызова.
Руководство
Документируйте и применяйте механизмы вызова в вашем коде. Это не всегда возможно. Хотя, по крайней мере, разумно ожидать, что программисты не будут вызывать доступ к свойству с массивами.
Обнаружение
Это будет зависеть от текущей среды. В самой последней итерации JavaScript вы можете использовать принудительное использование типов, чтобы обеспечить доступ к свойству Number или String. Typescript делает это довольно легко (вот хороший пример). По сути, это просто потребует, чтобы доступ был определен как:
function ArrayAccess(value: string | number) {
и это не позволит никому использовать массив в качестве значения метода доступа.
Реализация по умолчанию
Изменение реализации по умолчанию - ужасная идея. Это, скорее всего, вызовет всевозможные критические изменения, и не должно быть сделано. Однако, для полноты, вот как это будет выглядеть. В первую очередь я показываю это, чтобы вы могли распознать его, если вы где-то его увидите, а затем убить его огнем (или проверить какой-то код, чтобы исправить это, если рядом с ним нет пауков).
var arrayToString = [].toString;
Array.prototype.toString = function(){
if(this.length === 1) return;
return arrayToString.call(this);
};
Изменение реализации экземпляра также не является лучшей идеей. Об этом говорится в @Code Maniac в отдельном ответе. "В практических ситуациях вы никогда не должны этого делать", - заявляет @Code Maniac, с чем я тоже согласен.
Ответ 4
При использовании массива в качестве ключа javascript вызывает метод toString() этого массива, а затем пытается найти строковую версию массива в качестве ключа. И если вы вызываете ['foo']. ToString(), вы видите, что этот метод возвращает "foo".