Published on

Functions & Variables (Part III)

8 mins

Authors
  • Name
    Juleshwar Babu
    Twitter

Table Of Contents


The "new Function" syntax

  • Link: https://javascript.info/new-function

  • 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

  • Link: https://javascript.info/settimeout-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 nested setTimeouts. Frankly, that’s a more controllable way of running code regularly

    let 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);
    
    Timeline showing that calling a function with setInterval doesn't leave a gap of exact X seconds between function calls (Some time is consumed in running the function)

    setTimeout timeline

    let i = 1;
    setTimeout(function run() {
      func(i++);
      setTimeout(run, 100);
    }, 100);
    
    Timeline showing that calling a function with setTimeout mimicing setInterval ensures exact X seconds between function calls

    The nested setTimeout guarantees the fixed delay (here 100ms) in between running the business logic

Decorators and Forwarding, call/apply

  • Link: https://javascript.info/call-apply-decorators

  • 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 and apply allow us to set the context when a function is run

    func.call(context, ...args);
    func.apply(context, args);
    

    There’s only a subtle difference regarding args:

    • The spread syntax ... allows to pass iterable args as the list to call.
    • The apply accepts only array-like args.
  • 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 the user object changes before the setTimeout runs, the code can break

    let 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 runs

      let 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
    That’s because they are meant for short pieces of code that do not have their own “context”, but rather work in the current one. And they really shine in that use case.

Content Updates

  • 2023-09-07 - init