Могу ли я сохранить порядок записей объекта javascript, когда некоторые ключи ввода являются целыми числами?

#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 просто получаете {} — и невозможно напрямую преобразовать a Map в 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. Использование прокси упрощает задачу