- Published on
Functions & Variables (Part III)
8 mins
- Authors
- Name
- Juleshwar Babu
Table Of Contents
- The "new Function" syntax
- Scheduling: setTimeout and setInterval
- Decorators and Forwarding, call/apply
- Function Binding
- Arrow functions revisited
- Content Updates
The "new Function" syntax
Syntax:
let func = new Function ([arg1, arg2, ...argN], functionBody);
new Function('a', 'b', 'return a + b'); // basic syntax new Function('a,b', 'return a + b'); // comma-separated (present due to historical reasons) new Function('a , b', 'return a + b'); // comma-separated with spaces (present due to historical reasons)
functionBody
is a string. These are useful when we need to create functions during runtime (getting function body from the server)Functions created with
new Function
, have[[Environment]]
referencing the global Lexical Environment, not the outer one. Hence, they cannot use outer variables. But that’s actually good, because it insures us from errors. Passing parameters explicitly is a much better method architecturally and causes no problems with minifiers.
Scheduling: setTimeout and setInterval
Syntax:
let setTimeoutId = setTimeout(func: Function |code: string, [delay]: number, ***[arg1]: any, [arg2]: any, ...***) let setIntervalId = setInterval(func: Function |code: string, [delay]: number, ***[arg1]: any, [arg2]: any, ...***)
We can emulate the
setInterval
behaviour using nestedsetTimeout
s. Frankly, that’s a more controllable way of running code regularlylet timerId = setTimeout(function tick() { alert('tick'); timerId = setTimeout(tick, 2000); // (*) }, 2000);
Also the behaviour aligns more with what we expect 👇🏼
setInterval timeline
let i = 1; setInterval(function() { func(i++); }, 100);
setTimeout timeline
let i = 1; setTimeout(function run() { func(i++); setTimeout(run, 100); }, 100);
The nested
setTimeout
guarantees the fixed delay (here 100ms) in between running the business logic
Decorators and Forwarding, call/apply
Decorators are functions which take a function and add additional functionality to it/alter its behaviour
function slow(x) { // there can be a heavy CPU-intensive job here alert(`Called with ${x}`); return x; } function cachingDecorator(func) { let cache = new Map(); return function(x) { if (cache.has(x)) { // if there's such key in cache return cache.get(x); // read the result from it } let result = func(x); // otherwise call func cache.set(x, result); // and cache (remember) the result return result; }; } slow = cachingDecorator(slow); alert( slow(1) ); // slow(1) is cached and the result returned alert( "Again: " + slow(1) ); // slow(1) result returned from cache alert( slow(2) ); // slow(2) is cached and the result returned alert( "Again: " + slow(2) ); // slow(2) result returned from cache
call
andapply
allow us to set the context when a function is runfunc.call(context, ...args); func.apply(context, args);
There’s only a subtle difference regarding
args
:- The spread syntax
...
allows to pass iterableargs
as the list tocall
. - The
apply
accepts only array-likeargs
.
- The spread syntax
Passing all arguments along with the context to another function is called call forwarding
let wrapper = function() { return func.apply(this, arguments); };
Method Borrowing is a way of borrowing a method from one object to use it on another object
let arrayLikeObject = { 0: "a", 1: "b", 2: "c", length: 3 } // Result required: a,b,c using Array.join method [].join.call(arrayLikeObject)
Function Binding
let boundFunc = func.bind(context, ...args) // Any args provided during binding set fixed arguments for the function func
An use-case
No context bound
let user = { firstName: "John", sayHi() { alert(`Hello, ${this.firstName}!`); } }; setTimeout(user.sayHi, 1000); // Hello, undefined **/** which essentially means let sayHi = user.sayHi setTimeout(sayHi, 1000) */**
this is lost Context is present
This is one solution as the
user
is just accessed from the outer Lexical Environment. But this solution has a problem. If theuser
object changes before thesetTimeout
runs, the code can breaklet user = { firstName: "John", sayHi() { alert(`Hello, ${this.firstName}!`); } }; setTimeout(function() { user.sayHi(); // Hello, John! }, 1000);
To prevent issues as mentioned above, we can use bind which fixes the context in time to the function. So even if the
user
object is altered before the bound function runs, the bound function runslet user = { firstName: "John", sayHi() { alert(`Hello, ${this.firstName}!`); } }; setTimeout(user.sayHi.bind(user), 1000); // Hello, John
Arrow functions revisited
- Link: https://javascript.info/arrow-functions
- Arrow functions:
- Do not have
this
- Do not have
arguments
- Can’t be called with
new
- They also don’t have
super
, but we didn’t study it yet. We will on the chapter Class inheritance
- Do not have
Content Updates
- 2023-09-07 - init