JavaScript

Dynamic Context

const obj = {
  firstName: 'Arfat',
  print: function() {
    console.log(this.firstName);
  }
};

setTimeout(obj.print, 100);
var obj = {
  firstName: 'Jane',
  friends: ['Tarzan', 'Cheeta'],
  loop: function () {
    this.friends.forEach(function (friend) {
      console.log(this.firstName + ' knows ' + friend);
    });
  },
};

obj.loop();

1

2

Did you get...

1

undefined

2

undefined knows Tarzan
undefined knows Cheeta
let obj = {
  score: 200,
  getScore() {
    console.log(this.score);
  },
};

doSomething(obj.getScore);

Assume doSomething is a function which accepts a function as an argument and calls it.

Not calling getScore. Just passing a reference.

Object have Methods

let obj = {
  firstName: 'arfat',
  print() {
    return this.firstName;
  },
};

Methods are actions that can be performed on objects.

What is the value of the `this` keyword in a method?

In Java (statically bound)

class Person {
    String name = "arfat";
    void printName() {
        System.out.println(this.name);
        System.out.println(name);
    }
}

public class MyClass {
    public static void main(String args[]) {
        Person p = new Person();
        System.out.println("in main()");
        p.printName();
    }
}

Every function is a method.

That is, there are no functions. Only methods.

In C++ (static bind)

class Person {
    string name = "arfat";
    
    public:
    void printName() {
        cout<<this->name;
    }
};

int main() {
    Person p;
    cout<<"in main"<<endl;
    p.printName();
    return 0;
}

In C++ (static bind)

class Person {
    string name = "arfat";
    
    public:
    void printName() {
        cout<<this->name;
    }
};

void printName2() {
    cout<<this->name;
}

int main() {
    Person p;
    cout<<"in main"<<endl;
    p.printName();
    return 0;
}
 error: invalid use of ‘this’ in non-member function

Clear distinction between functions and methods

In JS

Every function is a method

Every method is a function

In other words, there is no distinction between functions and methods.

Then, what is the value of `this` in a method?

There is no value of the `this` keyword to begin with.

The object to which `this` is will be bound to is determined at runtime depending on the how the function was executed / called.

This can be stated in short, by saying, the `this` keyword is dynamically bound.

A function can become a method of any object

global.text = ' value on the global object';

function print() {
  console.log('I am printing -- ', this.text);
}

let obj1 = {
  text: 'values of OBJ1',
  print: print,
};

let obj2 = {
  text: 'values of OBJ2',
  print,
};

print();
obj1.print();
obj2.print();
  • this is not a compile-time binding but is a run-time binding.
  • this has nothing to do with where and how the function is declared but it has everything to do with how that function is invoked/called.

Important Rules

`this` inside methods

  • The `this` value can be modified in a couple of ways.
  • Default binding (when no object is supplied)
  • Implicit binding (when an object is supplied naturally)
  • Arrow functions (when arrow functions are used)
  • Explicit binding (When call, bind, or apply is used)
  • new binding (When `new` keyword is used)
  • strict mode (When using 'use strict')
  • Contextual (libraries or framework may change values, for e.g. Event emitters)
  • Modules
  • Others

Default binding

  • Standalone function invocation.
  • If no other binding rule matches, then it is default binding. It sort of works like default catch-all rule.
function add1(num1, num2) {
 return num1 + num2;
}
const add2 = function(num1, num2) {
 return num1 + num2;
}


add1(10,15)
add2(10,12)
function print() {
  console.log(this.firstName)
}

print()

Default binding is the global object*

When a function is executed normally (without any help of object), the this context is set to global.

function f() {
  return this;
}

// in nodejs
console.log(f() === global);

// in browsers
console.log(f() === window);

*in ES5

Implicit binding

function foo() {
  console.log(this.a);
}

const obj = {
  a: 2,
  foo: foo,
};

obj.foo();

Implicit binding rule says that it's adjoining object which should be used for the function call's this binding.

Problem: Implicit bind can be lost

function foo() {
  console.log(this.a);
}

const obj = {
  a: 2,
  foo: foo,
};

const bar = obj.foo;
bar();

Losing `this` When Extracting a Method

let counter = {
    count: 0,
    inc: function () {
        this.count++;
    }
}
  • We have called the value of counter.inc  as a function.

  • Hence, this  is the global object and we have performed window.count++ .

  • window.count  does not exist and is undefined . Applying the ++  operator to it sets it to NaN.

  • Use strict mode for avoiding this.

> let func = counter.inc;
> func()
> counter.count  // didn’t work
0

Explicit binding

The solution to the previous problem is explicit binding.

  • bind
  • call
  • apply

The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.

var module = {
  x: 42,
  getX: function() {
    return this.x;
  }
}

var unboundGetX = module.getX;
console.log(unboundGetX()); 
// The function gets invoked at the global scope
// expected output: undefined

var boundGetX = unboundGetX.bind(module);
console.log(boundGetX());
// expected output: 42

Syntax: function.bind(thisArg[, arg1[, arg2[, ...]]])

.bind

How to properly extract a method

> var func3 = counter.inc.bind(counter);
> func3()
> counter.count  // it worked!
1

Callbacks and extracted methods

function callIt(callback) {
    callback();
}
> callIt(counter.inc)

> callIt(counter.inc.bind(counter))

The call() method calls a function with a given this value and arguments provided individually.

Syntax: function.call(thisArg, arg1, arg2, ...)

function greet() {
  var reply = [
    this.animal,
    'typically sleep between',
    this.sleepDuration
  ].join(' ');

  console.log(reply);
}

var obj = {
  animal: 'cats', sleepDuration: '12 and 16 hours'
};

greet.call(obj); 
// cats typically sleep between 12 and 16 hours

.call

The apply() method calls a function with a given this value, and arguments provided as an array (or an array-like object).

var numbers = [5, 6, 2, 3, 7];

var max = Math.max.apply(null, numbers);

console.log(max);
// expected output: 7

var min = Math.min.apply(null, numbers);

console.log(min);
// expected output: 2

Syntax: function.apply(thisArg, [argsArray])

.apply

Note: While the syntax of apply() is almost identical to that of call(), the fundamental difference is that call() accepts an argument list, while apply() accepts a single array of arguments.

With call, you have to know the arguments at compile time. With apply, you can defer that decision at runtime.

Problem: Functions Inside Methods Shadow this

let obj = {
  firstName: 'Jane',
  friends: ['Tarzan', 'Cheeta'],
  loop: function () {
    this.friends.forEach(function (friend) {
      console.log(this.firstName + ' knows ' + friend);
    });
  },
};

obj.loop();

Problem: Functions Inside Methods Shadow this

function callback_ForEach(friend) {
  console.log(this.firstName + ' knows ' + friend);
}

let obj = {
  firstName: 'Jane',
  friends: ['Tarzan', 'Cheeta'],
  loop: function () {
    this.friends.forEach(callback_ForEach);
  },
};

obj.loop();

Workaround 1: that = this

loop: function () {
    'use strict';
    var that = this;
    this.friends.forEach(function (friend) {
        console.log(that.name+' knows '+friend);
    });
}

Workaround 2: bind()

loop: function () {
    'use strict';
    this.friends.forEach(function (friend) {
        console.log(this.name+' knows '+friend);
    }.bind(this));  // (1)
}

Workaround 3: a thisValue for forEach()

this.friends.forEach(function (friend) {
        console.log(this.name+' knows '+friend);
}, this);

At this point, even JS developers said

So they created another function with reduced power.

Arrow Functions

() => { ... } // no parameter
     x => { ... } // one parameter, an identifier
(x, y) => { ... } // several parameters
x => { return x * x }  // block
x => x * x
const arr = [1, 2, 3];
const squares = arr.map(x => x * x);

New Kind of Function

Specifying a body:

The complete list of variables whose values are determined lexically is:

arguments     super     this     new.target

var obj = {
  name: 'Jane',
  friends: [ 'Tarzan', 'Cheeta' ],
  loop: function () {
      this.friends.forEach(
           (friend) => {
              console.log(this.name+' knows '+friend); 
          }
      );
  }
};

obj.loop()

Workaround 4: Arrow functions

  • The following constructs are lexical: arguments, super, this, new.target
  • It can’t be used as a constructor: Normal functions support new via the internal method [[Construct]] and the property prototype. Arrow functions have neither, which is why new (() => {}) throws an error.

Arrow functions versus normal functions

An arrow function is different from a normal function in only two ways:

const obj = {
  firstName: 'Arfat',
  print: function() {
    console.log(this.firstName);
  }
};

setTimeout(obj.print, 100);

Example

  • Remember to find the the execution place
  • Not the place of definition

In DOM event handler

this is set to the element the event fired from

function bluify(e) {
  // Always true
  console.log(this === e.currentTarget);
  // true when currentTarget and target are the same object
  console.log(this === e.target);
  this.style.backgroundColor = '#A5D9F3';
}

target = element that triggered event; currentTarget = element that listens to event.

In an inline event handler

<button onclick="alert(this.tagName.toLowerCase());">
  Show this
</button>

When the code is called from an inline on-event handler, its this is set to the DOM element on which the listener is placed:

In ES6 modules

`this` is bound to undefined

In Node CJS module system

this is bound to the exports obejct

<script src="script.js" type="module"></script>

Strict Mode vs Sloppy Mode

  • JavaScript's strict mode, introduced in ECMAScript 5, is a way to opt in to a restricted variant of JavaScript, thereby implicitly opting-out of "sloppy mode".
  • Strict mode isn't just a subset: it intentionally has different semantics from normal code.

To invoke strict mode for an entire script, put the exact statement "use strict";

Strict mode applies to entire scripts or to individual functions.

  • Eliminates some JavaScript silent errors by changing them to throw errors.
  • Fixes mistakes that make it difficult for JavaScript engines to perform optimizations
  • Prohibits some syntax likely to be defined in future versions of ECMAScript.

Strict mode makes several changes to normal JavaScript semantics:

'use strict';
foo = 17;
foo = 17;
var undefined = 9
var Infinity = 10;

if (undefined) {
  console.log('is true');
}
'use strict';
var undefined = 9;
var Infinity = 10;

if (undefined) {
  console.log('is true');
}

Context in Strict mode

In sloppy mode,  this is always an object:

  • either the provided object if called with an object-valued this; the value, boxed, if called with a Boolean, string, or number this;
  • or the global object if called with an undefined or null this.
function fun() { return this; }
fun() === global;
typeof fun.call(2) === 'object';
fun.apply(null) === global;
fun.call(undefined) === global;
typeof fun.bind(true)() === 'object';
'use strict';
function fun() { return this; }
fun() === undefined;
fun.call(2) === 2;
fun.apply(null) === null;
fun.call(undefined) === undefined;
fun.bind(true)() === true;

"new" binding

  • Constructor functions “construct” objects. They are equivalent to classes in other languages.
  • Since JavaScript has no concept of a class (even though it has class keyword), functions rise up to the task.
  • A function, when used with the new operator, is called a constructor function. The new operator changes the behavior of the function.
function Person(name) {
  this.name = name;
}
const person1 = new Person('Arfat');
console.log(person1); 
// Person { name: 'Arfat' }

Dynamic Context

By Arfat Salman