how to catch a reference loop in a javascript object? - javascript

EDIT: this was marked as a duplicate of a deep cloning question but my question (cf. title and last phrase) is about a way to tell if an object references itself, the deep cloning part is only there to provide context
I am trying to implement a function that will let me deep-copy a nested object without overwriting nested fields (I know lodash can solve this but I'd rather not use it of possible).
This is what I have written :
function copyObject(target, source) {
if (typeof target !== "object" || !target) {
throw 'target nust be a non-null javascript object';
}
Object.entries(source).map(([key, value]) => {
if (typeof value === "object"
&& typeof target[key] === "object"
&& target[key] !== null) {
copyObject(target[key], value);
} else {
target[key] = value;
}
})
return target;
}
the problem is this function would enter a infinite loop if its source parameter is an object that references itself like so (because it would always call itself on the c property of source) :
let src = {
a: "a",
b: "b",
}
src.c = src;
is there a way to know if a reference is part of an object ? I think in C this would be possible by looking at the memory addresses but in JS I don't know.

If I understand the question correctly, you should be good to go with the code bellow at the start of the .map func.
if( Object.is(value, source) ) {
return
}
MDN Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
Object.is() determines whether two values are the same value. Two values are the same if one of the following holds...
both the same object (meaning both values reference the same object in memory)

Related

Which function is better to cover most cases to check if a data is Object or not between 2 function below

I'm usually using this condition to check if a value of data is Object or not:
// function A
export const isObject = item => {
return item && item.constructor === Object;
};
since it is easier to understand, but my friend usually using this function to check data with the same condition as above isObject(value) {}:
// function B
export function isObject(value) {
return typeof value === "object" && !Array.isArray(value) && value !== null;
}
we got confused when there is a conflict when we want to merge to latest branch, so here is the question, Between function A & B which one is better to cover most case regarding object data?, Can you explain it too so I have a better knowledge about it.
Assuming that you want to identify plain objects that inherit from Object.prototype, and aren't subclasses of something else:
The first approach could be fooled because the .constructor property doesn't necessarily refer to the constructor function used to create the function - an object could assign something else to that property.
const isObject = item => {
return item && item.constructor === Object;
};
const a = [];
console.log(a.constructor == Array);
a.constructor = Object;
console.log(a.constructor === Object);
console.log(isObject(a));
The second approach could be fooled because there are other sorts of things than arrays that inherit from Object.prototype that aren't plain objects nor arrays - like HTMLElements and many, many other things.
function isObject(value) {
return typeof value === "object" && !Array.isArray(value) && value !== null;
}
console.log(isObject(document.body));
console.log(isObject(new Proxy({}, {})));
So I prefer Object.getPrototypeOf, with which you can be quite confident that the value's immediate internal prototype is Object.prototype, and that it's nothing else special.
const isObject = item => Object.getPrototypeOf(item) === Object.prototype;
console.log(isObject([]));
console.log(isObject({}));
console.log(isObject(document));
It could fail if Object.getPrototypeOf is overwritten, of course - but if you're at that point, pretty much nothing is trustworthy anyway.
Note that many things inherit from Object.prototype which will give true for this, such as Number.prototype - if you want to exclude those kinds, you could check if a .constructor property exists.

Most elegant way to save only primitive properties of a Javascript object

I have an object with lots of own and inherited methods. I need to make a simple snapshot of it with only primitive properties.
What's the most elegant way how to do it?
Javascript snippet
function copy(source) {
var target;
if (source) {
target = {};
for (var key in source) {
var prop = source[key],
type = typeof prop;
if (type === "string" || type === "number" || type === "boolean") {
target[key] = prop;
}
if (type == "object") {
target[key] = copy(prop);
}
}
}
return target;
}
With Underscore.js
function copy(source) {
var target;
if (source) {
target = {};
_.filter( _.keys(source), function( key ) {
var prop = source[key];
if (!_.isFunction(prop) && !_.isObject(prop)) {
target[key] = prop;
}
if (_.isObject(prop)) {
target[key] = copy(prop);
}
});
}
return target;
}
The problem here is that you are dealing with a mix of types that may or may not be what you want thanks to the loose typing that JavaScript offers. Specifically without walking any given array it is impossible to tell whether it only contains primitives and you might therefore want to keep it or whether it contains objects or functions that you want to filter out.
I realise library recommendations are the oldest hat, but I'm also going to strongly recommend that you use Underscore.js ( or a derivative such as Lodash ) which gives a whole lot of super-convenient functions that I use the whole time. In a very real sense these feel like the missing part of the core JavaScript language - in fact in some cases they are wrapping native functionality, but allowing you to avoid platform inconsistencies.
If you have Underscore included in your library then you can find the non-object property names of any given object like this:
var myObject = {
a: "hello",
b: "goodbye",
c: function() { console.log(this.b);}
d: { hello: "banana" }
};
_.filter( _.keys(myObject), function( key ) {
return ! _.isObject(myObject[key]);
}) // => ["a", "b"]
This could then be tidied into a function to clone the object, but of course it's unnecessary because Underscore offers us _.pick, which returns a copy of the object with only the whitelisted properties:
_.pick( myObject, _.filter( _.keys(myObject), function( key ) { return ! _.isObject(myObject[key]);}))
By flipping the !_.isObject you can retrieve all the properties which are objects, and perform a similar mapping on those if necessary. By wrapping both in a function you can call it recursively on the child objects you want to clone.
This answers your question, but be aware that this might not be the best way to perform undo/redo actions, which you mention in your comments is the use you plan to put this to. It may be easier to record actions and states onto a history stack recording only the actual changes any action performs, where they can be retrieved and reverted safely without needing to keep snapshots of your data.

What did Douglas Crockford mean by 'constructed in a different window or frame'?

What does Douglas Crockford mean when he wrote the is_array() test saying that it will fail to identify arrays that were constructed in a different window or frame?
var is_array = function (value) {
return value &&
typeof value === 'object' &&
value.constructor === Array;
Why does the following work across windows and frames?
var is_array = function (value) {
return value &&
typeof value === 'object' &&
typeof value.length === 'number' &&
typeof value.splice === 'function' &&
!(value.propertyIsEnumerable('length'));
};
For example, if you have an iframe, which has its own window, any Array constructed in there will fail another window's test. That's because of the last condition, which assumes the current window's Array, which is a different object.
var isArray = document.querySelector("iframe").contentWindow.Array === Array;
// false (wrong, it *is* an array)
jsFiddle.
The latter works through knowing that typeof always says an Array is an "object", as well as looking for properties that exist on Arrays.
However, it can be fooled, by constructing an object that looks like an array.
var isArray = is_array(Object.create({length: 0, splice: function() {}}));
// true (wrong, it *isn't* an array)
jsFiddle.
Another method is to invoke Object's toString() method on it, which will return the internal class.
var isArray = ({}).toString.call(value) == "[object Array]";
// true (correct, it's an array)
jsFiddle.
This works in a multi-window environment and can't be tricked (though there is more overhead examining it this way, but never likely to be a bottleneck).
I think alex's solution is better than Crockford's. This answer is to point the flaw in Crockford's solution.
See this:
I purposefully created a faulty object.
function Stupid(){}
Stupid.prototype.length = 0;
Stupid.prototype.splice = function () {}
var stupid = new Stupid();
is_array(stupid);
// true
Obviously, if you see someone setting length to the prototype, you have every right to smash their head to the wall.
But this is valid:
function Mediocre(){
Object.defineProperty(this, 'length', {
get: function () { return 42; },
enumerable: false
});
}
Mediocre.prototype.splice = function () {}
var m = new Mediocre();
is_array(m);
// true
So, you shouldn't try to outsmart the usual/common methods, because you can't guess what the other developers are going to do.

Javascript - Determine if any scoped references in object

I execute some function and get a return value. This value could be anything (string, array, object, reference, function). I then pass this result along using JSON.stringify.
Now, the functions and references don't do me much good in the scope they're being delivered to, so the whole "to string, eval" method isn't much use. I'm going to store them in a local array and just pass along an ID to reference them by later. But, I do go ahead and send string data, arrays, and objects (in the "associated array" sense of javascript objects) as those all play very nicely with JSON.stringify.
I'm already using try... JSON.stringify() catch to do this with recursive objects (which JSON.stringify errors on.) But that doesn't account for anything else mentioned above.
What is the most efficient way to check if an value contains a function?
And not
typeof foo === "function"
Because the return might be
["foo", "bar", ["foo", "bar"], function(){...something}]
I don't want to pick apart each individual piece's type either, just return on the whole whether there's ANY functions/objects that cannot be safely stringified. I could probably work out how to loop and check each individual value, but if there's a shortcut or more efficient method someone can think of, I'd like to hear it.
Thanks!
Refining welcome and appreciated!
//your favorite object length checking function could go here
$.objectLength = (function(){
//ie<9
if (typeof Object.keys === "undefined" ){
return function(o){
var count = 0, i;
for (i in o) {
if (o.hasOwnProperty(i)) {
count++;
}
}
return count;
};
//everyone else
} else {
return function(o){
return Object.keys(o).length;
}
}
})();
//comparing our two objects
$.checkMatch = function(a, b){
//if they're not the same length, we're done here. (functions exist, etc)
if (typeof a !== typeof b || typeof a === "object" && typeof b === "object" && a && b && $.objectLength(a) !== $.objectLength(b)){
return false;
//if they are the same length, they may contain deeper objects we need to check.
} else {
var key;
for (key in a){
//make sure it's not prototyped key
if (a.hasOwnProperty(key)){
//if it doesn't exist on the other object
if (!b.hasOwnProperty(key)){
return false;
//if this an object as well
} else if (typeof a[key] === "object"){
//check for a match
if (!$.checkMatch(a[key], b[key])){
return false;
}
//then check if they're not equal (simple values)
} else if (a[key] !== b[key]){
return false
}
}
}
return true;
}
};
//...stuff
//catch recursive objects in parameters
var good = true, sendObject = {"ourobject", "values"}, finalSendObject;
//try to stringify, which rejects infinitely recursive objects
try {
finalSendObject = JSON.stringify(sendObject);
} catch(e){
good = false;
}
//if that passes, try matching the original against the parsed JSON string
if (good && sendObject !== JSON.parse(finalSendObject)){
good = $.checkMatch(sendObject, JSON.parse(finalSendObject));
}
This will not work
But I will leave it up for anyone who thinks of trying it. Working solution coming shortly.
30 seconds later, I figure it out myself.
String it, parse it. If anything changed, it wont be equal to itself.
var checkParse = function(obj){
return obj === JSON.parse(JSON.strigify(obj));
}

Updating JavaScript object-attributes from another object [duplicate]

This question already has answers here:
How can I merge properties of two JavaScript objects dynamically?
(69 answers)
Closed 6 years ago.
I want to update an object that could look like this:
currentObject = {
someValue : "value",
myObject : {
attribute1 : "foo",
attribute2 : "bar"
}
};
.. with an object that contains some changes i.e.:
updateObject = {
myObject : {
attribute2 : "hello world"
}
};
At the end I would like to have currentObject updated so that:
currentObject.myObject.attribute2 == "hello world"
That should be posible for other objects as well..
As a solution I thought about iterating over the object and somehow take care of the namespace. But I wonder if there is an easy solution for that problem by using a library like jQuery or prototype.
I suggest using underscore.js (or better, lo-dash) extend:
_.extend(destination, *sources)
Copy all of the properties in the source objects over to the
destination object, and return the destination object. It's in-order,
so the last source will override properties of the same name in
previous arguments.
_.extend({name: 'moe'}, {age: 50});
=> {name: 'moe', age: 50}
function update(obj/*, …*/) {
for (var i=1; i<arguments.length; i++) {
for (var prop in arguments[i]) {
var val = arguments[i][prop];
if (typeof val == "object") // this also applies to arrays or null!
update(obj[prop], val);
else
obj[prop] = val;
}
}
return obj;
}
should do the trick: update(currentObject, updateObject). You might want to add some type checks, like Object(obj) === obj to extend only real objects with real objects, use a correct loop for arrays or hasOwnProperty tests.
Here's an Object.keys and recursive example:
// execute object update function
update(currentObject, updateObject)
// instantiate object update function
function update (targetObject, obj) {
Object.keys(obj).forEach(function (key) {
// delete property if set to undefined or null
if ( undefined === obj[key] || null === obj[key] ) {
delete targetObject[key]
}
// property value is object, so recurse
else if (
'object' === typeof obj[key]
&& !Array.isArray(obj[key])
) {
// target property not object, overwrite with empty object
if (
!('object' === typeof targetObject[key]
&& !Array.isArray(targetObject[key]))
) {
targetObject[key] = {}
}
// recurse
update(targetObject[key], obj[key])
}
// set target property to update property
else {
targetObject[key] = obj[key]
}
})
}
JSFiddle demo (open console).
A simple implementation would look like this.
function copyInto(target /*, source1, sourcen */) {
if (!target || typeof target !== "object")
target = {};
if (arguments.length < 2)
return target;
for (var len = arguments.length - 1; len > 0; len--)
cloneObject(arguments[len-1], arguments[len]);
return target;
}
function cloneObject(target, source) {
if (!source || !target || typeof source !== "object" || typeof target !== "object")
throw new TypeError("Invalid argument");
for (var p in source)
if (source.hasOwnProperty(p))
if (source[p] && typeof source[p] === "object")
if (target[p] && typeof target[p] === "object")
cloneObject(target[p], source[p]);
else
target[p] = source[p];
else
target[p] = source[p];
}
This assumes no inherited properties should be cloned. It also does no checks for things like DOM objects, or boxed primitives.
We need to iterate in reverse through the arguments so that the copy is done in a right to left matter.
Then we make a separate cloneObject function to handle the recursive copying of nested objects in a manner that doesn't interfere with the right to left copying of the original object arguments.
It also ensures that the initial target is a plain object.
The cloneObject function will throw an error if a non-object was passed to it.

Categories