jquery extend vs angular extend - javascript

What is the difference between these two extend functions?
angular.extend(a,b);
$.extend(a,b);
While the jquery.extend is well documented the angular.extend lacks details and the comments there provide no answers. (https://docs.angularjs.org/api/ng/function/angular.extend).
Does angular.extend also provide deep copy?

angular.extend and jQuery.extend are very similar. They both do a shallow property copy from one or more source objects to a destination object. So for instance:
var src = {foo: "bar", baz: {}};
var dst = {};
whatever.extend(dst, src);
console.log(dst.foo); // "bar"
console.log(dst.baz === src.baz); // "true", it's a shallow copy, both
// point to same object
angular.copy provides a deep copy:
var src = {foo: "bar", baz: {}};
var dst = angular.copy(src);
console.log(dst.baz === src.baz); // "false", it's a deep copy, they point
// to different objects.
Getting back to extend: I only see one significant difference, which is that jQuery's extend allows you to specify just one object, in which case jQuery itself is the target.
Things in common:
It's a shallow copy. So if src has a property p that refers to an object, dst will get a property p that refers to the same object (not a copy of the object).
They both return the destination object.
They both support multiple source objects.
They both do the multiple source objects in order, and so the last source object will "win" in case more than one source object has the same property name.
Test page: Live Copy | Live Source
<!DOCTYPE html>
<html>
<head>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.min.js"></script>
<meta charset=utf-8 />
<title>Extend!</title>
</head>
<body>
<script>
(function() {
"use strict";
var src1, src2, dst, rv;
src1 = {
a: "I'm a in src1",
b: {name: "I'm the name property in b"},
c: "I'm c in src1"
};
src2 = {
c: "I'm c in src2"
};
// Shallow copy test
dst = {};
angular.extend(dst, src1);
display("angular shallow copy? " + (dst.b === src1.b));
dst = {};
jQuery.extend(dst, src1);
display("jQuery shallow copy? " + (dst.b === src1.b));
$("<hr>").appendTo(document.body);
// Return value test
dst = {};
rv = angular.extend(dst, src1);
display("angular returns dst? " + (rv === dst));
dst = {};
rv = jQuery.extend(dst, src1);
display("jQuery returns dst? " + (rv === dst));
$("<hr>").appendTo(document.body);
// Multiple source test
dst = {};
rv = angular.extend(dst, src1, src2);
display("angular does multiple in order? " +
(dst.c === src2.c));
dst = {};
rv = jQuery.extend(dst, src1, src2);
display("jQuery does multiple in order? " +
(dst.c === src2.c));
function display(msg) {
$("<p>").html(String(msg)).appendTo(document.body);
}
})();
</script>
</body>
</html>

There is one subtle difference between the two which was not mentioned in previous answers.
jQuery's .extend() allows you to conditionally add key,value pairs, only if the value is defined. So in jQuery, this: $.extend({}, {'a': x ? x : undefined}); will return {} in case x is undefined.
In Angular's .extend() however, this: angular.extend({}, {'a': x ? x : undefined}); will return {'a': undefined}, even if x is undefined. So the key will be there, no matter what.
This could be a good or a bad thing, depending on what you need. Anyway this is a difference in behavior between the two libraries.

The 1.0.7 angularjs build states that the extend & copy methods no longer copy over the angularjs internal $$hashKey values.
See release notes # https://github.com/angular/angular.js/blob/master/CHANGELOG.md
angular.copy/angular.extend: do not copy $$hashKey in copy/extend functions. (6d0b325f, #1875)
A quick test of the angular.copy in Chomre dev tools method shows that it does do a deep copy.
x = {p: 3, y: {x: 5}}
Object {p: 3, y: Object}
x
Object {p: 3, y: Object}
z = angular.copy(x);
Object {p: 3, y: Object}
z
Object {p: 3, y: Object}
x
Object {p: 3, y: Object}
z.y.x = 1000
1000
x
Object {p: 3, y: Object}
p: 3
y: Object
x: 5
__proto__: Object
__proto__: Object
z
Object {p: 3, y: Object}
p: 3
y: Object
x: 1000
__proto__: Object
__proto__: Object
angular.extend on the other hand does a shallow copy.

.extend() in AngularJS works similarly to jQuery's .extend()
http://jsfiddle.net/Troop4Christ/sR3Nj/
var o1 = {
a: 1,
b: 2,
c: {
d:3,
e:4
}
},
o2 = {
b: {
f:{
g:5
}
}
};
console.log(angular.extend({}, o1, o2));
console.log(o1);
console.log(o2);

Related

Problem understanding the .map method in JS [duplicate]

I am copying objA to objB
const objA = { prop: 1 },
const objB = objA;
objB.prop = 2;
console.log(objA.prop); // logs 2 instead of 1
same problem for Arrays
const arrA = [1, 2, 3],
const arrB = arrA;
arrB.push(4);
console.log(arrA.length); // `arrA` has 4 elements instead of 3.
It is clear that you have some misconceptions of what the statement var tempMyObj = myObj; does.
In JavaScript objects are passed and assigned by reference (more accurately the value of a reference), so tempMyObj and myObj are both references to the same object.
Here is a simplified illustration that may help you visualize what is happening
// [Object1]<--------- myObj
var tempMyObj = myObj;
// [Object1]<--------- myObj
// ^
// |
// ----------- tempMyObj
As you can see after the assignment, both references are pointing to the same object.
You need to create a copy if you need to modify one and not the other.
// [Object1]<--------- myObj
const tempMyObj = Object.assign({}, myObj);
// [Object1]<--------- myObj
// [Object2]<--------- tempMyObj
Old Answer:
Here are a couple of other ways of creating a copy of an object
Since you are already using jQuery:
var newObject = jQuery.extend(true, {}, myObj);
With vanilla JavaScript
function clone(obj) {
if (null == obj || "object" != typeof obj) return obj;
var copy = obj.constructor();
for (var attr in obj) {
if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
}
return copy;
}
var newObject = clone(myObj);
See here and here
deep clone object with JSON.parse() and JSON.stringify
// Deep Clone
obj = { a: 0 , b: { c: 0}};
let deepClone = JSON.parse(JSON.stringify(obj));
refrence: this article
Better reference: this article
To sum it all up, and for clarification, there's four ways of copying a JS object.
A normal copy. When you change the original object's properties, the copied object's properties will change too (and vice versa).
const a = { x: 0}
const b = a;
b.x = 1; // also updates a.x
A shallow copy. Top level properties will be unique for the original and the copied object. Nested properties will be shared across both objects though. Use the spread operator ...{} or Object.assign().
const a = { x: 0, y: { z: 0 } };
const b = {...a}; // or const b = Object.assign({}, a);
b.x = 1; // doesn't update a.x
b.y.z = 1; // also updates a.y.z
A deep copy. All properties are unique for the original and the copied object, even nested properties. For a deep copy, serialize the object to JSON and parse it back to a JS object.
const a = { x: 0, y: { z: 0 } };
const b = JSON.parse(JSON.stringify(a));
b.y.z = 1; // doesn't update a.y.z
A full deep copy. With the above technique, property values that are not valid in JSON (like functions) will be discarded. If you need a deep copy and keep nested properties that contain functions, you might want to look into a utility library like lodash.
import { cloneDeep } from "lodash";
const a = { x: 0, y: { z: (a, b) => a + b } };
const b = cloneDeep(a);
console.log(b.y.z(1, 2)); // returns 3
Using Object.create() does create a new object. The properties are shared between objects (changing one also changes the other). The difference with a normal copy, is that properties are added under the new object's prototype __proto__. When you never change the original object, this could also work as a shallow copy, but I would suggest using one of the methods above, unless you specifically need this behaviour.
Try using the create() method like as mentioned below.
var tempMyObj = Object.create(myObj);
This will solve the issue.
Try using $.extend():
If, however, you want to preserve both of the original objects, you
can do so by passing an empty object as the target:
var object = $.extend({}, object1, object2);
var tempMyObj = $.extend({}, myObj);
use three dots to spread object in the new variable
const a = {b: 1, c: 0};
let d = {...a};
As I couldn't find this code anywhere around suggested answers for shallow copy/cloning cases, I'll leave this here:
// shortcuts
const {
create,
getOwnPropertyDescriptors,
getPrototypeOf
} = Object;
// utility
const shallowClone = source => create(
getPrototypeOf(source),
getOwnPropertyDescriptors(source)
);
// ... everyday code ...
const first = {
_counts: 0,
get count() {
return ++this._counts;
}
};
first.count; // 1
const second = shallowClone(first);
// all accessors are preserved
second.count; // 2
second.count; // 3
second.count; // 4
// but `first` is still where it was
first.count; // just 2
The main difference compared to Object.assign or {...spread} operations, is that this utility will preserve all accessors, symbols, and so on, in the process, including the inheritance.
Every other solution in this space seems to miss the fact cloning, or even copying, is not just about properties values as retrieved once, but accessors and inheritance might be more than welcome in daily cases.
For everything else, use native structuredClone method or its polyfill 👋
This might be very tricky, let me try to put this in a simple way. When you "copy" one variable to another variable in javascript, you are not actually copying its value from one to another, you are assigning to the copied variable, a reference to the original object. To actually make a copy, you need to create a new object use
The tricky part is because there's a difference between assigning a new value to the copied variable and modify its value. When you assign a new value to the copy variable, you are getting rid of the reference and assigning the new value to the copy, however, if you only modify the value of the copy (without assigning a new value), you are modifying the copy and the original.
Hope the example helps!
let original = "Apple";
let copy1 = copy2 = original;
copy1 = "Banana";
copy2 = "John";
console.log("ASSIGNING a new value to a copied variable only changes the copy. The ogirinal variable doesn't change");
console.log(original); // Apple
console.log(copy1); // Banana
console.log(copy2); // John
//----------------------------
original = { "fruit" : "Apple" };
copy1 = copy2 = original;
copy1 = {"animal" : "Dog"};
copy2 = "John";
console.log("\n ASSIGNING a new value to a copied variable only changes the copy. The ogirinal variable doesn't change");
console.log(original); //{ fruit: 'Apple' }
console.log(copy1); // { animal: 'Dog' }
console.log(copy2); // John */
//----------------------------
// HERE'S THE TRICK!!!!!!!
original = { "fruit" : "Apple" };
let real_copy = {};
Object.assign(real_copy, original);
copy1 = copy2 = original;
copy1["fruit"] = "Banana"; // we're not assiging a new value to the variable, we're only MODIFYING it, so it changes the copy and the original!!!!
copy2 = "John";
console.log("\n MODIFY the variable without assigning a new value to it, also changes the original variable")
console.log(original); //{ fruit: 'Banana' } <====== Ops!!!!!!
console.log(copy1); // { fruit: 'Banana' }
console.log(copy2); // John
console.log(real_copy); // { fruit: 'Apple' } <======== real copy!
If you have the same problem with arrays then here is the solution
let sectionlist = [{"name":"xyz"},{"name":"abc"}];
let mainsectionlist = [];
for (let i = 0; i < sectionlist.length; i++) {
mainsectionlist[i] = Object.assign({}, sectionlist[i]);
}
In Javascript objects are passed as reference and they using shallow comparison so when we change any instance of the object the same changes is also referenced to the main object.
To ignore this replication we can stringify the JSON object.
example :-
let obj = {
key: "value"
}
function convertObj(obj){
let newObj = JSON.parse(obj);
console.log(newObj)
}
convertObj(JSON.stringify(obj));
The following would copy objA to objB without referencing objA
let objA = { prop: 1 },
let objB = Object.assign( {}, objA )
objB.prop = 2;
console.log( objA , objB )
You can now use structuredClone() for deep object clones :
https://developer.mozilla.org/en-US/docs/Web/API/structuredClone
const newItem = structuredClone(oldItem);
Serialize the original object into JSON and Deserialize to another object variable of same type. This will give you copy of object with all property values. And any modification to original object will not impact the copied object.
string s = Serialize(object); //Serialize to JSON
//Deserialize to original object type
tempSearchRequest = JsonConvert.DeserializeObject<OriginalObjectType>(s);

For loops modifying an object they are not supposed to modify (Google Script) [duplicate]

I am copying objA to objB
const objA = { prop: 1 },
const objB = objA;
objB.prop = 2;
console.log(objA.prop); // logs 2 instead of 1
same problem for Arrays
const arrA = [1, 2, 3],
const arrB = arrA;
arrB.push(4);
console.log(arrA.length); // `arrA` has 4 elements instead of 3.
It is clear that you have some misconceptions of what the statement var tempMyObj = myObj; does.
In JavaScript objects are passed and assigned by reference (more accurately the value of a reference), so tempMyObj and myObj are both references to the same object.
Here is a simplified illustration that may help you visualize what is happening
// [Object1]<--------- myObj
var tempMyObj = myObj;
// [Object1]<--------- myObj
// ^
// |
// ----------- tempMyObj
As you can see after the assignment, both references are pointing to the same object.
You need to create a copy if you need to modify one and not the other.
// [Object1]<--------- myObj
const tempMyObj = Object.assign({}, myObj);
// [Object1]<--------- myObj
// [Object2]<--------- tempMyObj
Old Answer:
Here are a couple of other ways of creating a copy of an object
Since you are already using jQuery:
var newObject = jQuery.extend(true, {}, myObj);
With vanilla JavaScript
function clone(obj) {
if (null == obj || "object" != typeof obj) return obj;
var copy = obj.constructor();
for (var attr in obj) {
if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
}
return copy;
}
var newObject = clone(myObj);
See here and here
deep clone object with JSON.parse() and JSON.stringify
// Deep Clone
obj = { a: 0 , b: { c: 0}};
let deepClone = JSON.parse(JSON.stringify(obj));
refrence: this article
Better reference: this article
To sum it all up, and for clarification, there's four ways of copying a JS object.
A normal copy. When you change the original object's properties, the copied object's properties will change too (and vice versa).
const a = { x: 0}
const b = a;
b.x = 1; // also updates a.x
A shallow copy. Top level properties will be unique for the original and the copied object. Nested properties will be shared across both objects though. Use the spread operator ...{} or Object.assign().
const a = { x: 0, y: { z: 0 } };
const b = {...a}; // or const b = Object.assign({}, a);
b.x = 1; // doesn't update a.x
b.y.z = 1; // also updates a.y.z
A deep copy. All properties are unique for the original and the copied object, even nested properties. For a deep copy, serialize the object to JSON and parse it back to a JS object.
const a = { x: 0, y: { z: 0 } };
const b = JSON.parse(JSON.stringify(a));
b.y.z = 1; // doesn't update a.y.z
A full deep copy. With the above technique, property values that are not valid in JSON (like functions) will be discarded. If you need a deep copy and keep nested properties that contain functions, you might want to look into a utility library like lodash.
import { cloneDeep } from "lodash";
const a = { x: 0, y: { z: (a, b) => a + b } };
const b = cloneDeep(a);
console.log(b.y.z(1, 2)); // returns 3
Using Object.create() does create a new object. The properties are shared between objects (changing one also changes the other). The difference with a normal copy, is that properties are added under the new object's prototype __proto__. When you never change the original object, this could also work as a shallow copy, but I would suggest using one of the methods above, unless you specifically need this behaviour.
Try using the create() method like as mentioned below.
var tempMyObj = Object.create(myObj);
This will solve the issue.
Try using $.extend():
If, however, you want to preserve both of the original objects, you
can do so by passing an empty object as the target:
var object = $.extend({}, object1, object2);
var tempMyObj = $.extend({}, myObj);
use three dots to spread object in the new variable
const a = {b: 1, c: 0};
let d = {...a};
As I couldn't find this code anywhere around suggested answers for shallow copy/cloning cases, I'll leave this here:
// shortcuts
const {
create,
getOwnPropertyDescriptors,
getPrototypeOf
} = Object;
// utility
const shallowClone = source => create(
getPrototypeOf(source),
getOwnPropertyDescriptors(source)
);
// ... everyday code ...
const first = {
_counts: 0,
get count() {
return ++this._counts;
}
};
first.count; // 1
const second = shallowClone(first);
// all accessors are preserved
second.count; // 2
second.count; // 3
second.count; // 4
// but `first` is still where it was
first.count; // just 2
The main difference compared to Object.assign or {...spread} operations, is that this utility will preserve all accessors, symbols, and so on, in the process, including the inheritance.
Every other solution in this space seems to miss the fact cloning, or even copying, is not just about properties values as retrieved once, but accessors and inheritance might be more than welcome in daily cases.
For everything else, use native structuredClone method or its polyfill 👋
This might be very tricky, let me try to put this in a simple way. When you "copy" one variable to another variable in javascript, you are not actually copying its value from one to another, you are assigning to the copied variable, a reference to the original object. To actually make a copy, you need to create a new object use
The tricky part is because there's a difference between assigning a new value to the copied variable and modify its value. When you assign a new value to the copy variable, you are getting rid of the reference and assigning the new value to the copy, however, if you only modify the value of the copy (without assigning a new value), you are modifying the copy and the original.
Hope the example helps!
let original = "Apple";
let copy1 = copy2 = original;
copy1 = "Banana";
copy2 = "John";
console.log("ASSIGNING a new value to a copied variable only changes the copy. The ogirinal variable doesn't change");
console.log(original); // Apple
console.log(copy1); // Banana
console.log(copy2); // John
//----------------------------
original = { "fruit" : "Apple" };
copy1 = copy2 = original;
copy1 = {"animal" : "Dog"};
copy2 = "John";
console.log("\n ASSIGNING a new value to a copied variable only changes the copy. The ogirinal variable doesn't change");
console.log(original); //{ fruit: 'Apple' }
console.log(copy1); // { animal: 'Dog' }
console.log(copy2); // John */
//----------------------------
// HERE'S THE TRICK!!!!!!!
original = { "fruit" : "Apple" };
let real_copy = {};
Object.assign(real_copy, original);
copy1 = copy2 = original;
copy1["fruit"] = "Banana"; // we're not assiging a new value to the variable, we're only MODIFYING it, so it changes the copy and the original!!!!
copy2 = "John";
console.log("\n MODIFY the variable without assigning a new value to it, also changes the original variable")
console.log(original); //{ fruit: 'Banana' } <====== Ops!!!!!!
console.log(copy1); // { fruit: 'Banana' }
console.log(copy2); // John
console.log(real_copy); // { fruit: 'Apple' } <======== real copy!
If you have the same problem with arrays then here is the solution
let sectionlist = [{"name":"xyz"},{"name":"abc"}];
let mainsectionlist = [];
for (let i = 0; i < sectionlist.length; i++) {
mainsectionlist[i] = Object.assign({}, sectionlist[i]);
}
In Javascript objects are passed as reference and they using shallow comparison so when we change any instance of the object the same changes is also referenced to the main object.
To ignore this replication we can stringify the JSON object.
example :-
let obj = {
key: "value"
}
function convertObj(obj){
let newObj = JSON.parse(obj);
console.log(newObj)
}
convertObj(JSON.stringify(obj));
The following would copy objA to objB without referencing objA
let objA = { prop: 1 },
let objB = Object.assign( {}, objA )
objB.prop = 2;
console.log( objA , objB )
You can now use structuredClone() for deep object clones :
https://developer.mozilla.org/en-US/docs/Web/API/structuredClone
const newItem = structuredClone(oldItem);
Serialize the original object into JSON and Deserialize to another object variable of same type. This will give you copy of object with all property values. And any modification to original object will not impact the copied object.
string s = Serialize(object); //Serialize to JSON
//Deserialize to original object type
tempSearchRequest = JsonConvert.DeserializeObject<OriginalObjectType>(s);

Extending object's properties without overwriting them

I'm trying to extend the keys/values in target object (with the keys/values) from source object, but without overwriting existing keys/values in the target object. Meaning:
var obj1 = {
a: 1,
b: 2
};
var obj2 = {
b: 4,
c: 3
};
extend(obj1, obj2);
console.log(obj1); // --> {a: 1, b: 2, c: 3}
Interestingly, I found Object.assign(obj1,obj2);, but it overwrites the keys/values.
I need to not overwrite them, if existent and add them if nonexistent.
Please help in plain JavaScript.
Just a simple loop. If you only want enumerable own properties, then:
Object.keys(obj2).forEach(function(key) {
if (!(key in obj1)) {
obj1[key] = obj2[key];
}
});
If you want all enumerable properties:
var key;
for (key in obj2) {
if (!(key in obj1)) {
obj1[key] = obj2[key];
}
}
The key (no pun) bit there is the in operator, which tells you whether an object has a property (of its own, or via inheritance).
There's also Object.prototype.hasOwnProperty which would tell you only if the object has its own (not inherited) property with a given name:
Object.keys(obj2).forEach(function(key) {
if (!obj1.hasOwnProperty(key)) {
obj1[key] = obj2[key];
}
});
More:
in operator
hasOwnProperty
There is no such built in functions available in JS to do that for you.
You have to write your own logic to do that,
var x = {a:10};
var y = {a:5, b: 20};
merge(x,y);
function merge(objSrc, objTarget){
return Object.keys(objTarget).reduce(function(src, prop){
if(!src.hasOwnProperty(prop)) src[prop] = objTarget[prop];
return src;
}, objSrc);
}
console.log(x); {a:10, b:20}
P.S The above code would do a merge over enumerable own properties since Object.keys() would return the same.

Shorthand for Object.create() with multiple properties

If I want to create an object in JavaScript that has a prototype link to another object, but has several of it's own properties how can I do this?
var object1 = {
a: 1,
b: 2
};
var object2 = Object.create( object1 );
object2.c = 3;
object2.d = 4;
console.log( object2 ); // my new object with object1 as it's prototype link
My challenge here is that I have to set object2's properties one at a time.
My other option is:
var object1 = {
a: 1,
b: 2
};
var object2 = {
c: 3,
d: 4
};
Object.setPrototypeOf( object2, object1 );
console.log( object2 );
My challenge above is that the performance is supposed to be terrible. Namely, setPrototypeOf is slow. https://jsperf.com/object-create-vs-object-setprototypeof
And then of course, there's the "shorthand" where you provide, writeable, enumerable and all that to Object.create(), but that's not really shorthand.
Any ideas?
You can combine Object.create with Object.assign for this:
var object2 = Object.assign(Object.create(object1), {
c: 3,
d: 4
});
As an alternative to Object.assign, remember Object.create accepts a second argument with the property descriptors you want to add to the object:
var object1 = {
a: 1,
b: 2
};
var object2 = Object.create(object1, {
c: {value: 3, enumerable: true},
d: {value: 4, enumerable: true}
});
console.log( object2 ); // my new object with object1 as it's prototype link
Note the default is non-configurable, non-writable and non-enumerable.
If that's a problem, ES2017 introduces Object.getOwnPropertyDescriptors.
var object1 = {
a: 1,
b: 2
};
var object2 = Object.create(object1, Object.getOwnPropertyDescriptors({
c: 3,
d: 4
}));
console.log( object2 ); // my new object with object1 as it's prototype link
A more elegant way of doing this would be by using spread syntax.
const obj1 = { a: 1, b: 2 }
const obj2 = { ...obj1, c: 3, d: 4 }
console.table(obj1)
console.table(obj2)
You can even add more properties to the same object in a similar way.
let obj = { a: 1, b: 2 }
obj = { ...obj, c: 3, d: 4 }
console.table(obj)
This also works for arrays.
let arr = [1, 2]
arr = [...arr, 3] // Equivalent to Array.push()
arr = [0, ...arr] // Equivalent to Array.unshift()
arr = [-1, ...arr, 4] // Equivalent to simultaneous Array.unshift() and Array.push()
console.table(arr)
Normally, when we talk about setting and swapping prototypes, we are talking about constructor functions that are instantiated into objects and not object literals themselves.
You can certainly, just manually switch the prototype yourself in this case (which is the basis for prototypical inheritance) and will cause you to inherit the right properties, but you also now have to deal with constructor issues when instances of your derived object get made.
But, this technique is fast as it only requires a new instance to be made and that reference is then set in the prototype property.
function object1(){
this.a = 1;
this.b = 2;
console.log("object1 has been invoked");
};
function object2(){
console.log("object2 has been invoked");
this.c = 3;
this.d = 4;
};
// It's very important that the prototype be set to a NEW instance
// of the super-object so that you don't wind up sharing a prototype
// with other unintended objects.
object2.prototype = new object1();
// object2.prototype.constructor was the function object2
// But now that object2 has had its prototype swapped out
// to object1, when new instances of object2 are made, the
// constructor for object1 will execute. To fix this, we
// just need to reset the constructor property of the new
// prototype that we just set. That's another reason we created
// a new instance of object1, so we could modify the constructor
// of that instance without screwing up other instances of object1
// that may be in use. object2 will use object1 as
// it's prototype, but that particular instance of object1
// will have object2 set as the constructor to use when instances
// are needed.
object2.prototype.constructor = object2;
console.log( new object2() );

Is there a hashmap library for JavaScript?

In JavaScript, all Objects act a bit like hashmaps. However, the keys to these hashmaps must be strings. If they're not, they're converted with toString(). That means:
var a = {foo: 1};
var b = {bar: 2};
var o = {};
o[a] = 100;
o[b]; // 100
JSON.stringify(o); // '{"[object Object]":100}'
That is, since the toString() of any plain Object is [object Object], they all address the same value.
I'd like to create a hashmap where Objects with the same properties and values address the same value, but objects with different properties or values address different values. That is:
var a = {foo: 1};
var b = {bar: 2, baz: 3};
var c = {baz: 3, bar: 2};
var hash = new Hash();
hash.set(a, 100);
hash.get(b); // undefined
hash.set(b, 200);
hash.get(b); // 200
hash.get(c); // 200
My first instinct was to use JSON.stringify() to turn objects into strings, but:
var hash = {};
var b = {bar: 2, baz: 3};
var c = {baz: 3, bar: 2};
hash[JSON.stringify(b)] = 100
hash[JSON.stringify(b)] // 100
hash[JSON.stringify(c)] // undefined
JSON.stringify(b) // '{"bar":2,"baz":3}'
JSON.stringify(c) // '{"baz":3,"bar":2}'
That is, JSON serialization is order-dependent.
Is there a good library or technique to implement a hashmap like this?
Update:
Equivalently, is there a good hashing function such that:
hash({foo: 1, bar: 2}) == hash({bar: 2, foo: 1})
I would recommend you the jshashtable project from Tim Down.
This is an old question, but ES6 has a feature that may be relevant: Map
Maps can have arbitrary objects as keys, and arbitrary values as values. The main distinction is that the objects used as keys stay unique, even if objects are identical.
Here's an example:
var map = new WeakMap();
var o1 = {x: 1};
var o2 = {x: 1};
map.set(o1, 'first object');
map.set(o2, 'second object');
// The keys are unique despite the objects being identical.
map.get(o1); // 'first object'
map.get(o2); // 'second object'
map.get({x: 1}); // undefined
JSON.stringifying the objects wouldn't be able to distinguish between o1 and o2.
MDN has more info. There's also a WeakMap which doesn't maintain references to the objects used as keys, so they may be garbage collected.
The Traceur compiler doesn't yet have official polyfills for Map and Weakmap, but there is an open pull request with polyfills for both. The code for those polyfills (in case anyone wants to add them individually) are here: Map and WeakMap. Assuming those polyfills work well, you can start using Map or WeakMap today. :)
Here's a quick proof-of-concept...
I've hardly tested it at all, and I'm certain that there will be corner-cases that it can't deal with.
Performance will be hideously inefficient because the __createHash function needs to recurse through the members of any objects and then sort them, in order to generate a "hash" that meets your requirements.
HashMap = function() {
this.get = function(key) {
var hash = this.__createHash(key);
return this.__map[hash];
};
this.set = function(key, value) {
var hash = this.__createHash(key);
this.__map[hash] = value;
};
this.__createHash = function(key) {
switch (typeof key) {
case 'function':
return 'function';
case 'undefined':
return 'undefined';
case 'string':
return '"' + key.replace('"', '""') + '"';
case 'object':
if (!key) {
return 'null';
}
switch (Object.prototype.toString.apply(key)) {
case '[object Array]':
var elements = [];
for (var i = 0; i < key.length; i++) {
elements.push(this.__createHash(key[i]));
}
return '[' + elements.join(',') + ']';
case '[object Date]':
return '#' + key.getUTCFullYear().toString()
+ (key.getUTCMonth() + 1).toString()
+ key.getUTCDate().toString()
+ key.getUTCHours().toString()
+ key.getUTCMinutes().toString()
+ key.getUTCSeconds().toString() + '#';
default:
var members = [];
for (var m in key) {
members.push(m + '=' + this.__createHash(key[m]));
}
members.sort();
return '{' + members.join(',') + '}';
}
default:
return key.toString();
}
};
this.__map = {};
}
You could implement your own class with a toString that ensures an ordering of keys.
Prototype has Hash quite nicely covered http://prototypejs.org/api/hash
jOrder could be modified to treat objects with the same properties (but in different order) as identical when performing an index lookup.
If you have the object {bar: 2, baz: 3, val: 200} in your list, and you've previously put a fitting index on a jOrder table like this:
var table = jOrder(data)
.index('signature', ['bar', 'baz'], {grouped: true});
Then right now table.where([{bar: 2, baz: 3}])[0].val will return 200, but table.where([{baz: 3, bar: 2}])[0].val won't.
It wouldn't take much effort though to make it not depend on the order of properties. Let me know if you're interested and I'll push the fix to GitHub as soon as I can.
This thread actually led me to find a bug in my current project. However, this is fixed now and my HashMap implementation in my project (https://github.com/Airblader/jsava) will do exactly what you described. Unlike jshashtable, there are no buckets.
the answer is to use two arrays, one for keys, one for values.
var i = keys.indexOf(key)
if (i == -1)
{keys.push(key); values.push(value)}
else
{values[i] = value; }

Categories

Resources