How does JSON.stringify make memoize function work? - javascript

_ .memoize = function(func) {
var hash = {};
return function() {
var arg = JSON.stringify(arguments);
if (hash[arg] === undefined) {
hash[arg] = func.apply(this, arguments);
}
return hash[arg];
};
};
Hello,
I am trying to implement the memoize underscore function. I have a question regarding to JSON.stringify.
In the if statement where it checks if the arg already exist or not in the hash. Why using JSON.stringify make it possible to check wether the input arg exist or not in the hash. I mean if we pass the arguments array without converting them using JSON.stringify, then we cannot check because we are passing an entire array. However, when using JSON.stringify, it makes it work. So how does JSON.stringify make it possible to check ?

The hash is a JavaScript object, which uses strings as keys. You cannot use an array (or array-like, in the case of arguments) there, so it needs to be converted to a string.
If no custom conversion is done, then the default serialisation would be "[object Arguments]" for any value of arguments. This is not unique and will not work with the intention of memoization.
var hash = {};
var i = 0;
//a naive function that takes anything and puts it in a hash with a unique value
function populateUnique() {
hash[arguments] = "Hello" + i;
i++;
}
populateUnique("a");
populateUnique("b");
populateUnique("c", "d", "e");
console.log(hash); //only shows the last thing, as it it's always overridden.
This implementation chooses to employ JSON.stringify because it is quite straight forward - you could implement a custom serialisation function, but there is already one provided, so this is the simplest way to do it.
Should be noted that JSON.stringify is not bulletproof. It is easy to use and covers a lot of cases, but may blow up, for example, if you have circular references:
var foo = {};
foo.bar = foo;
JSON.stringify(foo);
Since the memoize function does not control what will be passed in as arguments, it's possible that one of them, that is normally perfectly valid, will throw an error.
Another problem is if any of the arguments has its own toJSON method - this will be used for serialization, so you could end up in an interesting situation:
var a = 42;
var b = {
firstname: "Fred",
lastname: "Bloggs",
id: 42,
toJSON: function() { return this.id }
}
console.log(JSON.stringify(b));
console.log(a == JSON.stringify(b));

It's because only strings can be used as keys in javascript objects.
For example:
var key = {a:1};
var map = {};
map[key] = 1;
// {'[object Object]': 1}
This will result in every arguments combination being saved in the same key.
Using JSON.stringify transform the arguments list in an unique string that can in turn be used as an unique object key.
var key = {a:1};
var map = {};
map[JSON.stringify(key)] = 1;
// {'{"a":1}': 1}
This way, every time you call the function with the same arguments, JSON.stringify will return the same unique string and you can use that to check if you already have a cached result for that set of arguments, and if so, returning the cached value.

Related

Mapping JSON Objects to Javascript Objects

When using AJAX, I tend to pass objects from my server to Javascript in the form of JSON objects (aka Javascript). Certain functions within my Javascript rely on the specific type of object I am using. For instance, lets use a phone number for example. I have a constructor:
function PhoneNumber(number, type, isPrimary, contactId, id, className) {
this.number = number;
this.type = type;
this.isPrimary = isPrimary;
this.contactId = contactId;
this.id = id;
this.className = className;
}
Which I use when creating a phone number object in my Javascript. In some situations I don't create the object in JS, I get the object from the server so it comes in the form of a generic object with the exact same fields. So when my code relies on the specific type by using something such as this:
var objType = objArray[i].constructor.name;
var mappedObj;
switch(objType) {
case 'PhoneNumber':
currentArray = currentArray.phone;
//Convert response to javascript object.
mappedObj = mapPhone(jsonResponse[i]);
break;
case 'Email':
currentArray = currentArray.email;
mappedObj = mapEmail(jsonResponse[i]);
break;
case 'Address':
currentArray = currentArray.address;
mappedObj = mapAddress(jsonResponse[i]);
break;
case 'Website':
currentArray = currentArray.website;
mappedObj = mapWebsite(jsonResponse[i]);
}
In this situation, I check the name of the objects constructor and set certain variables based on that name. If the object I check the name on is a JSON from the server, it simply gives me a generic "Object" response and thus the code does not work. I get around this by using a mapping function for each object such as:
function mapPhone(phoneObj) {
var id = phoneObj.id;
var contactId = phoneObj.contactId;
var number = phoneObj.number;
var type = phoneObj.type;
var primary = phoneObj.isPrimary;
var className = phoneObj.className;
var phoneNumber = new PhoneNumber(number, type, primary, contactId, id, className);
return phoneNumber;
}
This works just fine, but to me seems a little redundant. Is this the best way to solve the JSON Object problem, or is there a better solution? I understand this is more of a "Am I doing this the best way possible" type of question, but I repeat this type of logic CONSTANTLY in my Javascript code and I figure I might as well get another opinion or two on whether or not its the proper way to do this before I have to spend hour upon hour fixing it in the future.
EDIT: I ended up accepting a jQuery solution because I happen to use jQuery in my project. There are however multiple solutions that also worked for me before I found the jQuery option. They just weren't quite as clean and efficient.
The following requires you to have the same properties in your object and your JSON object.
var phoneNumber = $.extend(new PhoneNumber(), yourJSONObject);
This basically creates a new PhoneNumber object and then copies all properties from your JSON object onto it. The $.extend() method is from jQuery, but you could also use as similar method from e.g. Underscore.js or one of the other js libraries/frameworks.
This similar question has a lot of interesting answers:
Parse JSON String into a Particular Object Prototype in JavaScript
Based off the poster's own answer, I think this would be an effective solution for you:
function recastJSON(jsonObject) {
// return generic object if objectType is not specified
if (!jsonObject.objectType)
return jsonObject;
// otherwise create a new object of type specified
var obj = eval('new '+jsonObject.objectType+'()');
for(var i in jsonObject)
obj[i] = jsonObject[i];
return obj;
}
You will need to add objectType to the JSON objects you are receiving to define the javascript class you want to instantiate. Then when you call this function, it will cast the object to that type and copy the data over (including the variable 'objectType').
Using your phone number example, your code would look like this:
// generic object from JSON
var genericObject = {objectType:'PhoneNumber', number:'555-555-5555', type:'home', primary:true, contactId:123, id:1234, className:'stdPhone'};
var phoneObject = recastJSON(genericObject);
AFAIK, in everything that is not IE, you can do this:
// define a class
var Foo = function(name) {
this.name = name;
}
// make a method
Foo.prototype.shout = function() {
return "I am " + this.name;
}
// make a simple object from JSON:
var x = JSON.parse('{ "name": "Jason" }');
// force its class to be Foo
x.__proto__ = Foo.prototype;
// the method works
x.shout();
Unfortunately, IE does not support the __proto__ accessor, so what you would need to do is first create an empty instance of your object, then just copy everything over:
// make a simple object from JSON:
var x = JSON.parse('{ "name": "Jason" }');
// make an empty Foo
var y = Object.create(Foo.prototype);
// copy stuff over
for (a in x) {
y[a] = x[a];
}
y.shout();
Both of these approaches are quite a bit more generic than your mapWhatever functions, keeping it DRY.
If not supporting older browsers is ok, You can use Object.create to do the mapping for you. (dropping the shim—at least the shim at MDN—in will not fix older browsers, since that shim does not accept the second parameter.)
DEMO
function makeThisExtend(obj, CtorFunc) {
for (var k in obj)
if ({}.hasOwnProperty.call(obj, k))
obj[k] = { value: obj[k] };
return Object.create(CtorFunc.prototype, obj);
}
var objFromServer = { Number: "123", id: 5 };
objFromServer = makeThisExtend(objFromServer, PhoneNumber);
alert(objFromServer.Number + " " + objFromServer.id); //123 5
alert(objFromServer.constructor); //function PhoneNumber ...

How can I store reference to a variable within an array?

I'm trying to create an array that maps strings to variables. It seems that the array stores the current value of the variable instead of storing a reference to the variable.
var name = "foo";
var array = [];
array["reference"] = name;
name = "bar";
// Still returns "foo" when I'd like it to return "bar."
array["reference"];
Is there a way to make the array refer to the variable?
Put an object into the array instead:
var name = {};
name.title = "foo";
var array = [];
array["reference"] = name;
name.title = "bar";
// now returns "bar"
array["reference"].title;
You can't.
JavaScript always pass by value. And everything is an object; var stores the pointer, hence it's pass by pointer's value.
If your name = "bar" is supposed to be inside a function, you'll need to pass in the whole array instead. The function will then need to change it using array["reference"] = "bar".
Btw, [] is an array literal. {} is an object literal.
That array["reference"] works because an Array is also an object, but array is meant to be accessed by 0-based index. You probably want to use {} instead.
And foo["bar"] is equivalent to foo.bar. The longer syntax is more useful if the key can be dynamic, e.g., foo[bar], not at all the same with foo.bar (or if you want to use a minimizer like Google's Closure Compiler).
Try pushing an object to the array instead and altering values within it.
var ar = [];
var obj = {value: 10};
ar[ar.length] = obj;
obj.value = 12;
alert(ar[0].value);
My solution to saving a reference is to pass a function instead:
If the variable you want to reference is called myTarget, then use:
myRef = function (newVal) {
if (newVal != undefined) myTarget = newVal;
return myTarget;
}
To read the value, use myRef();. To set the value, use myRef(<the value you want to set>);.
Helpfully, you can also assign this to an array element as well:
var myArray = [myRef];
Then use myArray[0]() to read and myArray[0](<new value>) to write.
Disclaimer: I've only tested this with a numerical target as that is my use case.
My solution to saving a reference is to pass a function instead:
If the variable you want to reference is called 'myTarget', then use:
myRef = function (newVal) {
if (newVal != undefined)
myTarget = newVal;
return myTarget;
}
To read the value, use myRef();. To set the value, use myRef(value_to_set);.
Helpfully, you can also assign this to an array element as well:
var myArray = [myRef];
Then use myArray0 to read and myArray[0](value_to_set) to write.
Disclaimer: I've only tested this with a numerical target as that is my use case.

Build javascript function to accept default values

function myFunct(){
//blah blah blah
}
how to build a function with key/value pair parameters so when i call this, it will be called like this?
myFunt(prm1:'value1',prm2:'value2',prm3:'value3');
so, when i only need to call the 3rd param, i will do this:
myFunct(prm3:'value3');
Specify some defaults in your function and then call using only the params you need:
function myFunct(param) {
var prm1 = param.prm1 || "default1";
var prm2 = param.prm2 || "default2";
var prm3 = param.prm3 || "default3";
}
Call it using a param object, like this:
myFunct({prm3:'value3'});
prm1 and prm2 will get the default values, but prm3 will get what you passed.
You can specify any combination of values in your param object. Any you leave out will be populated with their default values.
If you're using jQuery you can make this a little prettier using $.extend:
function myFunct(param) {
var parameters = $.extend(true, /* deep copy */
{prm1: "default1", prm2: "default2", prm3: "default3"}, param);
};
The first object given to extend will serve as the default and the properties in your param object will be merged in when present.
function myFunt(jsonObj){
var param3 = jsonObj.prm3;
}
Call the function like this:
myFunt({prm3: 'value3'});
As far as I know, that is not supported by Javascript. You can however achieve a similar effect by just passing one argument, that is an object.
Call:
foo({ prm1: 'value1', prm2: 'value2', prm3: 'value3'})
Function definition:
function foo(args)
{
//use values accordingly
var prm1 = args.prm1;
}
Javascript doesn't directly support this syntax (named parameters, specifically), so you'll have to resort to some sort of workaround. There are two approaches that work in certain situations:
If you only need certain contiguous subsets of the parameters supplied, you can just declare them in order and then manually check whether the remaining parameters have been supplied. Javascript lets you call a function with less than the number of declared parameters, with the unpassed ones defaulting to undefined. Hence you could do something like this:
function myFunc(prm3 ,prm1, prm1) {
// Use defaults if not supplied
if (typeOf(prm1) == 'undefined') prm1 = 'value1';
if (typeOf(prm2) == 'undefined') prm2 = 'value2';
// Rest of function as normal
...
};
Alternatively, if you need more flexibility (i.e. either prm3 or prm2 could be supplied on their own, you'll need some way of associating a name with the value. Hence you'd have to pass all parameters in as an associate array, which is javascript is simply an object:
function myFunc(prms) {
// Unpack actual arguments - if not supplied, will be 'undefined'
var prm1 = prms.prm1;
var prm2 = prms.prm2;
var prm3 = prms.prm3;
// Rest of function as normal
...
};
// Call this function something like the following:
myFunc({prm1: 'Hello', prm3: 'World'});
Now both of these approaches have disavantages, but if you need to support optional arguments they're the only ways I'm aware of to do it.
Use arrays.
either:
var param = [null, 'value2', null]
or
var param = ['value1', 'value2', 'value3']
with function:
myFunct(param);
function myFunct(array){
//blah blah blah
}
This is most easily done by passing an object in as an argument:
myFunct({prm1: 'value', prm2: 'value', prm3: 'value'});
However, if you want any omitted key to have a default value, the most common methodology for this is to use $.extend (assuming you are using jQuery). Example:
function myFunct(obj) {
var defaults = {
prm1: 'value',
prm2: 'value',
prm3: 'value'
};
// Set any defaults
obj = $.extend(defaults, obj);
// Output the results to the console
for (var i in obj) {
if (obj.hasOwnProperty(i)) {
console.log("obj[" + i + "] =", obj[i]);
}
}
}
Then you can call some sample code:
myFunct(); // will output all three params as being "value"
myFunct({prm3: 'test'}); // will output first two as being "value", third as being "test"
If you are not using jQuery, you can use the method described by lwburk above. However, lwburk's method gets rather lengthy if you have a lot of options possible.
The reason the extend method works is that it takes the first object (default in this case) and overwrites all values in the first object (default) with the ones specified in the second object (obj in this case). So $.extend({a: 1, b: 2}, {a: 2}) returns {a: 2, b: 2}; note that the a value was taken from the second object, but the b value from the first was untouched because it was not specified in the second.
Other libraries have similar extend methods (not an extensive list):
jQuery's extend (as linked above)
Prototype's extend
MooTools's merge
Or you could write your own, or use the code from any of the above.

Javascript: How can I transform an array?

I have this on a javascript var: (it's a http returned data, and I don't know if it's an array or string - (how can we see that?) - Update: using typeof returned "string", so it's a string.
[{"nomeDominio":"gggg.fa"},{"nomeDominio":"rarar.fa"}]
How can we pass/transform that, into something like this:
["gggg.fa","rarar.fa"]
?
Thanks a lot,
MEM
You can figure out if is a string or an already parsed object by checking the type of your variable, e.g.:
ajax('url', function (response) {
alert(typeof response);
});
You will now figure out if it's a "string" or an Array "object".
If it's a string, you can use the JSON.parse method as #alcuadrado suggest, otherwise you can simply use the array.
Several answers suggest the use of the for-in statement to iterate over the array elements, I would discourage you to use it for that.
The for-in statement should be used to enumerate over object properties, to iterate over Arrays or Array-like objects, use a sequential loop as #Ken Redler suggests.
You should really avoid for-in for this purpose because:
The order of enumeration is not guaranteed, properties may not be visited in the numeric order.
Enumerates also inherited properties.
You can also use the Array.prototype.map method to meet your requirements:
var response = [{"nomeDominio":"gggg.fa"},{"nomeDominio":"rarar.fa"}];
var array = response.map(function (item) { return item.nomeDominio; });
// ["gggg.fa", "rarar.fa"]
This question is strongly related with this one.
I would suggest reading my answer there, as it would really help; and with a little variation, it would just work:
var responseString = '[{"nomeDominio":"gggg.fa"},{"nomeDominio":"rarar.fa"}]',
responseObject = JSON.parse(responseString),
nombresDeDominio = [];
for(var i in responseObject) {
nombresDeDominio.push(responseObject[i].nomeDominio)
}
Suerte!
Assuming your data always looks like that, you can do something like this:
var foo = [{"nomeDominio":"gggg.fa"},{"nomeDominio":"rarar.fa"}];
var newarr = [];
for ( var i=0,j=foo.length;i<j;i++ ) {
newarr.push( foo[i]['nomeDominio'] );
}
Here's a working fiddle.
function transform(array, f) {
var ret = [];
$.each(array, function(index) {
var v = f.call(this, index);
if(v) {
ret.push(v);
}
});
return ret;
}
var result = transform(
[{"nomeDominio":"gggg.fa"},{"nomeDominio":"rarar.fa"}],
function() { return this.nomeDominio; }
);
alert(result.toString());
it's a http returned data, and I don't
know if it's an array or string
It's JSON, and you can use it directly in JavaScript.
If you transform it into your array, you will lose the association key / value ; are you sure it's what you want ?
Okay, firstly to get the type of a "thing", use the "typeof" operator (note that the type of an array is an object, not 'array'!):
var a = "string";
var b = 1;
var c = new Array();
alert(typeof(a)); // string
alert(typeof(b)); // number
alert(typeof(c)); // object
To get at the values in the associative array (assuming it is one), you can just loop through it, like so:
var d = [{"nomeDominio":"gggg.fa"},{"nomeDominio":"rarar.fa"}];
d["bob"] = "alice";
d["gary"] = "stephen";
for(var key in d) {
alert(d[key]);
}

Is there hash code function accepting any object type?

Basically, I'm trying to create an object of unique objects, a set. I had the brilliant idea of just using a JavaScript object with objects for the property names. Such as,
set[obj] = true;
This works, up to a point. It works great with string and numbers, but with other objects, they all seem to "hash" to the same value and access the same property. Is there some kind of way I can generate a unique hash value for an object? How do strings and numbers do it, can I override the same behavior?
If you want a hashCode() function like Java's in JavaScript, that is yours:
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;
}
That is the way of implementation in Java (bitwise operator).
Please note that hashCode could be positive and negative, and that's normal, see HashCode giving negative values. So, you could consider to use Math.abs() along with this function.
JavaScript objects can only use strings as keys (anything else is converted to a string).
You could, alternatively, maintain an array which indexes the objects in question, and use its index string as a reference to the object. Something like this:
var ObjectReference = [];
ObjectReference.push(obj);
set['ObjectReference.' + ObjectReference.indexOf(obj)] = true;
Obviously it's a little verbose, but you could write a couple of methods that handle it and get and set all willy nilly.
Edit:
Your guess is fact -- this is defined behaviour in JavaScript -- specifically a toString conversion occurs meaning that you can can define your own toString function on the object that will be used as the property name. - olliej
This brings up another interesting point; you can define a toString method on the objects you want to hash, and that can form their hash identifier.
The easiest way to do this is to give each of your objects its own unique toString method:
(function() {
var id = 0;
/*global MyObject */
MyObject = function() {
this.objectId = '<#MyObject:' + (id++) + '>';
this.toString= function() {
return this.objectId;
};
};
})();
I had the same problem and this solved it perfectly for me with minimal fuss, and was a lot easier that re-implementing some fatty Java style Hashtable and adding equals() and hashCode() to your object classes. Just make sure that you don't also stick a string '<#MyObject:12> into your hash or it will wipe out the entry for your exiting object with that id.
Now all my hashes are totally chill. I also just posted a blog entry a few days ago about this exact topic.
What you described is covered by Harmony WeakMaps, part of the ECMAScript 6 specification (next version of JavaScript). That is: a set where the keys can be anything (including undefined) and is non-enumerable.
This means it's impossible to get a reference to a value unless you have a direct reference to the key (any object!) that links to it. It's important for a bunch of engine implementation reasons relating to efficiency and garbage collection, but it's also super cool for in that it allows for new semantics like revokable access permissions and passing data without exposing the data sender.
From 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
WeakMaps are available in current Firefox, Chrome and Edge. They're also supported in Node v7 , and in v6 with the --harmony-weak-maps flag.
The solution I chose is similar to Daniel's, but rather than use an object factory and override the toString, I explicitly add the hash to the object when it is first requested through a getHashCode function. A little messy, but better for my needs :)
Function.prototype.getHashCode = (function(id) {
return function() {
if (!this.hashCode) {
this.hashCode = '<hash|#' + (id++) + '>';
}
return this.hashCode;
}
}(0));
For my specific situation I only care about the equality of the object as far as keys and primitive values go. The solution that worked for me was converting the object to its JSON representation and using that as the hash. There are limitations such as order of key definition potentially being inconsistent; but like I said it worked for me because these objects were all being generated in one place.
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
// }
I put together a small JavaScript module a while ago to produce hashcodes for strings, objects, arrays, etc. (I just committed it to GitHub :) )
Usage:
Hashcode.value("stackoverflow")
// -2559914341
Hashcode.value({ 'site' : "stackoverflow" })
// -3579752159
In ECMAScript 6 there's now a Set that works how you'd like: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
It's already available in the latest Chrome, FF, and IE11.
The JavaScript specification defines indexed property access as performing a toString conversion on the index name. For example,
myObject[myProperty] = ...;
is the same as
myObject[myProperty.toString()] = ...;
This is necessary as in JavaScript
myObject["someProperty"]
is the same as
myObject.someProperty
And yes, it makes me sad as well :-(
Based on the title, we can generate strong SHA hashes, in a browser context, it can be used to generate a unique hash from an object, an array of params, a string, or whatever.
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] ))
The above output in my browser, it should be equal for you too:
bf1cf3fe6975fe382ab392ec1dd42009380614be03d489f23601c11413cfca2b
93a23971a914e5eacbf0a8d25154cda309c3c1c72fbb9914d47c60f3cb681588
d2f209e194045604a3b15bdfd7502898a0e848e4603c5a818bd01da69c00ad19
Supported algos:
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
However, for a simple FAST checksum hash function, made only for collision avoidance, see CRC32 (Content Redundancy Check)
JavaScript CRC32
You might also be interested by this similar method to generate HMAC codes via the web crypto api.
Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol
you can use Es6 symbol to create unique key and access object.
Every symbol value returned from Symbol() is unique. A symbol value may be used as an identifier for object properties; this is the data type's only purpose.
var obj = {};
obj[Symbol('a')] = 'a';
obj[Symbol.for('b')] = 'b';
obj['c'] = 'c';
obj.d = 'd';
Here's my simple solution that returns a unique integer.
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;
}
My solution introduces a static function for the global Object object.
(function() {
var lastStorageId = 0;
this.Object.hash = function(object) {
var hash = object.__id;
if (!hash)
hash = object.__id = lastStorageId++;
return '#' + hash;
};
}());
I think this is more convenient with other object manipulating functions in JavaScript.
I will try to go a little deeper than other answers.
Even if JS had better hashing support it would not magically hash everything perfectly, in many cases you will have to define your own hash function. For example Java has good hashing support, but you still have to think and do some work.
One problem is with the term hash/hashcode ... there is cryptographic hashing and non-cryptographic hashing. The other problem, is you have to understand why hashing is useful and how it works.
When we talk about hashing in JavaScript or Java most of the time we are talking about non-cryptographic hashing, usually about hashing for hashmap/hashtable (unless we are working on authentication or passwords, which you could be doing server-side using NodeJS ...).
It depends on what data you have and what you want to achieve.
Your data has some natural "simple" uniqueness:
The hash of an integer is ... the integer, as it is unique, lucky you !
The hash of a string ... it depends on the string, if the string represents a unique identifier, you may consider it as a hash (so no hashing needed).
Anything which is indirectly pretty much a unique integer is the simplest case
This will respect: hashcode equal if objects are equal
Your data has some natural "composite" uniqueness:
For example with a person object, you may compute a hash using firstname, lastname, birthdate, ... see how Java does it: Good Hash Function for Strings, or use some other ID info that is cheap and unique enough for your usecase
You have no idea what your data will be:
Good luck ... you could serialize to string and hash it Java style, but that may be expensive if the string is large and it will not avoid collisions as well as say the hash of an integer (self).
There is no magically efficient hashing technique for unknown data, in some cases it is quite easy, in other cases you may have to think twice. So even if JavaScript/ECMAScript adds more support, there is no magic language solution for this problem.
In practice you need two things: enough uniqueness, enough speed
In addition to that it is great to have: "hashcode equal if objects are equal"
https://en.wikipedia.org/wiki/Hash_table#Collision_resolution
Relationship between hashCode and equals method in Java
I combined the answers from eyelidlessness and KimKha.
The following is an angularjs service and it supports numbers, strings, and objects.
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;
};
Example Usage:
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')
Output
432700947 true
-411117486 true
1725787021 true
-1585332251 true
1 true
-1881759168 true
Explanation
As you can see the heart of the service is the hash function created by KimKha.I have added types to the strings so that the sturucture of the object would also impact the final hash value.The keys are hashed to prevent array|object collisions.
eyelidlessness object comparision is used to prevent infinit recursion by self referencing objects.
Usage
I created this service so that I could have an error service that is accessed with objects. So that one service can register an error with a given object and another can determine if any errors were found.
ie
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"}'});
This would return:
['Invalid Json Syntax - key not double quoted']
While
ErrorSvc({id: 1, json: '{"attr": "not-valid"}'});
This would return
[]
If you truly want set behavior (I'm going by Java knowledge), then you will be hard pressed to find a solution in JavaScript. Most developers will recommend a unique key to represent each object, but this is unlike set, in that you can get two identical objects each with a unique key. The Java API does the work of checking for duplicate values by comparing hash code values, not keys, and since there is no hash code value representation of objects in JavaScript, it becomes almost impossible to do the same. Even the Prototype JS library admits this shortcoming, when it says:
"Hash can be thought of as an
associative array, binding unique keys
to values (which are not necessarily
unique)..."
http://www.prototypejs.org/api/hash
In addition to eyelidlessness's answer, here is a function that returns a reproducible, unique ID for any object:
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);
}
As you can see it uses a list for look-up which is very inefficient, however that's the best I could find for now.
If you want to use objects as keys you need to overwrite their toString Method, as some already mentioned here. The hash functions that were used are all fine, but they only work for the same objects not for equal objects.
I've written a small library that creates hashes from objects, which you can easily use for this purpose. The objects can even have a different order, the hashes will be the same. Internally you can use different types for your hash (djb2, md5, sha1, sha256, sha512, ripemd160).
Here is a small example from the documentation:
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[{}]);
The package can be used either in browser and in Node-Js.
Repository: https://bitbucket.org/tehrengruber/es-js-hash
If you want to have unique values in a lookup object you can do something like this:
Creating a lookup object
var lookup = {};
Setting up the hashcode function
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;
}
Object
var key = getHashCode({ 1: 3, 3: 7 });
// key = '1337'
lookup[key] = true;
Array
var key = getHashCode([1, 3, 3, 7]);
// key = '01132337'
lookup[key] = true;
Other types
var key = getHashCode('StackOverflow');
// key = 'StackOverflow'
lookup[key] = true;
Final result
{ 1337: true, 01132337: true, StackOverflow: true }
Do note that getHashCode doesn't return any value when the object or array is empty
getHashCode([{},{},{}]);
// '012'
getHashCode([[],[],[]]);
// '012'
This is similar to #ijmacd solution only getHashCode doesn't has the JSON dependency.
Just use hidden secret property with the defineProperty enumerable: false
It work very fast:
The first read uniqueId: 1,257,500 ops/s
All others: 309,226,485 ops/s
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
}

Categories