pass a function with its values "locked" - javascript

Say I have a queue class that's executing a series of functions I've already declared:
class DrawQueue{
constructor(interval){
this.sequence = [];
this.interval=interval?interval:50;
}
addFunction=(fn)=>{
this.sequence.push(fn);
//throw exception here if not a function
};
execFunctions = ()=>{
let intvl = setInterval(
()=>{
const fn = this.sequence.shift();
//clear interval & return here if not a function
fn.call();
},
this.interval
)
}
}
Now I want to pass it a series of functions that have some values calculated inside them:
//I have a single count variable here but the code I'm running is being generated by a user who might have any number of variables that are being updated
let count = 0;
let counterDiv = document.querySelector('#counter')
let dq = new DrawQueue(1000);
function startCount(){ //call when window's loaded
let countFn=(()=>
{
let innerFn= function(){
let str = (function(){
return count.toString()
})();
counterDiv.innerHTML=str;
}
//imagine that any number of count variables might be being updated somewhere in the function
count++;
dq.addFunction(innerFn);
})
while(count<10){
countFn();
}
dq.execFunctions();
}
Right now this immediately sets the counter div to 10, and then keeps setting it to 10 ten more times. But I want to assign the str variable's value before I pass the functions. So the first function I pass sets the counter to 1, then 2 and so on.
I was trying to set the let str= function(... using an iife, but that didn't work.
One solution that I know would work is to make the whole function a string and then run it with eval but I really do not want to use eval unless I absolutely have to.
Is there any other way to pass these functions with certain variables already "locked in", meaning they're assigned before the function is placed in the queue?
UPDATE: To clarify, this is just a simplified version of a more complex example. In the actual example, the code is dynamically generated by another user, so in addition to 'count' any number of other values might need to be evaluated. So passing the count variable, as several good answers have suggested, is not going to work.
FURTHER CLARIFICATION: What I'm saying above is that because the user could be generating any number of variables that will be updated as the code runs, I can't pass those variables as arguments. (imagine there might be a count2, count3...countn and I don't know how many or where they'll be used or updated in the code.
FURTHER UPDATE: so a commenter wants to see the code in which this applies so here goes. It is an application using Blockly and P5 play, where users will be making code with blocks to move a sprite. So the code for the blocks might be something like this (yes this code is really ugly because it's just a test, but you asked to see it):
function moveForward(sprite){
let dir = ship.rotation* (Math.PI / 180);
let deltaX = Math.cos(dir)*5;
let deltaY = Math.sin(dir)*5;
let newX = ship.position.x + deltaX;
let newY = ship.position.y + deltaY;
ship.position.x=newX;
ship.position.y=newY;
redraw();
}
function moveBackward(sprite){
let dir = ship.rotation* (Math.PI / 180);
let deltaX = Math.cos(dir)*5;
let deltaY = Math.sin(dir)*5;
let newX = ship.position.x - deltaX;
let newY = ship.position.y - deltaY;
ship.position.x=newX;
ship.position.y=newY;
redraw();
}
function turnLeft(sprite){
let newDir=ship.rotation-90;
ship.rotation=newDir;
redraw();
}
function turnRight(sprite){
let newDir=ship.rotation+90;
ship.rotation=newDir;
redraw();
}
There will be any number of other sprites, each with 20 or so properties that could be updated.
Now if I just put all these functions in a row, the sprite will just immediately jump to where the code would put it. Because, you know, normally we want computers to do things as fast as they can.
But since this is made for teaching, I want the user to see the canvas updating step by step, with a delay between each redraw. That means every sprite will have its x and y coordinates, along with color and rotation and a bunch of other things, change slowly.
So the purpose of the DrawQueue to execute the drawing update steps slowly with a setInterval and update the canvas at any interval I want. I can't just run every single command with a setInterval because there could be logic or loops in there. The only thing I want to go in the interval is the updates to the canvas, anything else can happen as fast as it wants.
So imagine the four functions I provided above, along with any number of other functions and modifications to the properties of any number of other sprites or properties of the canvas.

The problem you have is the value is not stored at the time you make the function. It is just a reference to a variable that you are updating. So when it calls, it is reading that variable.
You would need to pass it into the method so you can store the state of the variable at that moment in time.
let count = 0;
let counterDiv = document.querySelector('#counter')
let dq = new DrawQueue(1000);
function startCount(){ //call when window's loaded
let countFn=((count)=> . // <-- reference it here
{
let innerFn= function(){
let str = (function(){
return count.toString()
})();
counterDiv.innerHTML=str;
}
dq.addFunction(innerFn);
})
while(count<10){
countFn(count++); // <-- update it here
}
dq.execFunctions();
}

By the time the innerFn is actually called, the count variable has already increased to its final value.
To give each innerFn instance its own value for count, you could bind it as function argument:
let innerFn = function(count) { //<--- argument
let str = (function(){
return count.toString()
})();
counterDiv.innerHTML=str;
}.bind(null, count); // pass the global count into a bound argument
NB: make sure to check in your class that fn is defined (as the array will become empty at some point).
class DrawQueue{
constructor(interval){
this.sequence = [];
this.interval=interval?interval:50;
}
addFunction(fn){
this.sequence.push(fn);
//throw exception here if not a function
};
execFunctions(){
let intvl = setInterval(
()=>{
const fn = this.sequence.shift();
//clear interval & return here if not a function
if (fn) fn.call();
},
this.interval
)
}
}
let count = 0;
let counterDiv = document.querySelector('#counter')
let dq = new DrawQueue(1000);
function startCount(){ //call when window's loaded
let countFn=(()=>
{
let innerFn= function(count){
let str = (function(){
return count.toString()
})();
counterDiv.innerHTML=str;
}.bind(null, count);
count++;
dq.addFunction(innerFn);
})
while(count<10){
countFn();
}
dq.execFunctions();
}
window.onload = startCount;
<div id="counter"></div>
Even better would be to avoid a reference to a global variable, and pass count to the countFn function as parameter:
let counterDiv = document.querySelector('#counter')
let dq = new DrawQueue(1000);
function startCount(){ //call when window's loaded
let countFn=((count)=> // local variable
{
let innerFn= function(){
let str = (function(){
return count.toString()
})();
counterDiv.innerHTML=str;
}
dq.addFunction(innerFn);
})
for(let count = 0; count<10; count++){ // local variable
countFn(count); // pass it
}
dq.execFunctions();
}
Addendum
In your question's update you speak of more variables. In that case, pass an object around, which can have many properties, possibly even managed completely by the user-provided code:
let counterDiv = document.querySelector('#counter')
let dq = new DrawQueue(1000);
function startCount(){ //call when window's loaded
let countFn=((state)=> // local variable with count property
{
let innerFn= function(){
let str = (function(){
return state.count.toString()
})();
counterDiv.innerHTML=str;
}
dq.addFunction(innerFn);
})
for(let count = 0; count<10; count++){ // local variable
const state = {};
state.count = count;
countFn(state); // pass it
}
dq.execFunctions();
}
Depending on your expectations you should either use the same state object or create new state variables (within the loop, or even deeper in the execution context). This all depends on how you want the system to behave.

Related

How to trigger a var after x actions

I have the following code. I want to trigger the action in function activityDetected(eventName) only after 100 click. How to do this ?
I know I have to put let a = 1; ++a but not sure where...
https://pastebin.com/SMsJsikE
const intervalTimeout = 2000;
//here is where code should be added. let a = 1; ++a...
function activityDetected(eventName) {
console.log(`Activity detected with the event name: ${eventName}!`);
clearInterval(activityTimeout);
activityTimeout = setInterval(recordNoActivity, intervalTimeout);
}
document.addEventListener('click', _ => {
activityDetected('click');
});
You need to declare a counter outside the function and up it by 1 when the eventName is 'click'. After that check for a % 100 and put whatever action you want to call every 100 clicks in there.
Look at the code example:
// For ease, change this to a smaller value to detect more often, higher to detect less often!
const intervalTimeout = 2000;
let a = 0;
// Here's our interval, setting up the initial capture of no activity
let activityTimeout = setInterval(recordNoActivity, intervalTimeout);
// A single function to handle the events we're listening to.
// clears the interval and restarts it, also tells us which event has cleared the interval!
//here is where code should be added. let a = 1; ++a...
function activityDetected(eventName) {
if(eventName == 'click'){
a++;
if(a%100 == 0){
// Trigger whatever you want to trigger after every 100 clicks
}
}
console.log(`Activity detected with the event name: ${eventName}!`);
clearInterval(activityTimeout);
activityTimeout = setInterval(recordNoActivity, intervalTimeout);
}
// Set listening events
document.addEventListener('keydown', _ => {
activityDetected('keydown');
});
document.addEventListener('click', _ => {
activityDetected('click');
});
As correctly pointed by others on this thread the variable a should be declared and defined outside the function but the reason why this approach would work is because of Closure
So when the function is getting invoked an execution context is created which contains
scopeChain - it contains variableObject + all parent execution context's variableObject
variableObject - it contains function arguments / parameters, inner variable and function declarations
this - the this context.
Thus the variable a values would be saved before invoking the function and the variable will keep incrementing.

Table data is not appending when in a function

Whenever I write my code iteratively the program runs as it is supposed to, but when I place in a function like this it breaks.
function create_tableElements() {
Let myArticle = document.createElement(‘tr’);
Let rank = document.createElement(‘td’);
}
function assign_tableElements() {
Let count = 1
rank1 = count;
rank.textContent = rank1;
heroes_name.textContent = heroes[i].name;
}
function append_tableElements() {
myArticle.appendChild(rank);
myArticle.appendChild(heroes_name);
}
Does anyone know why this may happen? Is there a way for me to call a function within a function? I am using a for loop to loop through JSON. Now if I do not place in a function and just write the code, it will run perfectly fine. Just working on readability, and organizing my code better
There's a couple issues with the code you pasted (Let instead of let or the fancy single quotes).
I'm going to assume your phone or whatever tool you used corrected it. So let's say this is your code :
function create_tableElements() {
let myArticle = document.createElement('tr');
let rank = document.createElement('td');
}
function assign_tableElements() {
let count = 1;
rank1 = count;
rank.textContent = rank1;
heroes_name.textContent = heroes[i].name;
}
function append_tableElements() {
myArticle.appendChild(rank);
myArticle.appendChild(heroes_name);
}
Your code can't work because :
the rank variable is local to the create_tableElements function and can't be accessed by the append_tableElements function
same goes for the heroes_name function, it's local to the assign_tableElements function
You can fix this by :
either declaring these variables as global variables, outside of any function. It's not really a best practice, though.
change your function's definition so that they can access the same local variables : do you really need a function to create elements and another to append them to the DOM?
you could also use an Immediately Invoked Function Expression.
(function() {
// these variables will be visible to all the functions defined in this function, but won't be global :
let rank, myArticle, heroes_name;
function create_tableElements() {
myArticle = document.createElement('tr');
rank = document.createElement('td');
}
function assign_tableElements() {
let count = 1;
rank1 = count;
rank.textContent = rank1;
heroes_name.textContent = heroes[i].name;
}
function append_tableElements() {
myArticle.appendChild(rank);
myArticle.appendChild(heroes_name);
}
// invoking your functions :
create_tableElements();
assign_tableElements();
append_tableElements();
})();

How to make private or local variables?

So I checked a thread on here about global and local variables but didn't really find a solution to my problem. I just want a private or local variable to increment so that a function only fires once. I'll paste what I'm trying to achieve here any help would be much appreciate also please go easy on me I'm brand new to JavaScript. This code works but the variable I seems to be shared between functions.
function phonefun(){
i++;
console.log(i);
wage = wage - phone;
console.log(wage);
display();
document.getElementById('phone').style.backgroundColor = "darkgrey";
}
function waterfun(){
i++;
console.log(i);
wage = wage - water;
console.log(wage);
display();
document.getElementById('water-aid').style.backgroundColor = "darkgrey";
}
...the function is called on the click of a button, I want it so you
can only press the button
I think what you want to do is have your event handler unbind from the button after if fires. Thas is much better solution than counting how many times it's been clicked. Check out this link for how to bind and unbind event handlers using "vanilla" JS: https://plainjs.com/javascript/events/binding-and-unbinding-of-event-handlers-12/
In reference to your earlier questions...
A variable created inside of a function is said to be "scoped" to that function, which means that nothing outside of that function can access the variable. However, by initializing your variable without using the var or let keyword (the latter is ES6 syntax), you created an implicit global. This means that you inadvertently made it a global variable when you wanted it to be function-scoped.
Declaring a variable does not automatically assign a value of zero. If you do not assign a value, the value will be undefined.
If you had declared / assigned the variable thusly,var i = 0; or let i = 0; you would have had a properly scoped variable with an initial value of 0. The problem is, each time that function executed, the value would be reset to zero. To get the value to "stick" you would have to create state. You could do that by creating an object with getter and setter methods or by using a closure. However, the unbind solution seems to be the best way to go for what you want to do here.
I hope this helps.
To do what you want, you need a variable with a higher scope than the function so that the value can persist between function calls. A local variable will be garbage collected as the function returns and so, your counter would be lost.
var counter = 0; // This variable exists in a higher scope than the function
function loanfun(){
counter++;
if (counter == 1) {
console.log("function has run " + counter + " times.");
}
}
loanfun(); // Will run
loanfun(); // Won't run
you can make a class
function myStuff(){
this.i = 0,
this.loanfun = function(){
this.i++;
if (this.i == 1) {
wage = wage - loan;
console.log(wage);
display();
document.getElementById('loan').style.backgroundColor = "darkgrey";
}
}
}
var s = new myStuff();
s.loanfun();
s.loanfun();
You could try namespacing within an object:
var PageModule = {
count: 0,
loadfun: function (wage, loan) {
PageModule.count += 1;
if (PageModule.count === 1) {
console.log('execute!');
wage = wage - loan;
console.log(wage);
display();
document.getElementById('loan').style.backgroundColor = "darkgrey";
}
}
};
PageModule.loadfun();
PageModule.loadfun();
PageModule.loadfun();
// if you want to attach the method to a button
document.getElementById('my-btn-id').addEventListener('click', PageModule.loadfun);
Alternatively, you could use the following approach:
function myclickhandler () {
// do whatever you want here ...
//remove handler from button, so that the next button clicks will not do anything
document.getElementById('my-btn-id').removeEventListener('click', myclickhandler);
}
// attach the method to a button
document.getElementById('my-btn-id').addEventListener('click', myclickhandler);
Hope that this is what you want to do.But if you want simply to call(invoke) you function once just call and it will be executed only one time.
wage = 10;
loan = 5;
i=0; //this is the global variable
function loanfun(){
let j = i +1; //j is local variable
if (j === 1) {
wage = wage - loan;
console.log(wage);
//display();
document.getElementById('loan').style.backgroundColor = "darkgrey";
}
}
loanfun(); //invoke the function here
<div id="loan">
hi I am here working as expected
</div>

Own loops for each instantiated class in javascript

I wanted to try out creating a proof-of-concept of actors that have their own independent loops, outside of the main loop - I created something like that, but I'd like to know if there are some glaring issues, or if I am doing it completely wrong.
Basically I'd like to know if the right way to handle the "internal" loop would be using this, or if there is a better way of doing it (inside the live() function):
setTimeout(() => {this.live()}, 100);
Second question would be to know the best way of destroying an instantiated class, withing the class, with something like "this.destroy()" - right now I am just removing the connection from the container to the object
Example here: https://codepen.io/tommica/pen/qmNXYL
I'll paste the code itself too:
<ul id="simple-log"></ul>
<script>
// We will store out actors as "id" => "actor"
let actors = {};
// Custom logging functionality for a simple ul list
const sLog = (text) => {
let li = document.createElement('li');
li.innerHTML = text;
document.getElementById('simple-log').appendChild(li);
};
const randomNum = (min,max) => { return Math.floor(Math.random() * max) + min; }
// Actor definition
class Actor {
constructor(name, id) {
this.id = id;
this.name = name;
this.gender = randomNum(1,2) === 1 ? 'male' : 'female'; // Random gender
this.lastTime = null;
}
live() {
// Get the current time, and log every 5 seconds
let now = Date.now();
let difference = now - this.lastTime;
if(difference > 5000) {
sLog(`Actor "${this.name}" Log - Difference: ${difference}`);
this.lastTime = now;
}
// Determine if the actor died of a tragic accident
if(randomNum(1,100000) < 5) {
// Something tragic happened, that caused this actor to die
this.die();
} else {
// I'm pretty sure that this is not the best way, but for some reason just
// setTimeout(this.live, 1); does not work
setTimeout(() => {this.live()}, 100);
}
}
die() {
// Log the death
sLog(`Actor "${this.name}" died`);
// This seems really a wrong way to do this, should not need to rely on an element outside of the scope - something else should do this, but how?
delete actors[this.id];
}
}
// Function to spawn a new actor
let spawnActor = () => {
let id = 'a'+randomNum(1,9000000);
sLog('Spawning an actor');
let actorInstance = new Actor(id, id); // Rejoice!
actorInstance.live();
actors[id] = actorInstance;
}
// Simple loop that simulates the rendering of the screen
let lastTimeAnimated = null;
let mainAnimationLoop = () => {
// Logs every 5 seconds to the log
let now = Date.now();
let difference = now - lastTimeAnimated;
if(difference > 5000) {
sLog(`Main Animation Loop Log - Difference: ${difference}`);
lastTimeAnimated = now;
}
window.requestAnimationFrame(mainAnimationLoop);
}
// Simple loop that simulates a normal game main loop
let lastTime = null;
let mainLoop = () => {
// Mainloop logs every 5 seconds to the log
let now = Date.now();
let difference = now - lastTime;
if(difference > 5000) {
sLog(`Main Loop Log - Difference: ${difference}`);
lastTime = now;
}
// Random actor spawner
if(randomNum(1,10000) < 5) {
spawnActor(); // It truly is a blessed day!
}
setTimeout(mainLoop, 1);
}
// Should be obvious
let init = () => {
mainAnimationLoop();
mainLoop();
}
// Let's get started!
init();
</script>
Basically I'd like to know if the right way to handle the "internal" loop would be using this, or if there is a better way of doing it (inside the live() function): setTimeout(() => {this.live()}, 100);
There are many other ways to do this (some of them even involving a real while loop), but none of them is "the one right way".
I'm pretty sure that this is not the best way, but for some reason just
setTimeout(this.live, 1); does not work
See How to access the correct this / context inside a callback? for the why.
Second question would be to know the best way of destroying an instantiated class, withing the class, with something like "this.destroy()" - right now I am just removing the connection from the container to the object:
delete actors[this.id];
This seems really a wrong way to do this, should not need to rely on an element outside of the scope - something else should do this, but how?
You cannot "destroy" anything in javascript. If you want an instance to get garbage-collected, you need to remove all references to it. The right way to let an actor die is to just let it stop living - i.e. don't call .live() any more, and/or remove all timeouts that are scheduled to call it.
You don't need that container for anything (and in the code you've shown, you're not even using it). For some reason, spawnActor did store the instances, so it is its job to collect the dead. If you really don't need that collection, just omit it; if you use if for something then each actor should announce its death by setting an own property or by sending a message to the main actor, so that it can be removed from the collection as appropriate.

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.

Categories

Resources