Подтвердить что ты не робот

Упорядоченный хэш в JavaScript

Объекты JavaScript не имеют порядка, сохраненного для свойств (согласно спецификации). Кажется, Firefox сохраняет порядок определения свойств при использовании цикла for...in. Является ли это поведение чем-то, на что я могу положиться? Если нет, есть фрагмент кода JavaScript где-нибудь, который реализует упорядоченный хэш-тип?

4b9b3361

Ответ 1

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

## OrderedHash
# f = new OrderedHash
# f.push('a', 1)
# f.keys()
# 
class OrderedHash
 constructor: ->
   @m_keys = []
   @m_vals = {}

  push: (k,v) ->
    if not @m_vals[k]
      @m_keys.push k
    @m_vals[k] = v

  length: () -> return @m_keys.length

  keys: () -> return @m_keys

  val: (k) -> return @m_vals[k]
  vals: () -> return @m_vals

Ответ 2

JavaScript в 2016 году, в частности EcmaScript 6, поддерживает встроенный класс Map.

Объект Map выполняет итерацию своих элементов в порядке вставки - цикл for... возвращает массив [key, value] для каждой итерации.

Это то, что вам нужно. (Интересно, почему это первая информация в описании этой структуры данных?)

Например,

m = new Map()

m.set(3,'three')
m.set(1,'one')
m.set(2,'two')

m // Map { 3 => 'three', 1 => 'one', 2 => 'two' }

[...m.keys()] // [ 3, 1, 2 ]

или пример из документов:

var myMap = new Map();
myMap.set(0, 'zero');
myMap.set(1, 'one');

myMap // Map { 0 => 'zero', 1 => 'one' }

for (var [key, value] of myMap) {
  console.log(key + " = " + value);
}

for (var key of myMap.keys()) {
  console.log(key);
}

for (var value of myMap.values()) {
  console.log(value);
}

for (var [key, value] of myMap.entries()) {
  console.log(key + " = " + value);
}

Ответ 3

Нет, поскольку тип задан как неупорядоченный набор свойств, вы не можете полагаться на это. (Или: вы можете полагаться только на то, что объект является неупорядоченным набором свойств.)

Если вы хотите иметь упорядоченный хеш-набор, вам нужно будет реализовать его самостоятельно.

Ответ 4

@Vardhan отвечает в простом JavaScript, используя закрытие вместо классического OO и добавляя метод insert():

function makeOrderedHash() {
    var keys = [];
    var vals = {};
    return {
        push: function(k,v) {
            if (!vals[k]) keys.push(k);
            vals[k] = v;
        },
        insert: function(pos,k,v) {
            if (!vals[k]) {
                keys.splice(pos,0,k);
                vals[k] = v;
            }
        },
        val: function(k) {return vals[k]},
        length: function(){return keys.length},
        keys: function(){return keys},
        values: function(){return vals}
    };
};

var myHash = makeOrderedHash();

Ответ 5

Один трюк, который я делаю, - это хранить данные в регулярном неупорядоченном хэше, а затем сохранять предпочтительный порядок в массиве. В JS вы можете даже сделать массив порядка частью самого хэша.

var myHash = {
  a: "2",
  b: "3",
  c: "1"
};

myHash.order = [ myHash.c, myHash.a, myHash.b ];

alert("I can access values by key. Here B: " + myHash.b);
var message =  "I can access also loop over the values in order: ";

for (var i=0;i<myHash.order.length;i++)
{ 
  message = message + myHash.order[i] + ", ";
}

alert(message)

Это не совсем элегантный, но он выполняет свою работу.

Ответ 6

Поймите его поздно, но я нуждался в этом и не мог найти его в другом месте. *ОБНОВИТЬ Добавлены необходимые неперечислимые методы и свойства. Быстрая реализация ES 5 (polyfill по мере необходимости):

function orderedHash(object) {
    'use strict'
    var obj = object || {}
    Object.defineProperties(this, {
        'length': {
            value: 0,
            writable: true
        },
        'keys' : {
            value: [],
            writable: true
        },
        'sortedBy': {
            value: '',
            writable: true
        }
    })
    this.hash(obj)
    obj = null
}
Object.defineProperties(orderedHash.prototype, {
    'sortByKeys': {
        value: function sortByKeys() {
            var i, len, name
            this.keys.sort(function(a, b) {   
                return a >= b ? 1 : -1
            })
            for (i=0, len = this.keys.length; i < len; ++i) {
                name = this.keys[i]
                this[i] = this[name]
            }
            this.sortedBy = 'keys'
            return null
        }   
    },
    'sortByValues': {
        value: function sortByValues() {
            var i, len, newIndex, name, ordered = [], names = this.keys.splice(0)
            this.keys = []
            for (i=0, len = this.length; i < len; ++i) {
                ordered.push(this[i])
                ordered.sort(function(a, b) {   
                    return a >= b ? 1 : -1
                })
                newIndex = ordered.lastIndexOf(this[i])
                name = names[i]
                this.keys.splice(newIndex, 0 , name)
            }
            for (i=0, len = ordered.length; i < len; ++i) {
                this[i] = ordered[i]
            }
            this.sortedBy = 'values'
            return null
        }
    },
    'insert': {
        value: function insert(name, val) {
            this[this.length] = val
            this.length += 1
            this.keys.push(name)
            Object.defineProperty(this, name, {
                value: val,
                writable: true,
                configurable: true
            })
            if (this.sortedBy == 'keys') {
                this.sortByKeys()
            } else {
                this.sortByValues()
            }
            return null
        }
    },
    'remove': {
        value: function remove(name) {
            var keys, index, i, len
            delete this[name]
            index = this.keys[name]
            this.keys.splice(index, 1)
            keys = Object.keys(this)
            keys.sort(function(a, b) {   
                return a >= b ? 1 : -1
            })
            for (i=0, len = this.length; i < len; ++i) {
                if (i >= index) {
                    this[i] = this[i + 1]
                }
            }
            delete this[this.length - 1]
            this.length -= 1
            return null
        }
    },
    'toString': {
        value: function toString() {
            var i, len, string = ""
            for (i=0, len = this.length; i < len; ++i) {
                string += this.keys[i]
                string += ':'
                string += this[i].toString()
                if (!(i == len - 1)) {
                    string += ', '
                }
            }
            return string
        }
    },
    'toArray': {
        value: function toArray() {
            var i, len, arr = []
            for (i=0, len = this.length; i < len; ++i) {
                arr.push(this[i])
            }
            return arr
        }
    },
    'getKeys': {
        value: function getKeys() {
            return this.keys.splice(0)
        }
    },
    'hash': {
        value: function hash(obj) {
            var i, len, keys, name, val
            keys = Object.keys(obj)
            for (i=0, len = keys.length; i < len; ++i) {
                name = keys[i]
                val = obj[name]
                this[this.length] = val
                this.length += 1
                this.keys.push(name)
                Object.defineProperty(this, name, {
                    value: val,
                    writable: true,
                    configurable: true
                })
            }
             if (this.sortedBy == 'keys') {
                this.sortByKeys()
            } else {
                this.sortByValues()
            }
            return null
        }
    }
})

Что происходит, так это то, что вместо Object.defineProperty() вместо назначения мы можем сделать свойства неперечислимыми, поэтому, когда мы перебираем хэш с помощью for...in или Object.keys(), получаем только упорядоченные значения, но если мы проверим hash.propertyname он будет там. Существуют методы, предназначенные для вставки, удаления, ассимиляции других объектов (hash()), сортировки по ключевым словам, сортировки по значению, преобразования в массив или строку, получения исходных имен индексов и т.д. Я добавил их в прототип, но они также неперечислимые, for...in петли все еще работают. Я не потратил времени, чтобы проверить его на непримиримых, но он отлично работает для строк, чисел и т.д.

Ответ 7

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

var order=[];
var hash={"h1":4,"h2":2,"h3":3,"h4":1};

function cmp(a,b) {
  if (hash[a] < hash[b]) return -1;
  if (hash[a] > hash[b]) return 1;
  return 0;
}

// Add initial hash object to order array
for(i in hash) order.push(i);
order.sort(cmp);
// h4:1 h2:2 h3:3 h1:4

// Add entry
hash['h5']=2.5;
order.push('h5');
order.sort(cmp);
// h4:1 h2:2 h5:2.5 h3:3 h1:4

// Delete entry
order.splice(order.indexOf('h5'), 1);
delete hash['h5'];
// h4:1 h2:2 h3:3 h1:4

// Display ordered hash array (with keys)
for(i in order) console.log(order[i],hash[order[i]]);

Ответ 8

Принимая решение @Craig_Walker, если вам интересно узнать, в каком порядке были добавлены свойства, простым решением было бы следующее:

var obj ={ }
var order = [];

function add(key, value) {
    obj[key] = value;
    order.push(key);
}

function getOldestKey() {
    var key = order.shift();
    return obj[key]
}

function getNewsetKey() {
    var key = order.pop();
    return obj[key]
}

Ответ 9

Теперь вы можете использовать встроенную карту, так как она сохраняет порядок вставки при зацикливании с помощью for in