2010-05-09 21 views
20

Los objetos de JavaScript no tienen orden almacenada para las propiedades (de acuerdo con la especificación). Firefox parece conservar el orden de definición de las propiedades cuando se utiliza un ciclo for...in. ¿Es este comportamiento algo en lo que puedo confiar? Si no, ¿hay algún fragmento de código JavaScript en alguna parte que implemente un tipo de hash ordenado?hash ordenado en JavaScript

+0

Ver http: //stackoverflow.com/questions/280713/elements-order-for-in-loop-in-javascript –

Respuesta

8

Esta pregunta aparece como el resultado de búsqueda superior. Después de no encontrar un hash ordenado, acabo de escribir este pequeño coffescript. Esperamos que esto ayudará gente que aterrizan en esta página:

## 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 
+7

OP pedido una solución en JavaScript -> ver más abajo para mi puerto –

+0

@FloLedermann Gracias, también, http://js2.coffee/ puede ayudar en estas situaciones por lo general. – crizCraig

+0

'if not @m_vals [k]' parece que volvería a presionar la tecla en la matriz m_keys si el valor es falso. Probablemente debería hacer un 'if (! (K en this.m_vals))' (escribiéndolo de esa manera porque no conozco CoffeeScript). –

26

No, desde el Object type is specified to be an unordered collection of properties, no puede confiar en eso. (O bien: solo puede confiar en que un objeto es una colección de propiedades no ordenadas).

Si desea tener un conjunto de hash ordenado, deberá implementarlo por su cuenta.

+0

No conduzca downvotes chupar? – Pointy

+3

downvoting ya que esto está respondiendo sólo la primera parte de la pregunta y no intenta una solución –

4

Un truco que hago es almacenar los datos en un hash desordenado regular, y luego almacenar el orden preferido en una matriz. En JS, incluso puede hacer que la matriz de órdenes forme parte del hash.

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

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

alert("I can access values by key. Here's 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) 

No es exactamente elegante, pero hace el trabajo bien.

+0

Esta solución parece bastante frágil para mí, como el mecanismo utilizado para el pedido no está encapsulado y el código de cliente tiene que saber sobre el funcionamiento interno para que funcione ... puede estar bien para proyectos desechables, pero ciertamente no está listo para producción IMO. –

0

realizar sus fines pero necesitaba esto y no podía encontrar en otro lugar. * ACTUALIZACIÓN Se agregaron métodos y propiedades no enumerables necesarios. ES rápido 5 aplicación (polyfill según sea necesario):

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 
     } 
    } 
}) 

Lo que sucede aquí es que mediante el uso de Object.defineProperty() en lugar de asignación podemos hacer que las propiedades no numerable, por lo que cuando iteramos sobre el hash utilizando for...in o Object.keys() nos solo obtenga los valores solicitados, pero si marcamos hash.propertyname estará allí. Se proporcionan métodos para insertar, eliminar, asimilar otros objetos (hash()), ordenar por clave, ordenar por valor, convertir a matriz o cadena, obtener los nombres de índice originales, etc. Los agregué al prototipo pero también son no -enumerables, for...in bucles todavía funcionan. No me tomé el tiempo para probarlo en elementos no primitivos, pero funciona bien para cadenas, números, etc.

18

@Vardhan responde en JavaScript, utilizando el cierre en lugar del OO clásico y agregando un inserto () método:

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(); 
0

Una manera bastante simple es utilizar una matriz para almacenar la orden. Necesita escribir una función de comparación personalizada para establecer el orden que necesita. El inconveniente es que tiene que ordenar la matriz y realizar un seguimiento de las relaciones, cada vez que cambie la tabla hash. No es muy claro si tiene que mantener una lista de hash cambiante.

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]]); 
0

tomando la solución @Craig_Walker, si sólo está interesado en saber en qué orden las propiedades se han insertado, una solución fácil sería:

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] 
} 
13

JavaScript en 2016, específicamente EcmaScript 6, soportes el Map built-in class.

Un objeto de mapa itera sus elementos en orden de inserción - un para ... de bucle devuelve una matriz de [clave, valor] para cada iteración.

Eso es lo que necesita. (Me pregunto por qué es la primera información en la descripción de esta estructura de datos, sin embargo.)

Por ejemplo,

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 ] 

o el ejemplo de la docs:

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); 
} 
Cuestiones relacionadas