03. Function Invocation

From Gentle explanation of 'this' keyword in JavaScript

In JavaScript, this is the current execution context of a function. JS has 5 function invocation types:

  • Function invocation: alert('Hello World!')

  • Method invocation: console.log('Hello World!')

  • Arrow function invocation.

  • Constructor invocation: new RegExp('\\d')

  • Indirect invocation: alert.call(undefined, 'Hello World!')

    and each one defines its own context, this behaves slight different than developer expects.

Before starting, let's familiarize with a couple of terms:

  • Invocation is executing the code that makes the body of a function (simply calling the function). For example parseInt function invocation is parseInt('15').

  • Context of an invocation is the value of this within function body.

  • Scope of a function is a set of variables, objects, functions accessible within a function body.

Function Invocation

In function invocation this is the global object. The global object is determined by the execution environment.

  • It is the window object in a web browser.

  • It is the global object in a node environment.

  • this is undefined in a function invocation in strict mode. The strict mode is active not only in the current scope, but also in the inner scopes (for all functions declared inside).

function func() {
  return this;
}

const val = func();
val === this; // true

// in browser
val === window; // true

// in node
val === global; // true

A common trap with the function invocation is thinking that this is the same in an inner function as in the outer function. Correctly the context of the inner function depends only on invocation, but not on the outer function's context. To have the expected this, modify the inner function's context with indirect invocation (using .call() or .apply()) or create a bound function (using .bind()).

this для вложенной функции определяется самой этой функцией, только если она не стрелочная.

Method Invocation

Method invocation is performed when in a form of property accessor that evaluates to a function object. this is the object that owns the method in a method invocation.

const calc = {
  num: 0,
  increment() {
    console.log(this === calc); // => true
    this.num += 1;
    return this.num;
  }
};
// method invocation. this is calc
calc.increment(); // => 1
calc.increment(); // => 2

A method from an object can be extracted into a separated variable. When calling the method using this variable, you might think that this is the object on which the method was defined. Correctly if the method is called without an object, then a function invocation happens: where this is the global object window or undefined in strict mode. Creating a bound function (using .bind()) fixes the context, making it the object that owns the method.

Arrow function Invocation

Arrow-функции не имеют собственного this и оно для их определяется по правилам лексической области видимости. Простыми словами, Arrow функции заимствуют this из окружающей их области видимости.

Constructor Invocation

Constructor invocation is performed when new keyword is followed by an expression that evaluates to a function object. When a property accessor myObject.myFunction is preceded by new keyword, JavaScript will execute a constructor invocation, but not a method invocation.

In a constructor invocation this is the newly created object:

function Country(name, traveled) {
  this.name = name ? name : "United Kingdom";
  this.traveled = Boolean(traveled); // transform to a boolean
}
Country.prototype.travel = function() {
  this.traveled = true;
};
// Constructor invocation
const france = new Country("France", false);
// Constructor invocation
const unitedKingdom = new Country();

france.travel(); // Travel to France

Using a function invocation to create objects is a potential problem (excluding factory pattern), because some constructors may omit the logic to initialize the object when new keyword is missing.

function Vehicle(type, wheelsCount) {
  this.type = type;
  this.wheelsCount = wheelsCount;
  return this;
}
// Function invocation
const car = Vehicle("Car", 4);
car.type; // => 'Car'
car.wheelsCount; // => 4
car === window; // => true

Indirect Invocation

Indirect invocation is performed when a function is called using .call() or .apply() methods. this is the first argument of .call() or .apply() in an indirect invocation

  • The method .call(thisArg, arg1, arg2, ...) accepts the first argument thisArg as the context of the invocation and a list of arguments arg1, arg2, ... that are passed as arguments to the called function.

  • The method .apply(thisArg, [arg1, arg2, ...]) accepts the first argument thisArg as the context of the invocation and an array-like object of values [arg1, arg2, ...] that are passed as arguments to the called function.

function increment(number) {
  return ++number;
}
increment.call(undefined, 10); // => 11
increment.apply(undefined, [10]); // => 11

The spread operator (...) mostly replaces apply().

this propagation

Существует проблема, когда функция-callback, вызванная внутри другой функции имеет свой собственный this, который равен или глобальному объекту, или undefined (в строгом режиме).

В следующем примере this.name внутри функции выдаст ошибку, поскольку внутри анонимной функции this = undefined.

const obj = {
    name: 'Jane',
    friends: [ 'Tarzan', 'Cheeta' ],
    loop() {
'use strict';
        this.friends.forEach(function (friend) { // (1)
            console.log(this.name + ' knows ' + friend); // (2)
        });
    }
};

Существует четыре способа решения этой проблемы:

  1. Введение дополнительной временной переменной:

loop() {
    'use strict';
    const self = this;
    this.friends.forEach(function (friend) {
        console.log(self.name + ' knows ' + friend);
    });
}
  1. Использование bind для явного задания this:

loop() {
    'use strict';
    this.friends.forEach(function (friend) {
        console.log(this.name + ' knows ' + friend);
    }.bind(this));
}
  1. Передача this параметра в функцию forEach:

loop() {
    'use strict';
    this.friends.forEach(function (friend) {
        console.log(this.name + ' knows ' + friend);
    }, this);
}
  1. Использование arrow-functions в качестве функции обратного вызова (рекомендуемый):

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

Activation Object

При вызове функции создается объект активации, остающийся для вас невидимым. Это скрытая структура данных, которая содержит информацию и связывает все то, что функция должна выполнить, а также содержит обратный адрес активации вызывающей функции.

В языках, подобных C, объекты активации размещаются в стеке. Они покидают стек (или выводятся из него), когда функция возвращает управление. В JavaScript происходит иначе. Объекты активации JavaScript размещает в куче, как обычные объекты. При возвращении функцией управления объекты активации не проходят автоматическую деактивацию. Вместо этого объект активации может выживать, пока на него есть ссылка. Объекты активации подпадают под сборку мусора, как и обычные объекты.

В объекте активации содержатся:

  • ссылка на функциональный объект;

  • ссылка на объект активации вызывающей функции. Она используется инструкцией return для возврата управления;

  • информация о возвращении, которая применяется для продолжения вы- полнения кода после вызова. Обычно это адрес инструкции, выполняемой сразу же после вызова функции;

  • параметры функции, инициализированные аргументами;

  • переменные функции, инициализированные значением undefined;

  • временные переменные, используемые функциями для вычисления сложных выражений;

  • содержимое this, которое может быть ссылкой на интересующий объект, если функциональный объект был вызван как метод.

В функциональном объекте содержатся также два скрытых свойства:

  • ссылка на исполняемый код функции;

  • ссылка на объект активации, который был активен в момент создания функ- ционального объекта. Это делает возможным создание замыкания. Функция может использовать это скрытое свойство для доступа к переменным той функции, которая ее создала.

Last updated