Calling a function within a function (invoked functions) - javascript

I'm struggling to get my head around invoked functions and their usage. I have a function called addExp - it adds experience points to the player object and determines whether or not the player has levelled up.
Here is the function:
function addExp(amt) {
return function(amt) {
player.exp += amt;
while(player.exp >= player.reqExp) {
var carry = player.exp - player.reqExp;
player.level++;
nextLevel(player.level);
player.exp = 0 + carry;
}
}
}
Here is what happens when I use it:
addExp(200); // adds no exp to the player
addExp(200)(200); // adds 200 exp to the player
addExp()(200); // adds 200 exp to the player
I have searched around online and read other questions but can't understand why it works like this - which (if any) of those function calls is done correctly? Could someone please explain, in simple enough terms, why returning a function within a function is advantageous?

Related

PhaserJs/javascript: My array is updating too many times

So I have some code making a simple inventory, like this:
else if (this.inventory.indexOf(tileType) != -1) {
this.inventory[this.inventory.indexOf(tileType) + 1] =
this.inventory[this.inventory.indexOf(tileType) + 1] - 1;
console.log(this.inventory);
return;
In phasers update function, i'm using this.input.manager.activePointer.isDown as my event trigger, and it subtracts it for as long as the mouse is down, which results in at least 6 updates cycles for the quickest clicks
I've tried methods like return; etc. , but it always goes subtracting multiple times.
So is there any javascript method i could use, or a better phaser input event?
As #Geshode mentioned in the comments, I would also setup the mouse event in the create function, and remove the other mouse code from the update function. (checkout the documentation for details)
Since this event will be executed only once per click/touch.
function create(){
...
this.input.on('pointerdown', () => {
if (this.inventory.indexOf(tileType) != -1) {
this.inventory[this.inventory.indexOf(tileType) + 1] =
this.inventory[this.inventory.indexOf(tileType) + 1] - 1;
console.log(this.inventory);
...
}
});
}
Check out these official Mouse examples
Update example:
The idea is to make all local variables needed in the event function, that are created / used in the update function with "scene properties".
Out of tileType you would have to make this.tileType, and so on. (you just have to beware of naming collisions)
How it could look like:
function create(){
...
this.input.on('pointerdown', () => {
// the tileType is now a property of the current scene
if (this.inventory.indexOf(this.tileType) != -1) {
this.inventory[this.inventory.indexOf(this.tileType) + 1] =
this.inventory[this.inventory.indexOf(this.tileType) + 1] - 1;
console.log(this.inventory);
...
}
});
}
Or better use a "gameState" object. In the create function add this.gameState = {}; and than each variable you would like to add / access, just write this.gameState.tileType = 1;. So long the context (this) is the scene, this should work, and there will be no naming problem.

Difficulty in understanding javascript coding

i'm facing difficulty in understanding what this following code does. could anyone here please help me out in understanding this piece of code?
var PnPResponsiveApp = PnPResponsiveApp || {};
PnPResponsiveApp.responsivizeSettings = function () {
// return if no longer on Settings page
if (window.location.href.indexOf('/settings.aspx') < 0) return;
// find the Settings root element, or wait if not available yet
var settingsRoot = $(".ms-siteSettings-root");
if (!settingsRoot.length) {
setTimeout(PnPResponsiveApp.responsivizeSettings, 100);
return;
}
}
var PnPResponsiveApp = PnPResponsiveApp || {};
The above line ensures that the PnPResponsiveApp variable gets its old value if it already exists, otherwise it's set to a new object.
PnPResponsiveApp.responsivizeSettings = function () {
Here, a new function is created.
// return if no longer on Settings page
if (window.location.href.indexOf('/settings.aspx') < 0) return;
If the URL of the current page isn't the settings page, then the function exits immediately.
// find the Settings root element, or wait if not available yet
var settingsRoot = $(".ms-siteSettings-root");
This gets all elements with a class of .ms-siteSettings-root.
if (!settingsRoot.length) {
setTimeout(PnPResponsiveApp.responsivizeSettings, 100);
return;
}
If any elements were found (if the length of the node list is not zero), then call the PnPResponsiveApp.responsivizeSettings function in 100 milliseconds.
Very easy code basically, I'll explain what's going on:
var PnPResponsiveApp = PnPResponsiveApp || {};
This is very common way to see if the variable is already defined and if not, avoid throwing error and equal it to an empty object, It's used in many frameworks and library, very safe way to check if the var is there already... look at here for more info: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_Operators
PnPResponsiveApp.responsivizeSettings = function () {};
This is basically a simple function but attached to the object PnPResponsiveApp - if just responsivizeSettings = function () {}; it's attached to window Object
if (window.location.href.indexOf('/settings.aspx') < 0) return;
this is Checking if the Link in the linkbar has settings.aspx - indexOf return -1 if it doesn't contain the string, so if it's not settings.aspx it returns -1 that's smaller than 0 and then the whole function return ... the second return basically return undefined
var settingsRoot = $(".ms-siteSettings-root");
This is basically look for all element with class of ms-siteSettings-root and equal them to variable settingsRoot, it could be a single DOM or multiple...
if (!settingsRoot.length) {
and this basically check if any DOM element has ms-siteSettings-root class, length return a Number, so if it's not there, it returns 0, if there is return 1,2,3 etc... and 0 is equal to False in JavaScript and bigger than 0 is equal to True, so this way we can check if it's there...
setTimeout(PnPResponsiveApp.responsivizeSettings, 100);
so if the settingsRoot is there, we execute this function block and with setTimeout we wait 100ms... setTimeout always works in this manner, setTimeout(function(), time); and the same return happens at the end...
Hope it's informative enough...

Understanding JavaScript setTimeout and setInterval

I need a bit of help understanding and learning how to control these functions to do what I intend for them to do
So basically I'm coming from a Java background and diving into JavaScript with a "Pong game" project. I have managed to get the game running with setInteval calling my main game loop every 20ms, so that's all ok. However I'm trying to implement a "countdown-to-begin-round" type of feature that basically makes a hidden div visible between rounds, sets it's innerHTML = "3" // then "2" then "1" then "GO!".
I initially attempted to do this by putting setTimeout in a 4-iteration for-loop (3,2,1,go) but always only displayed the last iteration. I tried tinkering for a bit but I keep coming back to the feeling that I'm missing a fundamental concept about how the control flows.
I'll post the relevant code from my program, and my question would be basically how is it that I'm writing my code wrong, and what do I need to know about setTimeout and setInterval to be able to fix it up to execute the way I intend it to. I'm interested in learning how to understand and master these calls, so although code examples would be awesome to help me understand and are obviously not unwelcome, but I just want to make it clear that I'm NOT looking for you to just "fix my code". Also, please no jQuery.
The whole program would be a big wall of code, so I'll try to keep it trimmed and relevant:
//this function is called from the html via onclick="initGame();"
function initGame(){
usrScore = 0;
compScore = 0;
isInPlay = true;
//in code not shown here, these objects all have tracking variables
//(xPos, yPos, upperBound, etc) to update the CSS
board = new Board("board");
ball = new Ball("ball");
lPaddle = new LPaddle("lPaddle");
rPaddle = new RPaddle("rPaddle");
renderRate = setInterval(function(){play();}, 20);
}
.
function initNewRound(){
/*
* a bunch of code to reset the pieces and their tracking variables(xPos, etc)
*/
//make my hidden div pop into visibility to display countdown (in center of board)
count = document.getElementById("countdown");
count.style.visibility = "visible";
//*****!!!! Here's my issue !!!!*****//
//somehow i ends up as -1 and that's what is displayed on screen
//nothing else gets displayed except -1
for(var i = 3; i >= 0; i--){
setInterval(function(){transition(i);}, 1000);
}
}
.
//takes initNewRound() for-loop var i and is intended to display 3, 2, 1, GO!
function transition(i){
count.innerHTML = (i === 0) ? "Go" : i;
}
.
//and lastly my main game loop "play()" just for context
function play(){
if(usrScore < 5 && compScore < 5){
isInPlay = true;
checkCollision();
moveBall();
moveRPaddle();
if(goalScored()){
isInPlay = false;
initNewRound();
}
}
}
Thanks a bunch for your advise, I'm pretty new to JavaScript so I really appreciate it.
Expanding on cookie monster's comment, when you use setInterval in a loop, you are queueing up method executions that will run after the base code flow has completed. Rather than queue up multiple setInterval executions, you can queue up a single execution and use a variable closure or global counter to track the current count. In the example below, I used a global variable:
var i = 3 // global counter;
var counterInterval = null; // this will be the id of the interval so we can stop it
function initNewRound() {
// do reset stuff
counterInterval = setInterval(function () { transition() }, 1000); // set interval returns a ID number
}
// we don't need to worry about passing i, because it is global
function transition() {
if (i > 0) {
count.innerHTML = i;
}
else if (i === 0) {
count.innerHTML = "Go!";
}
else {
i = 4; // set it to 4, so we can do i-- as one line
clearInterval(counterInterval); // this stops execution of the interval; we have to specify the id, so you don't kill the main game loop
}
i--;
}
Here is a Fiddle Demo
The problem is in this code:
for(var i = 3; i >= 0; i--){
setInterval(function(){transition(i);}, 1000);
}
When the code runs, it creates a new function 3 times, once for each loop, and then passes that function to setInterval. Each of these new functions refers to the variable i.
When the first new function runs it first looks for a local variable (in it's own scope) called i. When it does not find it, it looks in the enclosing scope, and finds i has the value -1.
In Javascript, variables are lexically scoped; an inner function may access the variables defined in the scope enclosing it. This concept is also known as "closure". This is probably the most confusing aspect of the language to learn, but is incredibly powerful once you understand it.
There is no need to resort to global variables, as you can keep i safely inside the enclosing scope:
function initNewRound(){
var i = 3;
var count = document.getElementById("countdown");
count.style.visibility = "visible";
var interval = setInterval(function(){
//this function can see variables declared by the function that created it
count.innerHTML = i || "Go"; //another good trick
i-=1;
i || clearInterval(interval); //stop the interval when i is 0
},1000);
}
Each call to this function will create a new i, count and interval.

Calling random function without duplicates

I am creating a random photo365 challenge list generator using javascript. I have a list of 365 different function which come up with a different assignment name/page link (this probably isn't the best way to do it, but it works)
It works as it's supposed to, it does call 365 functions and puts them in a list...
But what I'd like to do is prevent repeats. (Please note, the code below doesn't have all the 365 functions listed)
I have searched on stack overflow, and I have come across a variety of methods of preventing repeats. But any time I try to add the new code it, I can't get it to work.
I'm really not that skilled in javascript, so any guidance you could provide would be extremely appreciated...
Noel
//Create a new To-Do
function randomFrom(array) {return array[Math.floor(Math.random() * array.length)];}
function randomCreate() {
var func = randomFrom([createNew365ToDo,
createNew365ToDoBulb,
createNew365ToDo2,
createNew365ToDoShallow,
createNew365ToDoWide,
createNew365ToDoLenses,
createNew365ToDoMacro,
createNew365ToDoAToZ]);
(func)();
}
function createNew365ToDoList()
{
deleteAll365Rows();
for (var p = 0; p < 365; p++) {
{
randomCreate();
}
}}
I would do something like this:
//are arrays passed by reference? I don't remember, so let's just make it available to everything to demonstrate
var fnArray = [createNew365ToDo, createNew365ToDoBulb, createNew365ToDo2, createNew365ToDoShallow, createNew365ToDoWide, createNew365ToDoLenses, createNew365ToDoMacro,createNew365ToDoAToZ];
function randomFunction() {
//get a random index from the list
var index = Math.floor(Math.random() * fnArray.length);
//save that function to fn, and REMOVE it from the list
var fn = fnArray.splice(index, 1);
//return that function - now, when randomFunction() gets called again, you won't ever
//return the same function since it's no longer in the list
return fn;
}
function callRandomFunction() {
var func = randomFunction();
(func)();
}

How to Add Event Handler with Arguments to an Array of Elements in Javascript?

I have a three-step process that is entirely reliant upon JavaScript and Ajax to load data and animate the process from one step to the next. To further complicate matters, the transition (forward and backward) between steps is animated :-(. As user's progress through the process anchor's appear showing the current step and previous steps. If they click on a previous step, then it takes them back to the previous step.
Right now, the entire process (forward and backward) works correctly, if you begin at step 1, but if you jump straight to step 3 then the anchors for step 1 and step 2 also perform the same action as step 3.
This is the portion of the code that loops through all of the steps up to the current step that the user would be on and displays each anchor in turn and assigns the appropriate function to the click event:
for (var i = 0; i < profile.current + 1; i++) {
if ($('step_anchor_' + i).innerHTML.empty()) {
var action = profile.steps[i].action;
var dao_id = profile.steps[i].dao_id;
$('step_anchor_' + i).innerHTML = profile.steps[i].anchor;
$('step_anchor_' + i).observe('click', function(){
pm.loadData(action, dao_id, true);
});
Effect.Appear('step_anchor_' + i, {
duration: 1,
delay: (down_delay++)
});
}
}
I know that problem lies in the way that the action and dao_id parameters are being passed in. I've also tried passing profile.steps[i].action and profile.steps[i].dao_id but in that case both profile and i or at least i are out scope.
How do I make it so that I can assign the parameters for action and dao_id correctly for each step? (If it makes any difference we are using Prototype and Scriptaculous)
Your closure scope chain is causing your problems. By declaring the handler function inline, you've created a closure. Obviously you did this to take advantage of the loop.
However, since you have created a closure, you're playing by closure scoping rules. Those rules state that the local variables within the parent function remain active and available as long as the closure exists.
You are trying to pass and then use "action" and "dao_id" to your closure, but you are passing references here, not values. So when your closures (handlers) are called they use the value that the reference was last assigned. In your case, the Step 3 handler.
Closure scoping rules are confusing enough, but you may also be confused by the fact that "action" and "dao_id" are still alive even though the loop block has finished executing. Well, in JavaScript there is no such thing as block scope. Once you declare a variable it is available until the end of the function or until is it deleted. Whichever comes first.
All that said, you need to break the scope chain. Here are two ways to do that:
Try this:
for (var i = 0; i < profile.current + 1; i++) {
if ($('step_anchor_' + i).innerHTML.empty()) {
var action = profile.steps[i].action;
var dao_id = profile.steps[i].dao_id;
$('step_anchor_' + i).innerHTML = profile.steps[i].anchor;
$('step_anchor_' + i).observe('click', function(a, b){
return function(){pm.loadData(a, b, true)};
}(action, dao_id));
Effect.Appear('step_anchor_' + i, {
duration: 1,
delay: (down_delay++)
});
}
}
Or this:
function createHandler(action, dao_id) {
return function(){pm.loadData(action, dao_id, true);};
}
/* snip - inside some other function */
for (var i = 0; i < profile.current + 1; i++) {
if ($('step_anchor_' + i).innerHTML.empty()) {
var action = profile.steps[i].action;
var dao_id = profile.steps[i].dao_id;
$('step_anchor_' + i).innerHTML = profile.steps[i].anchor;
$('step_anchor_' + i).observe('click', createHandler(action, dao_id));
Effect.Appear('step_anchor_' + i, {
duration: 1,
delay: (down_delay++)
});
}
}
First, remember your execution scope in the click event. The this keyword in that context refers to the element being clicked on. Is there any way you can determine the dao_id from the element that is clicked on?

Categories

Resources