JavaScript: Testing primitives for equality - javascript

Let's say I have two objects that only have primitives as properties for members (e.g. the object has no functions or object members):
var foo = {
start: 9,
end: 11
};
var bar = {
start: 9,
end: 11
};
Given two objects like this, I want to know if all their members have the same values.
Right now I'm simpling doing:
if (foo.start === bar.start && foo.end == bar.end) {
// same member values
}
But I'm going to have to work with objects that may have dozens of these primitive members.
Is there anything built into JavaScript to easily allow me to compare them? What's the easiest way to compare all their values?

If both objects are Objects (e.g., created via literal notation [{}] or new Object, not by [say] new Date), you can do it like this:
function primativelyEqual(a, b) {
var name;
for (name in a) {
if (!b.hasOwnProperty(name) || b[name] !== a[name]) {
// `b` doesn't have it or it's not the same
return false;
}
}
for (name in b) {
if (!a.hasOwnProperty(name)) {
// `a` doesn't have it
return false;
}
}
// All properties in both objects are present in the other,
// and have the same value down to the type
return true;
}
for..in iterates over the names of the properties of an object. hasOwnProperty tells you whether the instance itself (as opposed to a member of its prototype chain) has that property. !== checks for any inequality between two values without doing any type coercion. By looping through the names of the properties of both objects, you know that they have the same number of entries.
You can shortcut this a bit if the implementation has the new Object.keys feature from ECMAScript5:
function primativelyEqual(a, b) {
var name, checkedKeys;
checkedKeys = typeof Object.keys === "function";
if (checkedKeys && Object.keys(a).length !== Object.keys(b).length) {
// They don't have the same number of properties
return false;
}
for (name in a) {
if (!b.hasOwnProperty(name) || b[name] !== a[name]) {
// `b` doesn't have it or it's not the same
return false;
}
}
if (!checkedKeys) {
// Couldn't check for equal numbers of keys before
for (name in b) {
if (!a.hasOwnProperty(name)) {
// `a` doesn't have it
return false;
}
}
}
// All properties in both objects are present in the other,
// and have the same value down to the type
return true;
}
Live example
But both versions of the above assume that the objects don't inherit any enumerable properties from their prototypes (hence my opening statement about their being Objects). (I'm also assuming no one's added anything to Object.prototype, which is an insane thing people learned very quickly not to do.)
Definitely possible to refine that more to generalize it, and even make it descend into object properties by the same definition, but within the bounds of what you described (and within the bounds of most reasonable deployments), that should be fine.

You can use a for in loop to loop through every property in an object.
For example:
function areEqual(a, b) {
for (var prop in a)
if (a.hasOwnProperty(prop) && a[prop] !== b[prop])
return false;
return true;
}
Any properties in b but not a will be ignored.

Y'know, something's just occurred to me. Basic object literals are called JSON. The easiest way to compare those two objects is
function equalObjects(obj1, obj2) {
return JSON.stringify(obj1) === JSON.stringify(obj2);
}
If you need to use this in a browser that doesn't have native JSON support, you can use the open source JSON scripts

Related

Deep comparing objects inside an array and finding duplicates [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
How do you determine equality for two JavaScript objects?
Object comparison in JavaScript
If I have two arrays or objects and want to compare them, such as
object1 = [
{ shoes:
[ 'loafer', 'penny' ]
},
{ beers:
[ 'budweiser', 'busch' ]
}
]
object2 = [
{ shoes:
[ 'loafer', 'penny' ]
},
{ beers:
[ 'budweiser', 'busch' ]
}
]
object1 == object2 // false
this can be annoying if you're getting a response from a server and trying to see if it's changed
Update:
In response to the comments and worries surrounding the original suggestion (comparing 2 JSON strings), you could use this function:
function compareObjects(o, p)
{
var i,
keysO = Object.keys(o).sort(),
keysP = Object.keys(p).sort();
if (keysO.length !== keysP.length)
return false;//not the same nr of keys
if (keysO.join('') !== keysP.join(''))
return false;//different keys
for (i=0;i<keysO.length;++i)
{
if (o[keysO[i]] instanceof Array)
{
if (!(p[keysO[i]] instanceof Array))
return false;
//if (compareObjects(o[keysO[i]], p[keysO[i]] === false) return false
//would work, too, and perhaps is a better fit, still, this is easy, too
if (p[keysO[i]].sort().join('') !== o[keysO[i]].sort().join(''))
return false;
}
else if (o[keysO[i]] instanceof Date)
{
if (!(p[keysO[i]] instanceof Date))
return false;
if ((''+o[keysO[i]]) !== (''+p[keysO[i]]))
return false;
}
else if (o[keysO[i]] instanceof Function)
{
if (!(p[keysO[i]] instanceof Function))
return false;
//ignore functions, or check them regardless?
}
else if (o[keysO[i]] instanceof Object)
{
if (!(p[keysO[i]] instanceof Object))
return false;
if (o[keysO[i]] === o)
{//self reference?
if (p[keysO[i]] !== p)
return false;
}
else if (compareObjects(o[keysO[i]], p[keysO[i]]) === false)
return false;//WARNING: does not deal with circular refs other than ^^
}
if (o[keysO[i]] !== p[keysO[i]])//change !== to != for loose comparison
return false;//not the same value
}
return true;
}
But in many cases, it needn't be that difficult IMO:
JSON.stringify(object1) === JSON.stringify(object2);
If the stringified objects are the same, their values are alike.
For completeness' sake: JSON simply ignores functions (well, removes them all together). It's meant to represent Data, not functionality.
Attempting to compare 2 objects that contain only functions will result in true:
JSON.stringify({foo: function(){return 1;}}) === JSON.stringify({foo: function(){ return -1;}});
//evaulutes to:
'{}' === '{}'
//is true, of course
For deep-comparison of objects/functions, you'll have to turn to libs or write your own function, and overcome the fact that JS objects are all references, so when comparing o1 === ob2 it'll only return true if both variables point to the same object...
As #a-j pointed out in the comment:
JSON.stringify({a: 1, b: 2}) === JSON.stringify({b: 2, a: 1});
is false, as both stringify calls yield "{"a":1,"b":2}" and "{"b":2,"a":1}" respectively. As to why this is, you need to understand the internals of chrome's V8 engine. I'm not an expert, and without going into too much detail, here's what it boils down to:
Each object that is created, and each time it is modified, V8 creates a new hidden C++ class (sort of). If object X has a property a, and another object has the same property, both these JS objects will reference a hidden class that inherits from a shared hidden class that defines this property a. If two objects all share the same basic properties, then they will all reference the same hidden classes, and JSON.stringify will work exactly the same on both objects. That's a given (More details on V8's internals here, if you're interested).
However, in the example pointed out by a-j, both objects are stringified differently. How come? Well, put simply, these objects never exist at the same time:
JSON.stringify({a: 1, b: 2})
This is a function call, an expression that needs to be resolved to the resulting value before it can be compared to the right-hand operand. The second object literal isn't on the table yet.
The object is stringified, and the exoression is resolved to a string constant. The object literal isn't being referenced anywhere and is flagged for garbage collection.
After this, the right hand operand (the JSON.stringify({b: 2, a: 1}) expression) gets the same treatment.
All fine and dandy, but what also needs to be taken into consideration is that JS engines now are far more sophisticated than they used to be. Again, I'm no V8 expert, but I think its plausible that a-j's snippet is being heavily optimized, in that the code is optimized to:
"{"b":2,"a":1}" === "{"a":1,"b":2}"
Essentially omitting the JSON.stringify calls all together, and just adding quotes in the right places. That is, after all, a lot more efficient.
As an underscore mixin:
in coffee-script:
_.mixin deepEquals: (ar1, ar2) ->
# typeofs should match
return false unless (_.isArray(ar1) and _.isArray(ar2)) or (_.isObject(ar1) and _.isObject(ar2))
#lengths should match
return false if ar1.length != ar2.length
still_matches = true
_fail = -> still_matches = false
_.each ar1, (prop1, n) =>
prop2 = ar2[n]
return if prop1 == prop2
_fail() unless _.deepEquals prop1, prop2
return still_matches
And in javascript:
_.mixin({
deepEquals: function(ar1, ar2) {
var still_matches, _fail,
_this = this;
if (!((_.isArray(ar1) && _.isArray(ar2)) || (_.isObject(ar1) && _.isObject(ar2)))) {
return false;
}
if (ar1.length !== ar2.length) {
return false;
}
still_matches = true;
_fail = function() {
still_matches = false;
};
_.each(ar1, function(prop1, n) {
var prop2;
prop2 = ar2[n];
if (prop1 !== prop2 && !_.deepEquals(prop1, prop2)) {
_fail();
}
});
return still_matches;
}
});

Ways to determine if something is a plain object in JavaScript

I've recently stumbled on this function which determines if something is a plain object is JavaScript:
function isPlainObject (value){
if (typeof value !== 'object' || value === null) return false;
let proto = value;
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto);
}
return Object.getPrototypeOf(value) === proto;
};
Source: https://github.com/redux-utilities/redux-actions/blob/master/src/utils/isPlainObject.js
I would like to know:
If the following approach will do exactly the same stuff?
If so, can it be considered more effective?
function isPlainObj(value){
if (typeof value !== 'object' || value === null) return false;
let obj = {};
return Object.getPrototypeOf(value) === Object.getPrototypeOf(obj)
}
Checking if a value is a plain object:
/*
isPlainObject({}) // true
isPlainObject() // false
isPlainObject(null) // false
isPlainObject(true) // false
isPlainObject('1') // false
isPlainObject([]) // false
isPlainObject(new Function()) // false
isPlainObject(new Date()) // false
*/
const isPlainObject = value => value?.constructor === Object;
Excludes nulls, scalars, arrays, functions and any extended type other than Object.
No. The former walks through the whole prototype chain but returns true only if such chain is composed by 1 prototype (so, your first example is kinda pointless)
Yes and No. Yes, it's more effective, no need to loop everything just to check the prototype is the Object.prototype one. No, it performs a needless operation.
This is how I'd do it:
const isPlainObj = value => !!value &&
Object.getPrototypeOf(value) === Object.prototype;
No need to go too fancy, if all you want to know is that value prototype is Object.prototype 👋
P.S. There is one thing your initial example does that other examples, including mine, don't: it works with foreign objects, which are objects coming from different realms (i.e. iframes). I don't think this use case exists in 2021, but if your app/site passes objects between different windows/frames then the first function would return true for those objects too, while my suggestion, or yours, won't. Yet, there's no need to loop through the whole chain, you can simply do this instead:
function isPlainObj(value) {
// it's truthy
return !!value &&
// it has a prototype that's also truthy
!!(value = Object.getPrototypeOf(value)) &&
// which has `null` as parent prototype
!Object.getPrototypeOf(value);
}
This grabs the proto once or maximum twice ensuring its chain ends up with null, which is usually the common literal case.
Yet, I think foreign objects are non existent these days, so I'd stick with my suggested version.
ToolJS has a method under it's "Obj" module that checks if an object is infact a plain object literal.
Ref: ToolJS Object Module
Get the code on NPM or through its CDN and use as shown below
// export the methods in the "Obj" module
var $ = ToolJS.export("Obj");
var myObj = {name: "John Doe"};
var myArr = [1,2,3]; // note that arrays are of type object but are obviously not plain objects
var myEl = document.getElementById("elem"); // elements are also objects
$.isObj(myObj); // => true
$.isObj(myArr); // => false
$.isObj(myEl); // => false
You can check out it's full documentation here
Under the hood, the method checks the item type is not null or undefined but an object, then checks its constructor to see if its even an object, after which it makes sure that its not an array and finally converts it to string to see if its a plain object.

Comparing 2 JSON objects structure in JavaScript

I am using angular-translate for a big application. Having several people committing code + translations, many times the translation objects are not in sync.
I am building a Grunt plugin to look at both files' structure and compare it (just the keys and overall structure, not values).
The main goals are:
Look into each file, and check if the structure of the whole object
(or file, in this case) is the exact same as the translated ones;
On error, return the key that doesn't match.
It turns out it was a bit more complicated than I anticipated. So i figured I could do something like:
Sort the object;
Check the type of data the value contains (since they are translations, it will only have strings, or objects for the nestings) and store it in another object, making the key equal to the original key and the value would be a string 'String', or an object in case it's an object. That object contains the children elements;
Recursively repeat steps 1-2 until the whole object is mapped and sorted;
Do the same for all the files
Stringify and compare everything.
A tiny example would be the following object:
{
key1: 'cool',
key2: 'cooler',
keyWhatever: {
anotherObject: {
key1: 'better',
keyX: 'awesome'
},
aObject: 'actually, it\'s a string'
},
aKey: 'more awesomeness'
}
would map to:
{
aKey: 'String',
key1: 'String',
key2: 'String',
keyWhatever: {
aObject: 'String',
anotherObject: {
key1: 'String',
keyX: 'String'
}
}
}
After this, I would stringify all the objects and proceed with a strict comparison.
My question is, is there a better way to perform this? Both in terms of simplicity and performance, since there are many translation files and they are fairly big.
I tried to look for libraries that would already do this, but I couldn't find any.
Thank you
EDIT: Thank you Jared for pointing out objects can't be sorted. I am ashamed for saying something like that :D Another solution could be iterating each of the properties on the main translation file, and in case they are strings, compare the key with the other files. In case they are objects, "enter" them, and do the same. Maybe it is even simpler than my first guess. What should be done?
Lets say you have two JSON objects, jsonA and jsonB.
function compareValues(a, b) {
//if a and b aren't the same type, they can't be equal
if (typeof a !== typeof b) {
return false;
}
// Need the truthy guard because
// typeof null === 'object'
if (a && typeof a === 'object') {
var keysA = Object.keys(a).sort(),
keysB = Object.keys(b).sort();
//if a and b are objects with different no of keys, unequal
if (keysA.length !== keysB.length) {
return false;
}
//if keys aren't all the same, unequal
if (!keysA.every(function(k, i) { return k === keysB[i];})) {
return false;
}
//recurse on the values for each key
return keysA.every(function(key) {
//if we made it here, they have identical keys
return compareValues(a[key], b[key]);
});
//for primitives just use a straight up check
} else {
return a === b;
}
}
//true if their structure, values, and keys are identical
var passed = compareValues(jsonA, jsonB);
Note that this can overflow the stack for deeply nested JSON objects. Note also that this will work for JSON but not necessarily regular JS objects as special handling is needed for Date Objects, Regexes, etc.
Actually you do need to sort the keys, as they are not required to be spit out in any particular order. Write a function,
function getComparableForObject(obj) {
var keys = Object.keys(obj);
keys.sort(a, b => a > b ? 1 : -1);
var comparable = keys.map(
key => key + ":" + getValueRepresentation(obj[key])
).join(",");
return "{" + comparable + "}";
}
Where getValueRepresentation is a function that either returns "String" or calls getComparableForObject. If you are worried about circular references, add a Symbol to the outer scope, repr, assign obj[repr] = comparable in the function above, and in getValueRepresentation check every object for a defined obj[repr] and return it instead of processing it recursively.
Sorting an array of the keys from the object works. However, sorting has an average time complexity of O(nâ‹…log(n)). We can do better. A fast general algorithm for ensuring two sets A and B are equivalent is as follows:
for item in B
if item in A
remove item from A
else
sets are not equivalent
sets are equivalent iff A is empty
To address #Katana31, we can detect circular references as we go by maintaining a set of visited objects and ensuring that all descendents of that object are not already in the list:
# poorly written pseudo-code
fn detectCycles(A, found = {})
if A in found
there is a cycle
else
found = clone(found)
add A to found
for child in A
detectCycles(child, found)
Here's a complete implementation (you can find a simplified version that assumes JSON/non-circular input here):
var hasOwn = Object.prototype.hasOwnProperty;
var indexOf = Array.prototype.indexOf;
function isObjectEmpty(obj) {
for (var key in obj) {
return false;
}
return true;
}
function copyKeys(obj) {
var newObj = {};
for (var key in obj) {
newObj[key] = undefined;
}
return newObj;
}
// compares the structure of arbitrary values
function compareObjectStructure(a, b) {
return function innerCompare(a, b, pathA, pathB) {
if (typeof a !== typeof b) {
return false;
}
if (typeof a === 'object') {
// both or neither, but not mismatched
if (Array.isArray(a) !== Array.isArray(b)) {
return false;
}
if (indexOf.call(pathA, a) !== -1 || indexOf.call(pathB, b) !== -1) {
return false;
}
pathA = pathA.slice();
pathA.push(a);
pathB = pathB.slice();
pathB.push(b);
if (Array.isArray(a)) {
// can't compare structure in array if we don't have items in both
if (!a.length || !b.length) {
return true;
}
for (var i = 1; i < a.length; i++) {
if (!innerCompare(a[0], a[i], pathA, pathA)) {
return false;
}
}
for (var i = 0; i < b.length; i++) {
if (!innerCompare(a[0], b[i], pathA, pathB)) {
return false;
}
}
return true;
}
var map = copyKeys(a), keys = Object.keys(b);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (!hasOwn.call(map, key) || !innerCompare(a[key], b[key], pathA,
pathB)) {
return false;
}
delete map[key];
}
// we should've found all the keys in the map
return isObjectEmpty(map);
}
return true;
}(a, b, [], []);
}
Note that this implementation directly compares two objects for structural equivalency, but doesn't reduce the objects to a directly comparable value (like a string). I haven't done any performance testing, but I suspect that it won't add significant value, though it will remove the need to repeatedly ensure objects are non-cyclic. For that reason, you could easily split compareObjectStructure into two functions - one to compare the structure and one to check for cycles.

Check to see if all top-level objects have identical properties

Here is some JSON:
{
"environments":{
"production":{
"zmq_config":{
"host":"*",
"port":"7676"
},
"redis_server_config":{
"host":"localhost",
"port":"26379"
}
},
"dev_remote":{
"zmq_config":{
"host":"*",
"port":"5555"
},
"redis_server_config":{
"host":"localhost",
"port":"16379"
}
},
"dev_local":{
"zmq_config":{
"host":"*",
"port":"5555"
},
"redis_server_config":{
"host":"localhost",
"port":"6379"
}
}
}
}
I want to create a test in my test suite that ensures all of the properties have the same properties of their complements.
For example, for each property of "environments", I want to check that they have the same properties; in this case they do - they all have 2 properties "zmq_config" and "redis_server_config". Now I want to do at least one more level of checking. For properties "zmq_config" and "redis_server_config", I want to check that they in turn have the same properties "host" and "port".
You get the idea.
Is there a library that can do this? Is there some sort of JavaScript identity operator that check for this, just looking at the top level objects?
Now the easiest way I can think of doing this is simply to iterate through and look at each property with the same name (making the assumption that properties with the same name are in the same place in the object hierarchy), and then simply seeing if they have the same subproperties.
Is Underscore.js the best option? It seems Underscore has this functionality which might work:
_.isEqual(obj1, obj2);
from my research it looks like this is the best candidate:
_.isMatch(obj1,obj2);
For each object to test, you can use Object.keys function to extract the keys of the object and then compare them, because you only want to know if properties are equals, the value not matters.
Then, when you extract the keys of each object, you can compare using _.isEqual function by provided by lodash instead of underscore (usually lodash has better performance).
To automate as possible, you should create a recursive function to extract the keys and compare them.
Hacked this real quick but it should do you justice. It returns true if all nested object keys match. At each level it checks if the array of keys matches the other object's array of keys and it does that recursively.
function keysMatch(data1, data2) {
var result = null;
function check(d1, d2) {
if (result === false) {
return false;
}
if (_.isObject(d1) && _.isObject(d2)) {
if (allArraysAlike([_.keys(d1), _.keys(d2)])) {
result = true;
_.forOwn(d1, function (val, key) {
check(d1[key], d2[key]);
});
} else {
result = false;
}
}
return result;
}
return check(data1, data2);
}
function allArraysAlike(arrays) {
return _.all(arrays, function (array) {
return array.length == arrays[0].length && _.difference(array, arrays[0]).length == 0;
});
}
console.log(keysMatch(json1, json2));
http://jsfiddle.net/baafbjo8/2/
If you want a simple true/false answer, then a simple function can be created from basic javascript.
The function below uses ES5 features, but wouldn't be much more code using plain loops (and run a bit fast to boot, not that it's slow).
/**
* #param {Object} obj - Object to check properties of
* #param {Array} props - Array of properties to check
* #returns {boolean}
**/
function checkProps(obj, props) {
// List of members of obj
var memberNames = Object.keys(obj);
// Use keys of first object as base set
var baseKeys = Object.keys(obj[memberNames[0]]);
// Check every object in obj has base set of properties
// And each sub-object has props properties
return memberNames.every(function (memberName) {
// Get member
var member = obj[memberName];
// Get keys of this member
var memberKeys = Object.keys(member);
// First check that member has same keys as base, then that each sub-member
// has required properties
return memberKeys.length == baseKeys.length &&
baseKeys.every(function(key) {
return member.hasOwnProperty(key) &&
// Check sub-member properties
props.every(function(prop) {
return member[key].hasOwnProperty(prop);
});
});
});
}
console.log(checkProps(env,['host','port']));
For EcmaScript ed 4 compatability, requires polyfills for Array.prototype.every and Object.keys.

Creating new variable from another

What would be the best way to create a new array or object from another. Since doing
var oldvar = {x:1,y:2} //or [x,y]
var newvar = oldvar
will link them, what would be the bets best way to clone or cope the new variable?
Numbers in JavaScript
Numbers in JavaScript are what the spec calls 'Primitive Value Type'
From the specification about Numbers:
Number value # Ⓣ
primitive value corresponding to a double-precision 64-bit binary format IEEE 754 value.
NOTE A Number value is a member of the Number type and is a direct representation of a number.
So in your case newvar will be a copy of oldvar and not a reference.
In JavaScript, Number, Boolean, undefined, null or String are value types.
When passing any of these 5 around, you are in fact passing values and not references, no need to clone those.
When you pass anything else around (Objects) need to use cloning since they are reference types.
When cloning objects in JavaScript there are two approaches.
Shallow Cloning
This means you clone 1 level deep. Assuming all our properties in the object are enumerable (This is usually the case if you haven't used property descriptors) you can use something like:
var a = {a:3,b:5};
var copy = {};
for(var prop in a){
copy[prop] = a[prop];
}
However, often we want to copy the object's own properties and not everything it might inherit from its prototype, so we can do:
var copy = {};
for(var prop in a){
if(a.hasOwnProperty(prop)){//check that the cloned property belongs to _a_ itself
copy[prop] = a[prop];
}
}
Note these two only shallow copy properties off a, they do not deal with setting the prototype, and clone all the properties by reference (Except properties who are primitive value types themselves :) ).
Deep copying
Deep copying means creating a clone of the object that is several levels deep. This calls to recursion since deep copying is defined as such (in psuedocode)
CopyObject(object)
If object is a primitive value type
return object
clone := Empty Object
For every member of object
Add CopyObject(member) as a property to clone
Return clone
We are applying the algorithm recursively on object properties of the clone.
Here is a sample implementation that I documented for you. It assumes ES5 (Chrome) but you can easily adapt it to other/older browsers. It does more stuff like treat Date and Regex like special cases. It also keeps a dictionary of properties it already handled so it will be able to handle circular references in an object. It is intended for learning and not for production use :) If you have any questions about it feel free.
var clone = function (a) {
var passedRefs = []; // Keep track of references you passed to avoid cycles
var passedRefCreated = [];
function clone2(a1) { // Inner function to handle the actual cloning
var obj;
if (typeof a1 !== "object" || a1 === null) { // Handle value type
return a1;
}
var locInpPassed = passedRefs.indexOf(a1); // Detect circular reference
if (locInpPassed !== -1) {
return passedRefCreated[locInpPassed];
}
passedRefs.push(a1); // Add the object to the references to avoid circular references later
if (a1 instanceof Date) { // Handle date and RegExp for special cases
obj = new Date(a1.getTime());
} else if (a1 instanceof RegExp) {
obj = new RegExp(a1);
}else if (Array.isArray(a1)){// handle arrays in order for Array.isArray to work. Thanks FizzyTea for catching this.
obj = [];
} else { // Create a new object with the prototype of the one we're cloning to support prototypical inheritance. Prototypes are _shared_
obj = Object.create(Object.getPrototypeOf(a1));
}
passedRefCreated[passedRefs.indexOf(a1)] = obj; // Add to the references created dict
Object.getOwnPropertyNames(a1).forEach(function (prop) { // Go through all the property, even the ones that are not enumerable
obj[prop] = clone2(a1[prop]); // Call the algorithm recursively, just like in the pseudo code above
});
return obj;
}
return clone2(a); // Call the inner function that has access to the dictionary
}
(For example, you can use a for... in loop to iterate through the properties).
I wrote 2 relation functions for deep copying arrays and objects in javascript:
function clone_object(o) {
var r = {};
for (var p in o) {
if (o[p].constructor == Array) {
r[p] = clone_array(o[p]);
} else if (o[p].constructor == Object) {
r[p] = arguments.callee(o[p]);
} else {
r[p] = o[p];
}
}
return r;
}
function clone_array(o) {
var r = [];
for (var p = 0, l = o.length; p < l; p++) {
if (o[p].constructor == Array) {
r[p] = arguments.callee(o[p]);
} else if (o[p].constructor == Object) {
r[p] = clone_object(o[p]);
} else {
r[p] = o[p];
}
}
return r;
}
Example:
var o = { name: 'Prototype', version: 1.5, authors: ['sam', 'contributors'] };
var o2 = clone_object(o);
o2.authors.pop();
alert(o.authors);
// -> ['sam', 'contributors']
alert(o2.authors);
// -> ['sam']

Categories