Is there a way to do this in JS
function namedFunction(elements,args) {
const domElements = document.querySelector(elements);
const initialValue = 0;
let incrementBy = 5;
return function() {
// Do something to domElements based on initialValue and incrementBy
// function needs to run the first time namedFunction is called
// and this is the only function that needs to run on subsequent calls to namedFunction
}.call(null)
// the .call does not work as intended here, but this is basically what I want to do.
}
I think I can do namedFunction()() with the code above in order to invoke both, but I'm wondering if there is another way.
The longer version of the function would look like this:
function namedFunction(elements,args) {
const domElements = document.querySelector(elements);
const initialValue = 0;
let incrementBy = 5;
function namedFunctionEventHandler() {
// Do something to domElements based on initialValue and incrementBy
// function needs to run the first time namedFunction is called
// and this is the only function that needs to run on subsequent calls to namedFunction
}
namedFunctionEventHandler();
return namedFunctionEventHandler;
}
The goal would be to pass a single function as an event handler, that the first time it runs it does initial calculations, caches dom elements and the more heavier stuff, then executes the logic that is abstracted in the returned function and on subsequent calls it uses the data from the closure.
Edit: the namedFunction does not need to accept any arguments, its just for demonstration purposes.
document.addEventListener('scroll', namedFunction)
is what I want to be able to do.
#CertainPerformance - Sorry, I misread your answer.
If you take a look at the end result I would like to achieve, your proposition wont actually work as intended, as if I pass an invoked function as an event handler, its gonna run before an event has actually happened.
You can make namedFunction into an IIFE that saves a reference to a function (initially undefined). On call, if that variable is undefined, carry out the expensive calculations and then assign to the variable; if the variable is defined, then simply call it.
const handler = (() => {
let cheapFn;
return () => {
if (cheapFn) {
cheapFn();
return;
}
// expensive calculations here
const domElements = document.querySelector(elements);
...
cheapFn = () => {
// assign to cheapFn
};
cheapFn();
};
})();
Demo:
const handler = (() => {
let cheapFn;
return () => {
if (cheapFn) {
cheapFn();
return;
}
// expensive calculations here
console.log('expensive');
cheapFn = () => {
console.log('cheap');
};
cheapFn();
};
})();
document.addEventListener('scroll', handler);
body {
height: 400px;
}
body
You can take advantage of the fact that functions in JavaScript are first-class objects, and store the function state (initialized/uninitialized) in a property of the function.
The data computed during initialization can be stored in the function properties as well, please take a look at the demo:
const namedFunction = function(elements,args) {
if (!namedFunction.isInitialized) {
console.log('Initialization: Computing initial value...');
namedFunction.initialValue = 10 * 10;
console.log(`Initial value: ${namedFunction.initialValue}`);
namedFunction.isInitialized = true;
}
return function() {
console.log('Running regular operation:');
console.log(`Current value: ${--namedFunction.initialValue}`);
}.call(null)
}
document.getElementById('demo').addEventListener('click', namedFunction);
<button id="demo">Run</button>
Related
For example:
// run this:
alert('Loading...');
// dont run this again:
alert('Loading...');
I don't want to ever repeat that.
How can I do this with convenience (preferably without using booleans)?
The standard way is to use a boolean flag.
But, if you have an aversion to booleans, you can do it by overwriting the function, thus ensuring it literally can never be called again.
function loadButOnlyOnce() {
console.log('This will only happen once!');
loadButOnlyOnce = function() {};
}
loadButOnlyOnce();
loadButOnlyOnce();
Your IDE will probably spew out warnings along the lines of "What are you doing, this is overwriting the function!". But it's your code, and you can do it if you want.
So, now you want a generic solution you can use with different functions? You can do this like so:
function allowOnlyOneCall(f) {
return function() {
f.apply(this, arguments);
f = function() {};
}
}
function myMethod(p1) {
console.log('myMethod is being invoked. p1 = ' + p1);
}
myMethod = allowOnlyOneCall(myMethod);
myMethod(5);
myMethod(5);
Here is one clean implementation you could use so you can avoid the usage of booleans for every single task you don't want to repeat:
var cache = [];
function do_once(task, fn) {
if(cache.indexOf(task) == -1) { // shorthand: !~cache.indexOf(task)
cache.push(task);
return fn();
}
}
Usage:
var alertLoading = alert.bind(null, "Loading...");
do_once("alert_loading", alertLoading); // will run
// later on...
do_once("alert_loading", alertLoading); // will not run again
This works as long as you give each task you don’t want to repeat a different name. Regardless of the function provided as the second argument to do_once, it will not run as long as do_once has already been called using the same task name.
First create a variable to store whether the function has already run or not (such as hasRun). Then simply store your functionality inside an if condition which checks that this variable is false. You would need to update this variable after running the logic for the first time.
This can be seen in the following:
hasRun = false;
document.getElementsByTagName('button')[0].addEventListener('click', function() {
if (!hasRun) {
console.log('Loading...'); // Gets run once
hasRun = true; // Set the flag to true so the conditional doesn't get entered again
} else {
console.log('The function already ran!'); // Runs every subsequent time
}
})
<button>Click me</button>
If you want a factory function that memoizes the result of calling a function with a single primitive value as a parameter, you should use a Map:
const once = (() => fn => {
const cache = new Map()
return value => {
if (!cache.has(value)) {
cache.set(value, fn(value))
}
return cache.get(value)
}
})()
function verboseSquare (value) {
console.log('expensive calculation')
return value * value
}
const squareOnce = once(verboseSquare)
console.log(squareOnce(4))
console.log(squareOnce(4)) // skipped work
console.log(squareOnce(5))
console.log(squareOnce(5)) // skipped work
I'm using the Intersection Observer API to track the visibility of multiple element on a web page. When an element becomes visible, a function callback() should be executed. The restriction: For each element, the function may only be executed once.
Here is my current implementation for a web analytics project:
const elements = document.querySelectorAll('[data-observable]');
const callback = str => { console.log(str); };
const observer = new IntersectionObserver(handleIntersection);
elements.forEach(obs => {
observer.observe(obs);
});
function handleIntersection(entries, observer){
entries.forEach(entry => {
if (entry.intersectionRatio > 0) {
// Call this function only once per element, without modifying entry object
callback('observer-' + entry.target.getAttribute('data-observable'));
}
});
}
I'm struggeling to find a solution that does not modify existing elements, the IntersectionObserver or the IntersectionObserverEntries.
Usually I would use a closure to ensure that a function gets only executed once:
function once(func) {
let executed = false;
return function() {
if (!executed) {
executed = true;
return func.apply(this, arguments);
}
};
}
But in this case I have difficulties applying the function because IntersectionObserver uses a weird callback iterator logic that get's executed everytime any element changes (instead of using a event-driven model).
Any ideas, how to implement a once per element function call that does not mutate other elements or objects?
As James pointed out in the comments, the easiest solution to this problem is unobserving the element once it has become visible and the callback has been invoked.
const elements = document.querySelectorAll('[data-observable]');
const callback = str => { console.log(str); };
const observer = new IntersectionObserver(handleIntersection);
elements.forEach(obs => {
observer.observe(obs);
});
function handleIntersection(entries, observer){
entries.forEach(entry => {
if (entry.intersectionRatio > 0) {
callback('observer-' + entry.target.getAttribute('data-observable'));
observer.unobserve(entry.target);
}
});
}
I didn't find any viable solution to use a closure to control how often a function can be called.
I am looking for a good technique to get away from what I am tempted to do: to set a global variable.
The first time someone runs a function by clicking a button it triggers an initial function to turn a few things into draggables. Later, if they click the button a second time I want to determine if the init function has been initialized, and if so to not call it again. I could easily do this by setting a global variable from the init function and then checking that variable from the click function, but I'm wondering how to do this without setting a global variable. I would really like an example of a way to do this.
You could add a property to the function:
function init() {
init.called = true;
}
init();
if(init.called) {
//stuff
}
While #Levi's answer ought to work just fine, I would like to present another option. You would over write the init function to do nothing once it has been called.
var init = function () {
// do the initializing
init = function() {
return false;
}
};
The function when called the first time will do the init. It will then immediately overwrite itself to return false the next time its called. The second time the function is called, the function body will only contain return false.
For more reading: http://www.ericfeminella.com/blog/2011/11/19/function-overwriting-in-javascript/
Why don't you just check to see if your draggables have a class of draggable on them?
if ($('.mydiv').is('.draggable')) {
//do something
}
Function.prototype.fired = false;
function myfunc() {
myfunc.fired = true;
// your stuff
};
console.log(myfunc.fired) // false
myfunc();
console.log(myfunc.fired) // true
What you could do is unhook the init function from the prototype.
var Obj = function () {
this.init = function () {
document.write("init called<br/>");
this.init = null;
}
}
var o = new Obj();
if (o.init) document.write("exists!<br/>");
o.init();
if (o.init) document.write("exists!<br/>");
o.init();
The first if will be true and print exists! but since the function removes itself, the second if will fail. In my example, I call the second init unconditionally just to show that nothing will happen, but of course you could call it only if it exists:
if (o.init) o.init();
http://jsfiddle.net/coreyog/Wd3Q2/
The correct approach is to use the Javascript Proxy APIs to trap the function calls using apply handler.
const initFun = (args) => {
console.log('args', args);
}
const init = new Proxy(initFun, {
apply(target, thisArg, args){
target.calls = target.calls ? target.calls + 1 : 1;
return target.apply(thisArg, args);
}
});
init('hi');
console.log(init.calls); // 1
init('hello');
console.log(init.calls); // 2
I found a bug, and tracked it down.
You can see a simplified example of my code here.
As it turns out, I need to debounce my if() statement rather than debouncing the function itself.
I'd like to keep the debounce as a standalone function, but I'm not sure then how to pass the conditional in.
Any pointers?
Here's the code:
var foo = function(xyz) {
alert(xyz);
};
function setter(func, arg1, arg2) {
return {
fn: func,
arg1: arg1,
arg2: arg2
};
}
function debounce(someObject) {
var duration = someObject.arg2 || 100;
var timer;
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(function() {
someObject.fn(someObject.arg1);
timer = 0;
}, duration);
}
var toggle = true;
if (toggle) {
debounce(setter(foo, 'The best things in life are worth waiting for.', 1250));
} else {
foo('Instant gratification is sweet!!');
}
Using your example, why not pass toggle in as arg 1... something like:
var toggle = true;
var debouncedFunk = function(toggle) {
if (toggle)
// the function call
else
// something else
};
debounce(debouncedFunk, toggle, 1250);
You should also look into using the Function objects .call and .apply methods. They are for calling the function and passing in arguments. Taking the example function:
var example = function(one, two) {
// Logic here
};
You can call it in three ways:
// First
example(1, 2);
// Second
example.call({}, 1, 2);
// Third
example.apply({}, [ 1, 2 ]);
The first is the standard way to call a function. The difference between the first and the .call is that the first parameter to .call is the context object of the function (what this will point to inside the function), the other parameters are passed after that (and a known list is required for .call. The benefit of .apply is that you can pass an array to the function of arguments and they will be assigned to the parameter list appropriately, the first parameter is still the context object.
It would simplify your debounce function, instead of having to deal with a structured object as you currently do.
A suggestion for your debounce:
var debounce = function(funk, delay) {
var args = [];
if (arguments.length > 2)
args = [].slice.call(arguments, 2);
setTimeout(function() { funk.apply({}, args); }, delay);
};
Changing your current if to:
var toggle = true;
var debouncedFunk = function(toggle) {
if (toggle)
// Do if true
else
// DO if false
};
debounce(debouncedFunk, 1000, toggle);
Maybe too much information (sorry)?
As a last note, I'd recommend using a framework (if possible) where these functions have been implemented already (and many other useful functions) such as Underscore. Using Underscore your example would look like:
// Define debouncedFunk and toggle
debouncedFunk = _.bind(debouncedFunk, {}, toggle);
debouncedFunk = _.debounce(debouncedFunk, 1000);
debouncedFunk();
EDIT
Fixed the underscore example, _.debounce returns a function that will execute only after the delay but it still needs to be called.
I'm wondering if there is a way to implement a generic "memoize" functional (as in a function with a function as input and a function as output, as python's decorators) capable of handling also cps-style functions.
for a normal function (as in "the result value comes back by the return, the parameters are only for input!") a memoize function can be as simple as (in javascript)
function memoize(fun) {
var cache = {};
return function () {
var args = Array.prototype.slice.call(arguments);
if (args in cache)
return cache[args];
var ret = fun.apply(this, arguments);
cache[args] = ret;
return ret;
};
}
but a cps-style function cannot be memoized by my simple memoize function, cause I need to evaluate "again" the arguments of type function, knowing also the parameter to pass to them.
For example, given the function
function cps(param, next) {
var ret = param + 1;
// setTimeout for simulate async behaviour
setTimeout(function () {
next(ret);
}, 0);
}
maybe I can find that next is a function, but its signature (well... maybe, but it's tricky), and definitely not the parameters used in the function!
Can someone tell me I'm wrong? :D
I'm interested to be able to memoize an half dozen of cps-style functions and I don't want to mess with the logic inserting a "cache" in every one of them.
I'm new to CPS, but I think you'll have to construct your functions in a particular way.
Your CPS functions have the following structure (generalising from your example):
function cps(param, next) {
var ret = someFunctionOfParam(param);
// setTimeout for simulate async behaviour
setTimeout(function () {
next(ret);
}, 0);
}
So, you could use your standard memoizer, and construct the CPS function as well. Keeping this separate for the sake of it, first the CPS-maker (assumes the last argument for the functions is always the function to pass to):
function cpsMaker(transformFunc) {
return function() {
var args = Array.prototype.slice.call(arguments);
var next = args.pop(); // assume final arg is function to call
var ret = transformFunc.apply(this,args);
// setTimeout for simulate async behaviour
setTimeout(function () {
next(ret);
}, 0);
}
}
And then the memoizer can be used in conjunction with it:
function plusOne(val) {
return val+1;
}
var memoPlusOne = memoize(plusOne);
var cpsMemPlusOne = cpsMaker(memoPlusOne);
cpsMemPlusOne(3,function(n){console.log(n)});
The point is to separate the memoization of the transform from the CPS construction.
Thank you for introducing the idea of memoized CPS; even if this answer is rubbish, it has been an eye-opener for me!