是否有哈希码函数接受任何对象类型?

基本上,我试图创建一个对象的独特的对象,一个集合。我有一个绝妙的主意,就是只使用带有对象的 JavaScript 对象作为属性名。比如,

set[obj] = true;

这在一定程度上是有效的。它可以很好地处理字符串和数字,但是对于其他对象,它们似乎都“散列”到相同的值并访问相同的属性。有没有什么方法可以为对象生成唯一的散列值?字符串和数字如何做到这一点,我可以覆盖相同的行为吗?

259224 次浏览

JavaScript 对象只能使用字符串作为键(其他任何内容都转换为字符串)。

或者,您可以维护一个数组,该数组对所涉及的对象进行索引,并使用其索引字符串作为对该对象的引用。就像这样:

var ObjectReference = [];
ObjectReference.push(obj);


set['ObjectReference.' + ObjectReference.indexOf(obj)] = true;

显然它有点冗长,但是您可以编写几个方法来处理它,并且可以随意地获取和设置所有内容。

编辑:

您的猜测是事实——这是在 JavaScript 中定义的行为——特别是发生 toString 转换,这意味着您可以在将用作属性名的对象上定义自己的 toString 函数。奥利耶

这带来了另一个有趣的问题; 您可以在希望散列的对象上定义 toString 方法,这可以形成它们的散列标识符。

如果您真的想要设置行为(我将使用 Java 知识) ,那么您将很难在 JavaScript 中找到解决方案。大多数开发人员会建议使用一个唯一的键来表示每个对象,但是这不同于 set,因为您可以得到两个具有唯一键的相同对象。JavaAPI 通过比较哈希代码值而不是键来检查重复的值,而且由于 JavaScript 中没有对象的哈希代码值表示,所以几乎不可能做同样的事情。甚至 Prototype JS 库也承认了这个缺点,它说:

”散列可以被认为是 关联数组,绑定独特的钥匙 价值观(不一定 独一无二) ...”

Http://www.prototypejs.org/api/hash

JavaScript 规范将索引属性访问定义为对索引名执行 toString 转换,

myObject[myProperty] = ...;

myObject[myProperty.toString()] = ...;

这和 JavaScript 一样是必要的

myObject["someProperty"]

myObject.someProperty

是的,它也让我感到悲伤:

最简单的方法是给每个对象一个独特的 toString方法:

(function() {
var id = 0;


/*global MyObject */
MyObject = function() {
this.objectId = '<#MyObject:' + (id++) + '>';
this.toString= function() {
return this.objectId;
};
};
})();

我也遇到过同样的问题,这个方法很好地解决了这个问题,而且没有什么麻烦,而且比重新实现一些冗长的 Java 风格的 Hashtable并将 equals()hashCode()添加到对象类中要容易得多。只要确保不要将字符串“ < # MyObject: 12 > 粘贴到散列中,否则它将清除具有该 id 的退出对象的条目。

现在我所有的散列都是完全冷静的。我也刚刚发布了一个关于 就是这个话题的博客日志。

我选择的解决方案与 Daniel 的解决方案类似,但是我没有使用对象工厂并覆盖 toString,而是在通过 getHashCode 函数第一次请求对象时显式地将散列添加到对象中。有点乱,但更适合我的需要:)

Function.prototype.getHashCode = (function(id) {
return function() {
if (!this.hashCode) {
this.hashCode = '<hash|#' + (id++) + '>';
}
return this.hashCode;
}
}(0));

如果你想要一个像 JavaScript 中的 Java 那样的 hashCode ()函数,那就是你的了:

function hashCode(string){
var hash = 0;
for (var i = 0; i < string.length; i++) {
var code = string.charCodeAt(i);
hash = ((hash<<5)-hash)+code;
hash = hash & hash; // Convert to 32bit integer
}
return hash;
}

这是 Java 中的实现方式(按位运算符)。

请注意,hashCode 可以是正的,也可以是负的,这很正常,请参见 给出负值的 HashCode。因此,您可以考虑使用 Math.abs()和这个函数。

您所描述的内容涵盖在 Harmony 弱点地图中,它是 ECMAScript 6规范(JavaScript 的下一个版本)的一部分。即: 其中的键可以是任何(包括未定义的)且不可枚举的集合。

这意味着,除非直接引用键(任何对象!) ,否则不可能获得对值的引用和它有关的东西。对于一系列与效率和垃圾收集有关的引擎实现来说,它非常重要,但它也非常酷,因为它允许新的语义,如可撤销的访问权限和传递数据,而不暴露数据发送方。

来自 MDN:

var wm1 = new WeakMap(),
wm2 = new WeakMap();
var o1 = {},
o2 = function(){},
o3 = window;


wm1.set(o1, 37);
wm1.set(o2, "azerty");
wm2.set(o1, o2); // A value can be anything, including an object or a function.
wm2.set(o3, undefined);
wm2.set(wm1, wm2); // Keys and values can be any objects. Even WeakMaps!


wm1.get(o2); // "azerty"
wm2.get(o2); // Undefined, because there is no value for o2 on wm2.
wm2.get(o3); // Undefined, because that is the set value.


wm1.has(o2); // True
wm2.has(o2); // False
wm2.has(o3); // True (even if the value itself is 'undefined').


wm1.has(o1);   // True
wm1.delete(o1);
wm1.has(o1);   // False

当前的 Firefox、 Chrome 和 Edge 都提供了 WeakMaps。Node v7和带有 --harmony-weak-maps标志的 v6也支持它们。

除了无眼睑的答案之外,这里还有一个函数可以为任何对象返回一个可重复的、唯一的 ID:

var uniqueIdList = [];
function getConstantUniqueIdFor(element) {
// HACK, using a list results in O(n), but how do we hash e.g. a DOM node?
if (uniqueIdList.indexOf(element) < 0) {
uniqueIdList.push(element);
}
return uniqueIdList.indexOf(element);
}

正如你所看到的,它使用了一个查找列表,这是非常低效的,然而,这是我现在能找到的最好的。

如果您想使用对象作为键,您需要覆盖它们的 toString 方法,正如这里已经提到的那样。所使用的散列函数都很好,但它们只适用于相同的对象,而不适用于相同的对象。

我已经编写了一个小型库,它可以从对象创建哈希,您可以很容易地将其用于此目的。对象甚至可以有不同的顺序,散列将是相同的。在内部,您可以对散列使用不同的类型(djb2、 md5、 sha1、 sha256、 sha512、 ripemd160)。

下面是文档中的一个小例子:

var hash = require('es-hash');


// Save data in an object with an object as a key
Object.prototype.toString = function () {
return '[object Object #'+hash(this)+']';
}


var foo = {};


foo[{bar: 'foo'}] = 'foo';


/*
* Output:
*  foo
*  undefined
*/
console.log(foo[{bar: 'foo'}]);
console.log(foo[{}]);

该包可以在浏览器中使用,也可以在 Node-Js 中使用。

储存库: https://bitbucket.org/tehrengruber/es-js-hash

我的解决方案为全局 Object对象引入了一个静态函数。

(function() {
var lastStorageId = 0;


this.Object.hash = function(object) {
var hash = object.__id;


if (!hash)
hash = object.__id = lastStorageId++;


return '#' + hash;
};
}());

我认为这对 JavaScript 中的其他对象操作函数更方便。

前段时间我将 一个小的 JavaScript 模块组合在一起,生成了字符串、对象、数组等的哈希代码(我只是将它提交给了 GitHub:)

用法:

Hashcode.value("stackoverflow")
// -2559914341
Hashcode.value({ 'site' : "stackoverflow" })
// -3579752159

对于我的具体情况,我只关心键和原始值对象的相等性。对我有效的解决方案是将对象转换为其 JSON 表示形式,并使用它作为散列。有一些限制,比如键定义的顺序可能不一致; 但是就像我说的,它对我有效,因为这些对象都是在一个地方生成的。

var hashtable = {};


var myObject = {a:0,b:1,c:2};


var hash = JSON.stringify(myObject);
// '{"a":0,"b":1,"c":2}'


hashtable[hash] = myObject;
// {
//   '{"a":0,"b":1,"c":2}': myObject
// }

在 ECMAScript 6中,现在有一个 Set可以按照您希望的方式工作: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set

它已经在最新的 Chrome,FF 和 IE11中可用。

如果你想在一个查找对象中有唯一的值,你可以这样做:

创建查找对象

var lookup = {};

设置 hashcode 函数

function getHashCode(obj) {
var hashCode = '';
if (typeof obj !== 'object')
return hashCode + obj;
for (var prop in obj) // No hasOwnProperty needed
hashCode += prop + getHashCode(obj[prop]); // Add key + value to the result string
return hashCode;
}

反对

var key = getHashCode({ 1: 3, 3: 7 });
// key = '1337'
lookup[key] = true;

数组

var key = getHashCode([1, 3, 3, 7]);
// key = '01132337'
lookup[key] = true;

其他类型

var key = getHashCode('StackOverflow');
// key = 'StackOverflow'
lookup[key] = true;

最终结果

{ 1337: true, 01132337: true, StackOverflow: true }

请注意,当对象或数组为空时,getHashCode不返回任何值

getHashCode([{},{},{}]);
// '012'
getHashCode([[],[],[]]);
// '012'

这类似于@ijmacd 解决方案,只是 getHashCode没有 JSON依赖项。

参考资料: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol

您可以使用 Es6符号创建唯一的键和访问对象。 每个符号值返回的符号()是唯一的。符号值可用作对象属性的标识符; 这是数据类型的唯一用途。

var obj = {};


obj[Symbol('a')] = 'a';
obj[Symbol.for('b')] = 'b';
obj['c'] = 'c';
obj.d = 'd';

下面是返回唯一整数的简单解决方案。

function hashcode(obj) {
var hc = 0;
var chars = JSON.stringify(obj).replace(/\{|\"|\}|\:|,/g, '');
var len = chars.length;
for (var i = 0; i < len; i++) {
// Bump 7 to larger prime number to increase uniqueness
hc += (chars.charCodeAt(i) * 7);
}
return hc;
}

我会试着比其他答案更深入一点。

即使 JS 有更好的哈希支持,它也不会神奇地完美地哈希一切,在许多情况下,您将不得不定义自己的哈希函数。例如 Java 有很好的散列支持,但是您仍然需要思考和做一些工作。

一个问题是,术语哈希/哈希代码... 有加密哈希和非加密哈希。另一个问题是,您必须了解散列为什么有用,以及它是如何工作的。

当我们在谈论 JavaScript 或 Java 中的哈希时,大多数时候我们谈论的是非加密哈希,通常是针对 hashmap/hashtable 的哈希(除非我们正在研究身份验证或密码,你可以在服务器端使用 NodeJS...)。

这取决于您拥有什么样的数据以及您想要实现什么。

你的数据有一些自然的“简单”唯一性:

  • 整数的散列是... 整数,因为它是唯一的,你真幸运!
  • 一个字符串的散列... 它取决于这个字符串,如果这个字符串代表一个唯一标识符,你可以认为它是一个散列(所以不需要散列)。
  • 任何间接地几乎是唯一整数的东西都是最简单的情况
  • 这将尊重: 如果对象相等,则 hashcode 相等

你的数据有一些自然的“复合”唯一性:

  • 例如,对于 person 对象,您可以使用名字、姓氏、生日来计算散列,... ... 查看 Java 是如何做到这一点的: 字符串的好散列函数,或者使用一些其他的 ID 信息,这些信息对于您的用例来说是廉价和唯一的

你不知道你的数据会是什么:

  • 祝你好运... ... 你可以序列化字符串并用 Java 的方式散列它,但是如果字符串很大,并且不能避免冲突,比如整数的散列(self) ,那么这可能会很昂贵。

对于未知数据,没有神奇有效的哈希技术,在某些情况下,它非常容易,在其他情况下,您可能需要三思而后行。因此,即使 JavaScript/ECMAScript 添加了更多的支持,也不存在解决这个问题的神奇语言解决方案。

在实践中,你需要两样东西: 足够的独特性,足够的速度

除此之外,还有一个很棒的问题: “ hashcode 等于 if object 等于”

我把无眼睑和 KimKha 的答案结合起来。

下面是一个 angularjs 服务,它支持数字、字符串和对象。

exports.Hash = () => {
let hashFunc;
function stringHash(string, noType) {
let hashString = string;
if (!noType) {
hashString = `string${string}`;
}
var hash = 0;
for (var i = 0; i < hashString.length; i++) {
var character = hashString.charCodeAt(i);
hash = ((hash<<5)-hash)+character;
hash = hash & hash; // Convert to 32bit integer
}
return hash;
}


function objectHash(obj, exclude) {
if (exclude.indexOf(obj) > -1) {
return undefined;
}
let hash = '';
const keys = Object.keys(obj).sort();
for (let index = 0; index < keys.length; index += 1) {
const key = keys[index];
const keyHash = hashFunc(key);
const attrHash = hashFunc(obj[key], exclude);
exclude.push(obj[key]);
hash += stringHash(`object${keyHash}${attrHash}`, true);
}
return stringHash(hash, true);
}


function Hash(unkType, exclude) {
let ex = exclude;
if (ex === undefined) {
ex = [];
}
if (!isNaN(unkType) && typeof unkType !== 'string') {
return unkType;
}
switch (typeof unkType) {
case 'object':
return objectHash(unkType, ex);
default:
return stringHash(String(unkType));
}
}


hashFunc = Hash;


return Hash;
};

示例用法:

Hash('hello world'), Hash('hello world') == Hash('hello world')
Hash({hello: 'hello world'}), Hash({hello: 'hello world'}) == Hash({hello: 'hello world'})
Hash({hello: 'hello world', goodbye: 'adios amigos'}), Hash({hello: 'hello world', goodbye: 'adios amigos'}) == Hash({goodbye: 'adios amigos', hello: 'hello world'})
Hash(['hello world']), Hash(['hello world']) == Hash(['hello world'])
Hash(1), Hash(1) == Hash(1)
Hash('1'), Hash('1') == Hash('1')

输出

432700947 true
-411117486 true
1725787021 true
-1585332251 true
1 true
-1881759168 true

解释

正如你所看到的,服务的核心是由 KimKha 创建的 hash 函数。我已经在字符串中添加了类型,这样对象的结构也会影响最终的 hash 值。键被散列以防止数组 | 对象冲突。

无眼睑对象比较是用来防止无限递归的自引用对象。

用法

我创建了这个服务,这样就可以使用对象访问错误服务。这样一来,一个服务可以向给定对象注册错误,而另一个服务可以确定是否发现了错误。

也就是说

JsonValidation.js

ErrorSvc({id: 1, json: '{attr: "not-valid"}'}, 'Invalid Json Syntax - key not double quoted');

UserOfData.js

ErrorSvc({id: 1, json: '{attr: "not-valid"}'});

这种情况将会回归:

['Invalid Json Syntax - key not double quoted']

同时

ErrorSvc({id: 1, json: '{"attr": "not-valid"}'});

这个会回来的

[]

基于这个标题,我们可以生成强 民政事务局局长散列,在浏览器上下文中,它可以用来从对象、参数数组、字符串或任何东西生成唯一的散列。

async function H(m) {
const msgUint8 = new TextEncoder().encode(m)
const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8)
const hashArray = Array.from(new Uint8Array(hashBuffer))
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
console.log(hashHex)
}


/* Examples ----------------------- */
H("An obscure ....")
H(JSON.stringify( {"hello" : "world"} ))
H(JSON.stringify( [54,51,54,47] ))

以上输出在我的浏览器中,对你来说也应该是相等的:

bf1cf3fe6975fe382ab392ec1dd42009380614be03d489f23601c11413cfca2b
93a23971a914e5eacbf0a8d25154cda309c3c1c72fbb9914d47c60f3cb681588
d2f209e194045604a3b15bdfd7502898a0e848e4603c5a818bd01da69c00ad19

支持的算法:

SHA-1 (but don't use this in cryptographic applications)
SHA-256
SHA-384
SHA-512

Https://developer.mozilla.org/en-us/docs/web/api/subtlecrypto/digest#converting_a_digest_to_a_hex_string


但是,对于一个简单的 FAST 校验和散列函数,仅用于避免冲突,请参见 CRC32(内容冗余检查)

JavaScript CRC32


您可能还对通过 Web 加密 API 实现与 产生 HMAC 码产生 HMAC 码类似的方法感兴趣。

只需对 defineProperty enumerable: false使用隐藏秘密属性

它的工作 非常快:

  • 第一个读取 uniqueId: 1,257,500次/秒
  • 其他人: 309,226,485次/秒
var nextObjectId = 1
function getNextObjectId() {
return nextObjectId++
}


var UNIQUE_ID_PROPERTY_NAME = '458d576952bc489ab45e98ac7f296fd9'
function getObjectUniqueId(object) {
if (object == null) {
return null
}


var id = object[UNIQUE_ID_PROPERTY_NAME]


if (id != null) {
return id
}


if (Object.isFrozen(object)) {
return null
}


var uniqueId = getNextObjectId()
Object.defineProperty(object, UNIQUE_ID_PROPERTY_NAME, {
enumerable: false,
configurable: false,
writable: false,
value: uniqueId,
})


return uniqueId
}