2

My code involves lots of event handlers, which do not require any context (scope) to execute. When I use inline lambda functions in contrast to statically declared (constant) lambda functions, a new event handler is created each time I assign the event handler.

Question: Is it possible to define an inline lambda function, which does not create a new Function object for each time the lambda is passed as a callback. (Given that no unique context scope is required.)

Two examples illustrating the trade-off between notation and memory usage.

1. Inline lambda: (desired notation, unnecesarry memory consumption)

for (const divId of ["div1", "div2", "div3"]) {
     documentgetElementById(divId).addEventListener("click", e => console.log(e));
}                                                         // Creates a new Function
                                                          // object for each loop
                                                          // cycle.

Desired notation, but creates a new Function callback (e => console.log(e)) for each divId, despite the callback not depending on any context information (hence being functionally equivalent for each divId). It would be great if there was a way to just pass a pointer to this function.

2. Statically declared lambda: (undesired notation, desired and minimal memory consumption)

const staticLambda = e => console.log(e); // Function object created only once.
for (const divId of ["div1", "div2", "div3"]) {
     documentgetElementById(divId).addEventListener("click", staticLambda);
}

Undesired notation (needs the extra constant), but on the up-side only creates the Function callback (staticLambda) once for all three divIds. Imagine how this would look inside a class method; the lambda function needs to be declared outside of its respective method as a static property of the class, hence destroying the elegance of lambdas (which are so good at keeping the callback code at the location where it is passed).

Note: This is a simplified example. I realize that creating 2 (out of 3) unnecessary callbacks does not affect performance substantially, however, I am interested in properly handling cases with orders of magnitude more callbacks.

Simon
  • 21
  • 3
  • I can't see a way to do this. The only thing that comes to mind is to leave this to a pre-processor that is able to determine that the function doesn't use any loop variables and rewrites the code to hoist the declaration in a higher scope. – Felix Kling Sep 07 '20 at 13:58
  • @FelixKling I am using typescript (pre-processor), would this open up more possibilities? I don't even need to automatically check for usage of context variables, if I could just use (or define) a custom arrow syntax enforcing this behavior, that could do the trick. – Simon Sep 07 '20 at 14:51

3 Answers3

0

You might want to add the event listeners in a function, like this:

function addListeners(ids, listener){
  for( const divId of ids){
     document.getElementById(divId).addEventListener("click", listener);
  }
}

Now you can call your function like this:

addListeners(["div1", "div2", "div3"], e => console.log(e));
Marko
  • 372
  • 2
  • 7
  • Thanks for thinking along. In essence this is the same as my second example (2. Statically declared lambda), where the `for` is wrapped by a function. The reason this doesn't do the trick for me is because I don't know at _one_ moment in time what all my `divIds` will be (I agree that's not clear from my code). (Imagine for example that the lambda is an eventhandler passed in a constructor of an HTML element. I would prefer to have the lambda written in the constructor instead of declaring it as a static property and then _using_ it in the constructor.) – Simon Sep 09 '20 at 13:46
  • Hi @Simon. I think you want to build your lambda dynamically. This can't be done as such. What you could do however is return a lamdba / function from another function. This way the wanted behaviour (the returned function) can be dependent on other values (such as arguments of the function, or state of the object) at runtime. – Marko Sep 09 '20 at 15:22
  • I indeed think that what I want can't be done. I'll just create the lambda once as a static constant and then reuse it (I don't need runtime information such as scope binding / object state). Thanks for thinking along! – Simon Sep 11 '20 at 06:32
0

While these kinds of optimizations do help in performance (Does use of anonymous functions affect performance?) I don't think it has a very large impact and shouldn't be pursued just for performance reasons, but your case can of course be a valid one.

If you are managing many event listeners you should also take care to remove them when they become unused. Removing an event listener is the easiest if you still have a reference to the original handler around and thus declaring the handler somewhere outside the scope where it is attached.

I would also argue that it is good practice to put 'simple' event handlers in their own functions and keep them in a separate module or file, this allows for easier refactoring. This also solves the performance issue.

Qurben
  • 1,276
  • 1
  • 10
  • 21
  • Alright, I agree with the middle of your answer. Maybe inline lambda's are not a good idea for event listeners anyway because of this. – Simon Sep 09 '20 at 14:05
0

Memory usage should not be the reason you choose one form over the other. For two reasons:

  1. The usage is so small as to be almost unmeasurable in most cases.

  2. Javascript interpreters will most likely compile a single function where it can when optimizing bytecode or JIT machine code.

However you should choose either form due to their properties:

  1. Declare implicit function when you have more than one place using the same logic. Always look for opportunities to refactor code especially an easy refactor as naming a function. It reduces future workload when fixing bugs.

  2. When you need to instantiate a new closure each time you call your function then use an IIFE (inline). Modern javascript may compile it to a single function but they will create a new closure each time the function is "redeclared".

slebetman
  • 109,858
  • 19
  • 140
  • 171
  • Point 1.1: is the usage really so small when I create 100.000 HTML objects, and with that 100.000 exactly the same event handlers? Point 1.2: I doubt that this is compiled the way you say. I observed it with the firebug profiler, and saw that one `Function` object is created for each lambda. – Simon Sep 09 '20 at 13:50
  • Point 1.3: The usage itself may not be measurable, but I have the feeling that the garbage collecter takes a lot of time on these objects. Point 2.1: It isn't about naming the function, after all, I only write it in _one_ location. Point 2.2: I'll see what this can do with this! I'll let you know! – Simon Sep 09 '20 at 13:57
  • So far in the wild (as in, real-world experience of people) I have not seen nor ever heard any javascript garbage collection slowing down the entire program like Java has a reputation for. Indeed, most modern languages like go, Swift, C# etc. has not had any complaints from garbage collection the way people used to complain about C++ and still sometimes complain about Java. There were issues with garbage collection. For example IE (which almost nobody uses anymore) older than IE7 or IE8 could not garbage collect circular references. – slebetman Sep 09 '20 at 14:44
  • Okay, then I need to investigate why the profiler points out it is doing 90% of the time garbage collection (despite it didn't reach the GC cealing, and there there being no garbage to collect). – Simon Sep 11 '20 at 06:35