JavaScript returning var in for loop - javascript

I am trying to understand code which implements canvas/context objects. This code returns an object if the sprite of that object is encountered on the canvas at a specified set of coordinates provided by a mouse button down event (as far as I can tell).
Does the following code create an array of objects?
var selObj = getObjectByPixel(mx,my);
and
function getObjectByPixel(x,y) {
gctx.clearRect(0,0,MaxX,MaxY);
//alert(levelData.world['ExtraBlockTNT_1'].name);
for (var objname in levelData.world) {
var obj = levelData.world[objname];
var sprd = spriteData[obj.definition];
if(!sprd) continue;
var tr = transform(obj.x, obj.y, sprd.data.width, sprd.data.height);
gctx.save();
gctx.translate(tr.x,tr.y);
gctx.rotate(obj.angle);
gctx.fillRect(-tr.w/2, -tr.h/2, tr.w, tr.h);
gctx.restore();
//console.info(x,y);
var imageData = gctx.getImageData(x, y, 1, 1);
if (imageData.data[3] > 0) {
return obj;
}
}
return null;
}
It would seem to me that the first object in the loop will return if pixel data is encountered. If that is the case, does the loop end (which is what I assume will happen) or does it keep returning objects and store them in selObj
I'm quite confused by this code but the app runs without error so I must not be fully understanding it.
Thanks.

It does not return an array. It returns an object, see: return obj;. You can only return from a function once.
p.s. if the author of this code was to return an array he would have probably called it: getObjectsByPixel (note the s).

return always ends the execution and returns to the stack at the point the function was entered.
So that means it is only returning a single object. In order to return an array, the function would have to first create the array, and then return it after the loop has finished.

I finally worked out the dynamic of the block. The loop does only return a single obj (which is what I knew anyway). The logic is, for every object sprite on the canvas, an invisible filled rectangle is created in a overlayed canvas until the mouse click coordinates are within the bounds of one of the rectangles. Then the object that that rectangle was generated from is returned.

Related

My code is removing more than i want it to from my array

I am trying to make a coin collection system using p5.js and when i call the destroy function it deletes the object i want it to but also every object in the array after that.
destroy(){
let index = coins.findIndex(function (item){
return item == this;
});
coins.splice(index, 1);
}
You can fix this by rewriting your findIndex to be
destroy(){
const index = coins.findIndex((item) => item === this);
print(coins);
coins.splice(index, 1);
print(coins);
}
The triple equal sign is strict sameness check (see more at the mdn article), but was not the issue you were experiencing. I also refactored the function to a 1-line arrow function, which fixed the issue.
"this" is undefined in the original function, which as #Robo Robok pointed out in his comment, means that the -1 is splicing off everything else in your list. Your second coin on the left, if you stand on it, "this" being undefined makes your findIndex function return -1, which splicing -1 removes the last coing from the array (the top-center coin). The draw function calls your collect method again, which does the same steps as before, but removing the last coin is now the second coin in the array (the one you're standing on), which is why the 1st coin (bottom center) is not removed.
I'll admit I'm not certain why "this" was undefined, my best guess is it has something to do with how js objects are created. In the pre ECMA 2015 era, class objects were created in a way like this
function MyObj(x, y){
this.x = x;
this.y = y;
}
const myObj = new MyObj(10, 50);
Which leads me to believe that the callback function you supplied was using the scope of its own "this" as opposed to the coin "this"

JS (and maybe React): Is this "find" method actually returning a reference to the array value?

I'm currently reading this somewhat outdated but fine React tutorial, and I spent hours looking at this little piece of trouble, which might be more Javascript-related than React-related. At a certain point, the author starts building a small notepad app, and then there's this code:
var onChangeNote = function (id, value) {
var note = _.find(notepad.notes, function (note) {
return note.id === id;
});
if (note) {
note.content = value;
}
onChange();
};
In the app, which can be viewed in full at the forementioned article or on the respective fiddle, we have a list of notes (which by itself is an array assigned to the notes property on a notepad object defined at the top of the script), and the selected one may be changed by the user, all while using React.
What really gets me is that this is the function responsible for changing the content of the note, in the note.content = value; line, but note is a variable that got its value from _.find() (it's the lodash variant, but I already tried replacing it with the vanilla JS array.find() and nothing changed), and yet, changing it appears to be updating the actual array, I found nowhere in the code any other instance of the selected note being changed, and the onChange() function just updates the view layer (therefore it doesn't do anything to the notepad itself), so this has to be it. So is the variable note referencing the actual respective item on the notepad.notes array it got its value from even though Javascript doesn't usually do that?
Maybe I'm missing something really obvious, but I cannot put my finger on it.
Basing from the source we can check that _.find doesn't create a deep copy of the object, it returns the object from the array.
Taken from: https://github.com/lodash/lodash/blob/4.6.0-npm-packages/lodash.find/index.js
function createFind(findIndexFunc) {
return function(collection, predicate, fromIndex) {
var iterable = Object(collection);
if (!isArrayLike(collection)) {
var iteratee = baseIteratee(predicate, 3);
collection = keys(collection);
predicate = function(key) { return iteratee(iterable[key], key, iterable); };
}
var index = findIndexFunc(collection, predicate, fromIndex);
return index > -1 ? iterable[iteratee ? collection[index] : index] : undefined;
};
}
So yes, it returns the object "reference", and not a clone, so changing a property in it, changes the one in the array.
============
Here's an example regarding your question if javascript is pass by value or reference. Javascript is always pass by value except if the value passed is an object or array. Changing the value of a property to the object will also affect the original one. But changing the whole object will not affect the original one.
var arr = [{a: 1}, {a: 2}];
var x = arr.find(v => v.a === 1);
x.a = 5;
console.log(arr); // you'll see a is 5 here
x = 100; // we changed variable directly (note that x is the object that we extracted from the find function)
console.log(arr); // it's not changed, 5 is still the value
x = arr.find(v => v.a === 5); // let's get that object again
x = {a: 10}; // we replaced it with another object with same property but another value
console.log(arr); // still not changed

Javascript push() replacing element instead of appending

Here's a function I have written to add words to local storage using Javascript. In case you're wondering, this is my attempt at building a search history functionality for a simple dictionary lookup site.
function add2local(entry){
var l = localStorage.length;
if(l==0){
var lu = [entry];
localStorage.setItem("w", JSON.stringify(lu));
}
else {
var lu = JSON.parse(localStorage.getItem("w")); alert(lu);
lu.push(entry); alert(lu);
}}
My understanding is the this function would keep appending its argument to local storage every time it's called. So, the first time I call it, I send it the word pirate. This gets added to the array and subsequently to the local storage as expected. The first alert() (the alert() functions are solely being used for testing) confirms this. The second time, I call it with the word vampire. Now, going by the function's logic, vampire should be appended to the array and thus the second alert() should output "pirate, vampire." And so it does.
But the third time around, say, I call the function with the word foo. This time around, it should output "pirate, vampire, foo" but instead shows "pirate, foo". Every subsequent call simply replaces the second word with the new word instead of appending it. What might I be doing wrong here? Am I misunderstanding how push() works?
The if condition and logic is incorrect; it is irrelevant how many items localStorage has, but it is very important to re-save the modified array.
In any case, I suspect an impl. might look as so:
function add2local(entry){
// load entries - if there are none, we simulate an empty array to load
var lu = JSON.parse(localStorage.getItem("w") || "[]");
// add new entry
lu.push(entry);
// write back - so change is not lost between function calls
localStorage.setItem("w", JSON.stringify(lu));
// return all local entries saved - for use from the caller
return lu;
}
Why check the storage length? You don't care. Fetch the key and if it's null then default to empty array.
function add2local (entry) {
var key = "w"
var value = localStorage.getItem(key)
if (value) {
value = JSON.parse(value)
} else {
value = []
}
value.push(entry)
localStorage.setItem(key, JSON.stringify(value))
}

Type error undefined not a function - Fabric JS

I'm receiving this error after attempting to optimise some code I'm working on.
Initially I was using canvas.forEachObject(function(obj){}) which was working fine but I needed to streamline the process somewhat.
I now have a function that iterates through the canvas and adds all the relevant object type to an array that I will then use.
function getObjects(){
var canvasObjects = canvas.getObjects();
var theArray = new Array();
for(obj in canvasObjects){
if(canvasObjects[obj].get('type') == 'thisType'){
theArray.push(canvasObjects[obj]);
}
if(canvasObjects[obj].get('type') == 'group'){
var groupObjects = canvasObjects[obj].getObjects();
for(groupObj in groupObjects){
if(groupObjects[groupObj].get('type') == 'thisType'){
theArray.push(groupObjects[groupObj]);
}
}
}
}
return theArray;
}
I'm then calling a function in an animation loop that uses the array in order to determine if a collision has occurred.
Array created here:
var geoArray = getObjects();
function detectCollision(target) {
target.setCoords();
geoArray.forEachObject(function(obj)
//for(obj in geoArray) //1st attempt - same result
{
obj.setCoords();
if(obj!=target && target.intersectsWithObject(obj)){
//..do stuff
}
});
}
The array is built fine and contains the correct number of objects so I'm sure there is no problem there. The problem occurs when I run the collision function and the type error problem occurs. Searching indicates that I may not be returning the object type but I'm not sure if this is the case and if so I'm not sure how to fix it.
Many thanks for any help.
Edit: the exact error is:
[Error] TypeError: 'undefined' is not a function (evaluating 'geoArray.forEachObject')
Edit, the error occurs always within the collision loop and as soon as 'obj' is called.
The method you're using to iterate through the array is not correct. The forEachObject is not a method of a plain JavaScript Array. It is a method defined on fabric.Collection.
The error simply indicates that you trying to use an undefined type as a function; You can either iterate using the forEach method or using a common for loop.

Why is this not an infinite loop?

I'm reading this this example and I'm stuck at understanding one line. I need to understand everything so I can't move on.
This function is supposed to hide all the elements inside an object. It's supposed to work. But to me, that for loop looks like an infinite one. Why is it not?
getChild: function (i) {
return this.children[i];
},
hide: function () {
for (var node, i = 0; node = this.getChild(i); i++) {
node.hide();
}
this.element.hide(0);
},
From what I see, the function takes the first element of the object with getChild(0) and then calls hide again on that 0-dimension object. Then it resets the counter (i) and gets the first element of the 0-dimension object (which is the same 0-dim object) and calls the function again.
I know I'm mistaken but that's what I see. Please show me the light! Thanks
In a for loop like the one above, the first bit (var node, i = 0) is only executed once, at the beginning of the loop. The loop stops executing when the middle section (node = this.getChild(i);) returns false. getChild will return false when there isn't anything at index i. (Technically, it'll return undefined, but that equates to false in this instance).
Secondly, even though hide() is called in the for loop, i is not reset. Why? This recursive call creates a new instance of hide() separate from the original. All of the variables in this new hide() are separate from the original. (and so on, down the rabbit hole).
See http://www.tizag.com/javascriptT/javascriptfor.php for more information on for loops.
The variable i is not reset on each iteration. The only actions that are recurisvely executed are the boolean expression and i++. node.hide() is not the same as this.hide(). The latter is a different function being called. If it were the same function, then yes, there would be an infinite loop.
The "outer" hide function is being used to "hide" all the elements in this.getChild(i). node.hide() will call the hide() method on those elements so they are hidden. There is no infinite loop because node.hide(), although it has the same name as the function it's being used in, is not the same function.
The code
node.hide();
is still a member of the tree and still traversable. It is just hidden from being displayed.
The initialization part of the for loop
var node, i=0
is executed only once, before the looping begins.
The conditional
node = this.getChild(i)
evaluates to true (non-null) when there is a child node, and false (null) when it has run out of descendants, thereby breaking out of the loop.
If there is no child at i, getChild will return undefined and break out of the loop.
Consider the following text from the article:
Now create the GalleryImage class. Notice that it uses all of the exact same methods as the GalleryComposite. In other words, they implement the same interface, except that the image is a leaf so it doesn't actually do anything for the methods regarding children, as it cannot have any. Using the same interface is required for the composite to work because a composite element doesn't know whether it's adding another composite element or a leaf, so if it tries to call these methods on its children, it needs to work without any errors.
And consider the constructor for GalleryImage:
var GalleryImage = function (src, id) {
this.children = [];
this.element = $('<img />')
.attr('id', id)
.attr('src', src);
}
And how the images and composites are constructed:
var container = new GalleryComposite('', 'allgalleries');
var gallery1 = new GalleryComposite('Gallery 1', 'gallery1');
var gallery2 = new GalleryComposite('Gallery 2', 'gallery2');
var image1 = new GalleryImage('image1.jpg', 'img1');
var image2 = new GalleryImage('image2.jpg', 'img2');
var image3 = new GalleryImage('image3.jpg', 'img3');
var image4 = new GalleryImage('image4.jpg', 'img4');
gallery1.add(image1);
gallery1.add(image2);
gallery2.add(image3);
gallery2.add(image4);
container.add(gallery1);
container.add(gallery2);
Since an image cannot contain children, its this.children will remain an empty array. So, when the hide function finally gets called on an image (at one of the leaves of the composite tree), the loop will attempt to evaluate this.children[0] which will return undefined. This will cause the code node = this.getChild(i) to evaluate to a "false" value, and that particular for loop will terminate. Thus preventing an endless loop.

Categories