JavaScript 中的有序哈希

问题描述 投票:0回答:10

JavaScript 对象没有存储属性的顺序(根据规范)。当使用

for...in
循环时,Firefox 似乎保留了属性定义的顺序。这种行为值得我信赖吗?如果没有,是否有一段 JavaScript 代码实现了有序哈希类型?

javascript
10个回答
66
投票

2016 年的 JavaScript,特别是 EcmaScript 6,支持

Map
内置类

Map 对象按插入顺序迭代其元素 - for...of 循环为每次迭代返回一个 [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 ]

或来自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);
}

25
投票

@Vardhan 在纯 JavaScript 中的回答,使用闭包而不是经典的 OO 并添加 insert() 方法:

function makeOrderedHash() {
    let keys = [];
    let vals = {};
    return {
        push: function(k,v) {
            if (!Object.hasOwn(vals,k)) keys.push(k);
            vals[k] = v;
        },
        insert: function(pos,k,v) {
            if (!Object.hasOwn(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}
    };
};

let myHash = makeOrderedHash();

22
投票

不,因为对象类型被指定为无序的属性集合,所以你不能依赖它。 (或者:您只能依赖对象是属性的无序集合。)

如果你想要一个有序的哈希集,你需要自己实现。


9
投票

这个问题出现在搜索结果的顶部。在找不到有序哈希后,我只是写了这个小咖啡脚本。希望这能帮助人们登陆此页面:

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

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

它并不完全优雅,但它可以完成工作。


2
投票

意识到已经晚了,但我需要这个,但在其他地方找不到。 *更新 添加了必要的非枚举方法和属性。 快速 ES 5 实现(根据需要进行填充):

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
循环仍然有效。 我没有花时间在非基元上测试它,但它对于字符串、数字等效果很好。


1
投票

采用@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]
}

1
投票

一个相当简单的方法是使用数组来存储订单。 您需要编写一个自定义比较函数来建立您需要的顺序。 缺点是每次更改哈希表时,您都必须对数组进行排序并跟踪关系。

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
投票

您现在可以使用原生 Map,因为它在使用

for in

循环时保留插入顺序

0
投票
class @OrderedHash

  constructor: (h_as_array=[])->
    @keys = []
    @vals = {}
    if h_as_array.length > 0
      i = 0
      while i < h_as_array.length
        @push(h_as_array[i], h_as_array[i+1])
        i += 2
    @

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

  length: ()-> return @keys.length
  keys:   ()-> return @keys
  val:   (k)-> return @vals[k]
  vals:   ()-> return @vals
  each: (callback)->
    return unless callback
    for k in @keys
      callback(@vals[k])
© www.soinside.com 2019 - 2024. All rights reserved.