![](/img/trans.png)
[英]Javascript - Saving class instances into hashtable/object and using key instance function
[英]Javascript HashTable use Object key
我想使用未轉換為字符串的Object
鍵創建一個哈希表。
像這樣的一些事情:
var object1 = new Object();
var object2 = new Object();
var myHash = new HashTable();
myHash.put(object1, "value1");
myHash.put(object2, "value2");
alert(myHash.get(object1), myHash.get(object2)); // I wish that it will print value1 value2
編輯:查看我的完整解決方案的答案
這是一個簡單的Map
實現,可以處理任何類型的鍵,包括對象引用,並且不會以任何方式改變鍵:
function Map() {
var keys = [], values = [];
return {
put: function (key, value) {
var index = keys.indexOf(key);
if(index == -1) {
keys.push(key);
values.push(value);
}
else {
values[index] = value;
}
},
get: function (key) {
return values[keys.indexOf(key)];
}
};
}
雖然這產生了與哈希表相同的功能,但它實際上並沒有使用哈希函數來實現,因為它遍歷數組並且具有 O(n) 的最壞情況性能。 然而,對於絕大多數合理的用例來說,這根本不應該是一個問題。 indexOf
函數由 JavaScript 引擎實現並經過高度優化。
這是一個提議:
function HashTable() {
this.hashes = {};
}
HashTable.prototype = {
constructor: HashTable,
put: function( key, value ) {
this.hashes[ JSON.stringify( key ) ] = value;
},
get: function( key ) {
return this.hashes[ JSON.stringify( key ) ];
}
};
API 與您的問題中顯示的完全相同。
然而,你不能在 js 中使用引用(所以兩個空對象對於哈希表看起來是一樣的),因為你沒有辦法得到它。 有關更多詳細信息,請參閱此答案: 如何獲取 javascript 對象引用或引用計數?
jsfiddle 演示: http : //jsfiddle.net/HKz3e/
但是,對於事物的獨特方面,您可以使用原始對象,如下所示:
function HashTable() {
this.hashes = {},
this.id = 0;
}
HashTable.prototype = {
constructor: HashTable,
put: function( obj, value ) {
obj.id = this.id;
this.hashes[ this.id ] = value;
this.id++;
},
get: function( obj ) {
return this.hashes[ obj.id ];
}
};
jsfiddle 演示: http : //jsfiddle.net/HKz3e/2/
這意味着您的對象需要有一個名為id
的屬性,您不會在其他地方使用該屬性。 如果您想將此屬性設為不可枚舉,我建議您查看defineProperty
(但它不是跨瀏覽器的,即使使用 ES5-Shim,它在 IE7 中也不起作用)。
這也意味着您可以在此哈希表中存儲的項目數量受到限制。 限制為2 53 ,即。
現在,“它不會在任何地方工作”的解決方案:使用 ES6 WeakMaps。 它們正是為此目的而完成的:將對象作為鍵。 我建議您閱讀 MDN 以獲取更多信息: https : //developer.mozilla.org/en/JavaScript/Reference/Global_Objects/WeakMap
雖然它與您的 API 略有不同(它是set
而不是put
):
var myMap = new WeakMap(),
object1 = {},
object2 = {};
myMap.set( object1, 'value1' );
myMap.set( object2, 'value2' );
console.log( myMap.get( object1 ) ); // "value1"
console.log( myMap.get( object2 ) ); // "value2"
帶有弱圖墊片的 Jsfiddle 演示: http : //jsfiddle.net/Ralt/HKz3e/9/
然而,weakmaps在FF和Chrome執行(僅當您啟用了“實驗性JavaScript功能”,在鍍鉻標志不過)。 有可用的墊片,例如: https : //gist.github.com/1269991 。 使用風險自負。
我將@Florian Margaine 的建議提升到了更高的水平,並提出了以下建議:
function HashTable(){
var hash = new Object();
this.put = function(key, value){
if(typeof key === "string"){
hash[key] = value;
}
else{
if(key._hashtableUniqueId == undefined){
key._hashtableUniqueId = UniqueId.prototype.generateId();
}
hash[key._hashtableUniqueId] = value;
}
};
this.get = function(key){
if(typeof key === "string"){
return hash[key];
}
if(key._hashtableUniqueId == undefined){
return undefined;
}
return hash[key._hashtableUniqueId];
};
}
function UniqueId(){
}
UniqueId.prototype._id = 0;
UniqueId.prototype.generateId = function(){
return (++UniqueId.prototype._id).toString();
};
用法
var map = new HashTable();
var object1 = new Object();
map.put(object1, "Cocakola");
alert(map.get(object1)); // Cocakola
//Overriding
map.put(object1, "Cocakola 2");
alert(map.get(object1)); // Cocakola 2
// String key is used as String
map.put("myKey", "MyValue");
alert(map.get("myKey")); // MyValue
alert(map.get("my".concat("Key"))); // MyValue
// Invalid keys
alert(map.get("unknownKey")); // undefined
alert(map.get(new Object())); // undefined
這是一個提案,將@Florian 的解決方案與@Laurent 的解決方案相結合。
function HashTable() {
this.hashes = [];
}
HashTable.prototype = {
constructor: HashTable,
put: function( key, value ) {
this.hashes.push({
key: key,
value: value
});
},
get: function( key ) {
for( var i = 0; i < this.hashes.length; i++ ){
if(this.hashes[i].key == key){
return this.hashes[i].value;
}
}
}
};
它不會以任何方式更改您的對象,也不依賴於 JSON.stringify。
我知道我遲到了一年,但是對於所有其他偶然發現此線程的人,我已將有序對象 stringify 編寫為 JSON,從而解決了上述難題: http : //stamat.wordpress.com/javascript-object -有序屬性字符串化/
我也在玩自定義哈希表實現,這也與該主題相關: http : //stamat.wordpress.com/javascript-quickly-find-very-large-objects-in-a-large-array/
//SORT WITH STRINGIFICATION
var orderedStringify = function(o, fn) {
var props = [];
var res = '{';
for(var i in o) {
props.push(i);
}
props = props.sort(fn);
for(var i = 0; i < props.length; i++) {
var val = o[props[i]];
var type = types[whatis(val)];
if(type === 3) {
val = orderedStringify(val, fn);
} else if(type === 2) {
val = arrayStringify(val, fn);
} else if(type === 1) {
val = '"'+val+'"';
}
if(type !== 4)
res += '"'+props[i]+'":'+ val+',';
}
return res.substring(res, res.lastIndexOf(','))+'}';
};
//orderedStringify for array containing objects
var arrayStringify = function(a, fn) {
var res = '[';
for(var i = 0; i < a.length; i++) {
var val = a[i];
var type = types[whatis(val)];
if(type === 3) {
val = orderedStringify(val, fn);
} else if(type === 2) {
val = arrayStringify(val);
} else if(type === 1) {
val = '"'+val+'"';
}
if(type !== 4)
res += ''+ val+',';
}
return res.substring(res, res.lastIndexOf(','))+']';
}
基於 Peters 的回答,但具有適當的類設計(不濫用閉包),因此這些值是可調試的。 從Map
重命名為ObjectMap
,因為Map
是一個內置函數。 還添加了exists
方法:
ObjectMap = function() {
this.keys = [];
this.values = [];
}
ObjectMap.prototype.set = function(key, value) {
var index = this.keys.indexOf(key);
if (index == -1) {
this.keys.push(key);
this.values.push(value);
} else {
this.values[index] = value;
}
}
ObjectMap.prototype.get = function(key) {
return this.values[ this.keys.indexOf(key) ];
}
ObjectMap.prototype.exists = function(key) {
return this.keys.indexOf(key) != -1;
}
/*
TestObject = function() {}
testA = new TestObject()
testB = new TestObject()
om = new ObjectMap()
om.set(testA, true)
om.get(testB)
om.exists(testB)
om.exists(testA)
om.exists(testB)
*/
使用JSON.stringify()
對我來說完全JSON.stringify()
,並且讓客戶端無法真正控制如何唯一標識它們的鍵。 用作鍵的對象應該有一個散列函數,但我的猜測是在大多數情況下覆蓋toString()
方法,以便它們返回唯一的字符串,會正常工作:
var myMap = {};
var myKey = { toString: function(){ return '12345' }};
var myValue = 6;
// same as myMap['12345']
myMap[myKey] = myValue;
顯然, toString()
應該對對象的屬性做一些有意義的事情來創建一個唯一的字符串。 如果您想強制您的密鑰有效,您可以創建一個包裝器並在get()
和put()
方法中添加如下檢查:
if(!key.hasOwnProperty('toString')){
throw(new Error('keys must override toString()'));
}
但是如果你要完成那么多工作,你也可以使用toString()
以外的東西; 使您的意圖更加清晰的東西。 所以一個非常簡單的建議是:
function HashTable() {
this.hashes = {};
}
HashTable.prototype = {
constructor: HashTable,
put: function( key, value ) {
// check that the key is meaningful,
// also will cause an error if primitive type
if( !key.hasOwnProperty( 'hashString' ) ){
throw( new Error( 'keys must implement hashString()' ) );
}
// use .hashString() because it makes the intent of the code clear
this.hashes[ key.hashString() ] = value;
},
get: function( key ) {
// check that the key is meaningful,
// also will cause an error if primitive type
if( !key.hasOwnProperty( 'hashString' ) ){
throw( new Error( 'keys must implement hashString()' ) );
}
// use .hashString() because it make the intent of the code clear
return this.hashes[ key.hashString() ];
}
};
受到@florian 的啟發,這里有一種 id 不需要JSON.stringify
:
'use strict';
module.exports = HashTable;
function HashTable () {
this.index = [];
this.table = [];
}
HashTable.prototype = {
constructor: HashTable,
set: function (id, key, value) {
var index = this.index.indexOf(id);
if (index === -1) {
index = this.index.length;
this.index.push(id);
this.table[index] = {};
}
this.table[index][key] = value;
},
get: function (id, key) {
var index = this.index.indexOf(id);
if (index === -1) {
return undefined;
}
return this.table[index][key];
}
};
我采用了@Ilya_Gazman 解決方案並通過將 '_hashtableUniqueId' 設置為不可枚舉的屬性來改進它(它不會出現在 JSON 請求中,也不會在 for 循環中列出)。 還刪除了 UniqueId 對象,因為僅使用 HastTable 函數閉包就足夠了。 有關使用詳情,請參閱 Ilya_Gazman 帖子
function HashTable() {
var hash = new Object();
return {
put: function (key, value) {
if(!HashTable.uid){
HashTable.uid = 0;
}
if (typeof key === "string") {
hash[key] = value;
} else {
if (key._hashtableUniqueId === undefined) {
Object.defineProperty(key, '_hashtableUniqueId', {
enumerable: false,
value: HashTable.uid++
});
}
hash[key._hashtableUniqueId] = value;
}
},
get: function (key) {
if (typeof key === "string") {
return hash[key];
}
if (key._hashtableUniqueId === undefined) {
return undefined;
}
return hash[key._hashtableUniqueId];
}
};
}
最好的解決方案是盡可能使用WeakMap (即當您的目標瀏覽器支持它時)
否則,您可以使用以下解決方法(Typescript 編寫和碰撞安全):
// Run this in the beginning of your app (or put it into a file you just import)
(enableObjectID)();
const uniqueId: symbol = Symbol('The unique id of an object');
function enableObjectID(): void {
if (typeof Object['id'] !== 'undefined') {
return;
}
let id: number = 0;
Object['id'] = (object: any) => {
const hasUniqueId: boolean = !!object[uniqueId];
if (!hasUniqueId) {
object[uniqueId] = ++id;
}
return object[uniqueId];
};
}
然后你可以簡單地為你的代碼中的任何對象獲取一個唯一的編號(就像指針地址一樣)
let objectA = {};
let objectB = {};
let dico = {};
dico[(<any>Object).id(objectA)] = "value1";
// or
dico[Object['id'](objectA);] = "value1";
// If you are not using typescript you don't need the casting
dico[Object.id(objectA)] = "value1"
我知道我遲到了,但這里有一個簡單的HashMap
實現:
Function.prototype.toJSON = Function.prototype.toString; //taken from https://stackoverflow.com/questions/1249531/how-to-get-a-javascript-objects-class function getNativeClass(obj) { if (typeof obj === "undefined") return "undefined"; if (obj === null) return "null"; return Object.prototype.toString.call(obj).match(/^\\[object\\s(.*)\\]$/)[1]; } function globals() { if (typeof global === "object") //node return global; return this; } function lookup(x) { return globals()[x]; } function getAnyClass(obj) { if (typeof obj === "undefined") return "undefined"; if (obj === null) return "null"; return obj.constructor.name; } //taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value#examples var getCircularReplacer = () => { const seen = new WeakSet(); return (key, value) => { if (typeof value === "object" && value !== null) { if (seen.has(value)) { return "[Circular]"; } seen.add(value); } return value; }; }; function encode(x) { if (typeof x === "object" && x !== null) { var y = myClone(x); x = Object.getPrototypeOf(x); for (var i = 0; i < Object.getOwnPropertyNames(y).length; i++) { //Make enumerable x[Object.getOwnPropertyNames(y)[i]] = y[Object.getOwnPropertyNames(y)[i]]; } } return getAnyClass(x) + " " + JSON.stringify(x, getCircularReplacer()); } function decode(x) { var a = x.split(" ").slice(1).join(" "); //OBJECT if (typeof lookup(x.split(" ")[0])) { return new (lookup(x.split(" ")[0]))(JSON.parse(a)) } else { return JSON.parse(a); } } //taken from https://github.com/feross/fromentries/blob/master/index.js /*! fromentries. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */ function fromEntries(iterable) { return [...iterable].reduce((obj, [key, val]) => { obj[key] = val return obj }, {}) } var toEnumerable = (obj) => { return fromEntries( Object.getOwnPropertyNames(obj).map(prop => [prop, obj[prop]]) ); }; //taken from https://stackoverflow.com/questions/41474986/how-to-clone-a-javascript-es6-class-instance function myClone(instanceOfBlah) { if (typeof instanceOfBlah !== "object" || !instanceOfBlah) { return instanceOfBlah; } const clone = Object.assign({}, toEnumerable(instanceOfBlah)); const Blah = instanceOfBlah.constructor; Object.setPrototypeOf(clone, Blah.prototype); return clone; } function HashMap(a) { if (typeof a === "undefined") { a = []; } a = Array.from(a); a = a.map((e) => [encode(e[0]), e[1]]); this.a = a; } HashMap.from = function (a) { var temp = myClone(a); //convert to array a = []; for (var i = 0; i < Object.getOwnPropertyNames(temp).length; i++) { a.push([Object.getOwnPropertyNames(temp)[i], temp[Object.getOwnPropertyNames(temp)[i]]]); } return new HashMap(a); } HashMap.prototype.put = function (x, y) { this.a.push([encode(x), y]); } HashMap.prototype.get = function (x) { var t1 = this.a.map((e) => e[0]); return this.a[t1.indexOf(encode(x))][1]; } HashMap.prototype.length = function () { return this.a.length; } HashMap.prototype.toString = function () { var result = []; for (var i = 0; i < this.length(); i++) { result.push(JSON.stringify(decode(this.a[i][0]), getCircularReplacer()) + " => " + this.a[i][1]); } return "HashMap {" + result + "}"; } var foo = new HashMap(); foo.put("SQRT3", Math.sqrt(3)); foo.put({}, "bar"); console.log(foo.get({})); console.log(foo.toString());
請注意,它是有序的。 方法:
put
: 添加一個項目get
: 訪問一個項目from
(靜態):從 JavaScript 對象轉換toString
: 轉換為字符串縮小且未經測試:
function getNativeClass(t){return void 0===t?"undefined":null===t?"null":Object.prototype.toString.call(t).match(/^\[object\s(.*)\]$/)[1]}function globals(){return"object"==typeof global?global:this}function lookup(t){return globals()[t]}function getAnyClass(t){return void 0===t?"undefined":null===t?"null":t.constructor.name}Function.prototype.toJSON=Function.prototype.toString;var getCircularReplacer=()=>{const t=new WeakSet;return(e,r)=>{if("object"==typeof r&&null!==r){if(t.has(r))return"[Circular]";t.add(r)}return r}};function encode(t){if("object"==typeof t&&null!==t){var e=myClone(t);t=Object.getPrototypeOf(t);for(var r=0;r<Object.getOwnPropertyNames(e).length;r++)t[Object.getOwnPropertyNames(e)[r]]=e[Object.getOwnPropertyNames(e)[r]]}return getAnyClass(t)+" "+JSON.stringify(t,getCircularReplacer())}function decode(t){var e=t.split(" ").slice(1).join(" ");return lookup(t.split(" ")[0]),new(lookup(t.split(" ")[0]))(JSON.parse(e))}function fromEntries(t){return[...t].reduce((t,[e,r])=>(t[e]=r,t),{})}var toEnumerable=t=>fromEntries(Object.getOwnPropertyNames(t).map(e=>[e,t[e]]));function myClone(t){if("object"!=typeof t||!t)return t;const e=Object.assign({},toEnumerable(t)),r=t.constructor;return Object.setPrototypeOf(e,r.prototype),e}function HashMap(t){void 0===t&&(t=[]),t=(t=Array.from(t)).map(t=>[encode(t[0]),t[1]]),this.a=t}HashMap.from=function(t){var e=myClone(t);t=[];for(var r=0;r<Object.getOwnPropertyNames(e).length;r++)t.push([Object.getOwnPropertyNames(e)[r],e[Object.getOwnPropertyNames(e)[r]]]);return new HashMap(t)},HashMap.prototype.put=function(t,e){this.a.push([encode(t),e])},HashMap.prototype.get=function(t){var e=this.a.map(t=>t[0]);return this.a[e.indexOf(encode(t))][1]},HashMap.prototype.length=function(){return this.a.length},HashMap.prototype.toString=function(){for(var t=[],e=0;e<this.length();e++)t.push(JSON.stringify(decode(this.a[e][0]),getCircularReplacer())+" => "+this.a[e][1]);return"HashMap {"+t+"}"};
此外,您可以通過更改encode
和decode
功能來自定義編碼器和解碼器。
正如弗洛里安的回答一樣,你不能在 js 中使用引用(所以兩個空對象對於哈希表看起來是一樣的)。
class Dict{
constructor(){
this.keys = [];
this.values = [];
this.set = this.set.bind(this);
}
set(key, value){
this.keys.push(key);
this.values.push(value);
}
get(key){
return this.values[this.keys.indexOf(key)];
}
all(){
return this.keys.map((kk, ii)=>[kk, this.values[ii]]);
}
}
let d1 = new Dict();
let k1 = {1: 'a'};
d1.set(k1, 2);
console.log(d1.get(k1)); // 2
let k2 = {2: 'b'};
d1.set(k2, 3);
console.log(d1.all());
// [ [ { '1': 'a' }, 2 ], [ { '2': 'b' }, 3 ] ]
當您說您不希望將您的對象鍵轉換為字符串時,我會假設這是因為您只是不希望將對象的整個代碼內容用作鍵。 當然,這完全有道理。
雖然 Javascript 本身沒有“哈希表”,但您可以通過簡單地覆蓋 Object 的 prototype.toString 並返回每個實例唯一的有效鍵值來完成您要查找的內容。 一種方法是使用Symbol() :
function Obj () {
this.symbol = Symbol() // Guaranteed to be unique to each instance
}
Obj.prototype.toString = function () {
return this.symbol // Return the unique Symbol, instead of Obj's stringified code
}
let a = new Obj()
let b = new Obj()
let table = {}
table[a] = 'A'
table[b] = 'B'
console.log(table) // {Symbol(): 'A', Symbol(): 'B'}
console.log(table[a]) // A
console.log(table[b]) // B
查找對象時只需使用嚴格相等運算符: ===
var objects = [];
objects.push(object1);
objects.push(object2);
objects[0] === object1; // true
objects[1] === object1; // false
實現將取決於您如何在HashTable
類中存儲對象。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.