#javascript #object #key #es6-map
Вопрос:
Я работаю с object
тем, где мне нужно сохранить порядок entries
, даже если некоторые keys
из них являются буквенно-цифровыми, а другие — целыми числами. (Да, я знаю.)
Объект, с которого я начинаю, выглядит следующим образом:
{
a: 'the',
quick: 'quick',
b: 'brown',
fox: 'fox'
}
После манипуляций объект должен выглядеть следующим образом:
{
a: 'the',
0: 'quick',
b: 'brown',
1: 'fox'
}
Но. Поскольку порядок итераций в объектах javascript отличается от порядка вставки (сначала повторяются целые числа), если я сделаю это прямолинейно, я не получу правильно упорядоченный результат:
let myReindexedObject = {};
myReindexedObject['a'] = 'the';
myReindexedObject['0'] = 'quick';
myReindexedObject['b'] = 'brown';
myReindexedObject['1'] = 'fox';
console.log(myReindexedObject);
Я попытался решить эту проблему, создав a Map
(который, в отличие от an object
, сохраняет порядок ввода), который я затем могу преобразовать в an object
.
Источник: (Я адаптировал эту суть Люком Хорватом: преобразовать карту ES6 в литерал объекта .)
Можете ли вы догадаться, что происходит?
let myMap = new Map();
myMap.set('a', 'the');
myMap.set('0', 'quick');
myMap.set('b', 'brown');
myMap.set('1', 'fox');
let myArray = Array.from(myMap);
let myReindexedObject = myArray.reduce((myReindexingObject, [key, value]) => {
return Object.assign(myReindexingObject, { [key]: value })
}, {});
console.log(myReindexedObject);
Есть ли какой-либо способ, которым я могу использовать целочисленный keys
like 0
1
и по-прежнему сохранять object
записи в пользовательском порядке?
Или мне нужно рассмотреть другие подходы?
Комментарии:
1. Нет. Порядок свойств объекта определен (сейчас) в спецификации языка, но он неизменяем. Числовые записи идут первыми, другие записи идут в том порядке, в котором были добавлены свойства. Полагаться на порядок свойств объекта в JavaScript — действительно плохая идея, потому что это делает код чрезвычайно хрупким. Если вам нужен порядок, создайте массив со свойствами в нем в порядке, который подходит для вашего приложения.
2. Почему вы не можете использовать Map напрямую? Вместо циклического перебора
Object.keys(myObject)
почему бы не использоватьmyMap.keys()
или[...myMap.keys()]
?3. @onkarruikar — В идеале я хочу работать с an
Object
постоянно, и я вообще не хочу привлекатьMaps
. Среди прочего, браузерным консолям не нравитсяMaps
— еслиconsole.log
выMap
просто получаете{}
— и невозможно напрямую преобразовать aMap
вJSON
.
Ответ №1:
В процессе написания приведенного выше вопроса мне внезапно пришло в голову, когда я печатал:
(сначала повторяются целые числа)
то, что движок javascript распознает как an integer
, и то, что люди распознают как число, конечно, не одно и то же.
Для любого человека эти два:
1
1.
не являются типографски идентичными, но они в значительной степени эквивалентны.
Для любого интерпретатора javascript они полностью различны: первое является целым числом, второе — нет.
Рабочий пример:
let myReindexedObject = {};
myReindexedObject['a'] = 'the';
myReindexedObject['0.'] = 'quick';
myReindexedObject['b'] = 'brown';
myReindexedObject['1.'] = 'fox';
console.log(myReindexedObject);
Если интерпретатору javascript необходимо идентифицировать эти индексы, он может это сделать, используя регулярное выражение:
/d ./
и, после идентификации, если ему нужно знать целое число, которому string
соответствует, он может использовать:
parseInt(myIndex);
Пока я буду использовать этот подход.
Если кто-нибудь может предложить лучший подход, я буду рад проголосовать и принять.
Комментарии:
1. Обратите внимание, что
parseInt("1.")
это успешно вернется1
, что может быть или не быть тем, что вы хотите.2. Да, это нормально. Во время циклического перехода
Object.keys(myObject)
я могу сделать что-то вроде:let thisKey = (myKeys[i].match(/d ./)) ? parseInt(myKeys[i]) : myKeys[i];
(или.forEach()
.map()
эквивалент / ).3. @Rounin Я добавил награду за этот пост. Давайте посмотрим, сможем ли мы получить лучший ответ на вашу проблему.
4. Очень признателен, @CreativeLearner — спасибо!
Ответ №2:
Мы можем определить наш собственный объект, который отслеживает свойства. И, перехватывая необходимые функции, мы можем заставить это работать.
используя прокси, это легко достижимо:
// DEMO
let o = new CoolObject();
o['a'] = 'the';
o['0'] = 'quick';
o['b'] = 'brown';
o['1'] = 'fox';
o['c'] = 'jumped';
delete o['c'];
console.log('Object.keys: ', Object.keys(o));
console.log('JSON.stringify: ', JSON.stringify(o));
console.log('console.log: ', o);
console.log('Object.getOwnPropertyNames: ', Object.getOwnPropertyNames(o));
console.log('obj.propertyIsEnumerable("keys"): ', o.propertyIsEnumerable('keys'));
console.log('obj.propertyIsEnumerable("a"): ', o.propertyIsEnumerable('a'));
<script src="https://cdn.jsdelivr.net/gh/OnkarRuikar/temp@main/CoolObject.js"></script>
See console logs for output.
Обратите внимание на упорядоченные имена свойств вставки. Результат getOwnPropertyNames
также упорядочен для вставки, кроме методов.
Определение класса CoolObject:
(function () {
// original functions
let _keys = Object.keys;
let _getOwnPropertyNames = Object.getOwnPropertyNames;
let _defineProperty = Object.defineProperty;
let _stringify = JSON.stringify;
let _log = console.log;
// main feature definition
let CoolObject = function () {
let self = this;
let handler = {
_coolKeys: [],
set(target, key, val) {
let keys = this._coolKeys;
if (!keys.some(k => k === key))
keys.push(key);
target[key] = val;
},
get(target, key) {
return target[key];
},
keys() {
return this._coolKeys.slice(0);
},
deleteProperty(target, key) {
let keys = this._coolKeys;
const index = keys.indexOf(key);
if (index > -1) {
keys.splice(index, 1);
}
delete target[key];
},
defineProperty(obj, prop, val) {
let keys = this._coolKeys;
if (!keys.some(k => k === prop))
keys.push(prop);
_defineProperty(self, prop, val);
},
getOwnPropertyNames(obj) {
let props = _getOwnPropertyNames(obj);
return [...new Set([...this._coolKeys, ...props])];
},
// many improvements can be done here
// you can use your own modified pollyfill
stringifyHelper(obj, replacer, space) {
let out = '{';
for (let key of this._coolKeys) {
out = `"${key}":${_stringify(obj[key], replacer, space)}, `;
}
out = '}';
return out;
},
};
_defineProperty(self, 'keys', { value: () => handler.keys() });
_defineProperty(self, 'getOwnPropertyNames', { value: (o) => handler.getOwnPropertyNames(o) });
_defineProperty(self, 'stringify', { value: (...args) => handler.stringifyHelper(...args) });
return new Proxy(self, handler);
} // CoolObject end
// ----- wrap inbuilt objects -----
Object.keys = function (obj) {
if (!(obj instanceof CoolObject))
return _keys(obj);
return obj.keys();
}
Object.defineProperty = function (obj, prop, val) {
if (!(obj instanceof CoolObject))
_defineProperty(...arguments);
obj.defineProperty(...arguments);
}
Object.getOwnPropertyNames = function (obj) {
if (!(obj instanceof CoolObject))
return _getOwnPropertyNames(obj);
return obj.getOwnPropertyNames(obj);
}
JSON.stringify = function (obj, replacer, indent) {
if (!(obj instanceof CoolObject))
return _stringify(...arguments);
return obj.stringify(...arguments);
}
console.log = function () {
let myArgs = [];
for (let arg of arguments) {
if (arg instanceof CoolObject) {
let keys = arg.keys();
arg = Object.assign({}, arg);
for (let key of keys) {
arg[`.${key}`] = arg[key]
delete arg[key];
}
}
myArgs.push(arg);
}
_log(...myArgs);
}
window.CoolObject = CoolObject;
})();
handler
Объект сохраняет имена свойств в _coolKeys
массиве. И отслеживает операции добавления и удаления. Чтобы заставить объект вести себя как исходный объект, нам нужно обернуть некоторые встроенные API, например Object.keys() .
Примечание: для демонстрации я реализовал минимальный грубый код. Можно сделать много улучшений. Вы можете перехватывать больше встроенных API в соответствии с вашими требованиями.
Комментарии:
1. Использование прокси упрощает задачу