This word in javascript

A function’s this keyword behaves a little differently in JavaScript compared to other languages. It also has some differences between strict mode and non-strict mode.

In most cases, the value of this is determined by how a function is called (runtime binding). It can’t be set by assignment during execution, and it may be different each time the function is called. The bind() method can set the value of a function’s this regardless of how it’s called, and arrow functions don’t provide their own this binding (it retains the this value of the enclosing lexical context).

Try it

Syntax

Value

In non–strict mode, this is always a reference to an object. In strict mode, it can be any value. For more information on how the value is determined, see the description below.

Description

The value of this depends on in which context it appears: function, class, or global.

Function context

Inside a function, the value of this depends on how the function is called. Think about this as a hidden parameter of a function — just like the parameters declared in the function definition, this is a binding that the language creates for you when the function body is evaluated.

For a typical function, the value of this is the object that the function is accessed on. In other words, if the function call is in the form obj.f(), then this refers to obj. For example:

function getThis() {
  return this;
}

const obj1 = { name: "obj1" };
const obj2 = { name: "obj2" };

obj1.getThis = getThis;
obj2.getThis = getThis;

console.log(obj1.getThis()); // { name: 'obj1', getThis: [Function: getThis] }
console.log(obj2.getThis()); // { name: 'obj2', getThis: [Function: getThis] }

Note how the function is the same, but based on how it’s invoked, the value of this is different. This is analogous to how function parameters work.

The value of this is not the object that has the function as an own property, but the object that is used to call the function. You can prove this by calling a method of an object up in the prototype chain.

const obj3 = {
  __proto__: obj1,
  name: "obj3",
};

console.log(obj3.getThis()); // { name: 'obj3' }

The value of this always changes based on how a function is called, even when the function was defined on an object at creation:

const obj4 = {
  name: "obj4",
  getThis() {
    return this;
  },
};

const obj5 = { name: "obj5" };

obj5.getThis = obj4.getThis;
console.log(obj5.getThis()); // { name: 'obj5', getThis: [Function: getThis] }

If the value that the method is accessed on is a primitive, this will be a primitive value as well — but only if the function is in strict mode.

function getThisStrict() {
  "use strict"; // Enter strict mode
  return this;
}

// Only for demonstration — you should not mutate built-in prototypes
Number.prototype.getThisStrict = getThisStrict;
console.log(typeof (1).getThisStrict()); // "number"

If the function is called without being accessed on anything, this will be undefined — but only if the function is in strict mode.

console.log(typeof getThisStrict()); // "undefined"

In non-strict mode, a special process called this substitution ensures that the value of this is always an object. This means:

  • If a function is called with this set to undefined or null, this gets substituted with globalThis.
  • If the function is called with this set to a primitive value, this gets substituted with the primitive value’s wrapper object.
function getThis() {
  return this;
}

// Only for demonstration — you should not mutate built-in prototypes
Number.prototype.getThis = getThis;
console.log(typeof (1).getThis()); // "object"
console.log(getThis() === globalThis); // true

In typical function calls, this is implicitly passed like a parameter through the function’s prefix (the part before the dot). You can also explicitly set the value of this using the Function.prototype.call(), Function.prototype.apply(), or Reflect.apply() methods. Using Function.prototype.bind(), you can create a new function with a specific value of this that doesn’t change regardless of how the function is called. When using these methods, the this substitution rules above still apply if the function is non-strict.

Callbacks

When a function is passed as a callback, the value of this depends on how the callback is called, which is determined by the implementor of the API. Callbacks are typically called with a this value of undefined (calling it directly without attaching it to any object), which means if the function is non–strict, the value of this is the global object (globalThis). This is the case for iterative array methods, the Promise() constructor, etc.

function logThis() {
  "use strict";
  console.log(this);
}

[1, 2, 3].forEach(logThis); // undefined, undefined, undefined

Some APIs allow you to set a this value for invocations of the callback. For example, all iterative array methods and related ones like Set.prototype.forEach() accept an optional thisArg parameter.

[1, 2, 3].forEach(logThis, { name: "obj" });
// { name: 'obj' }, { name: 'obj' }, { name: 'obj' }

Occasionally, a callback is called with a this value other than undefined. For example, the reviver parameter of JSON.parse() and the replacer parameter of JSON.stringify() are both called with this set to the object that the property being parsed/serialized belongs to.

Arrow functions

In arrow functions, this retains the value of the enclosing lexical context’s this. In other words, when evaluating an arrow function’s body, the language does not create a new this binding.

For example, in global code, this is always globalThis regardless of strictness, because of the global context binding:

const globalObject = this;
const foo = () => this;
console.log(foo() === globalObject); // true

Arrow functions create a closure over the this value of its surrounding scope, which means arrow functions behave as if they are «auto-bound» — no matter how it’s invoked, this is set to what it was when the function was created (in the example above, the global object). The same applies to arrow functions created inside other functions: their this remains that of the enclosing lexical context. See example below.

Furthermore, when invoking arrow functions using call(), bind(), or apply(), the thisArg parameter is ignored. You can still pass other arguments using these methods, though.

const obj = { name: "obj" };

// Attempt to set this using call
console.log(foo.call(obj) === globalObject); // true

// Attempt to set this using bind
const boundFoo = foo.bind(obj);
console.log(boundFoo() === globalObject); // true

Constructors

When a function is used as a constructor (with the new keyword), its this is bound to the new object being constructed, no matter which object the constructor function is accessed on. The value of this becomes the value of the new expression unless the constructor returns another non–primitive value.

function C() {
  this.a = 37;
}

let o = new C();
console.log(o.a); // 37

function C2() {
  this.a = 37;
  return { a: 38 };
}

o = new C2();
console.log(o.a); // 38

In the second example (C2), because an object was returned during construction, the new object that this was bound to gets discarded. (This essentially makes the statement this.a = 37; dead code. It’s not exactly dead because it gets executed, but it can be eliminated with no outside effects.)

super

When a function is invoked in the super.method() form, the this inside the method function is the same value as the this value around the super.method() call, and is generally not equal to the object that super refers to. This is because super.method is not an object member access like the ones above — it’s a special syntax with different binding rules. For examples, see the super reference.

Class context

A class can be split into two contexts: static and instance. Constructors, methods, and instance field initializers (public or private) belong to the instance context. Static methods, static field initializers, and static initialization blocks belong to the static context. The this value is different in each context.

Class constructors are always called with new, so their behavior is the same as function constructors: the this value is the new instance being created. Class methods behave like methods in object literals — the this value is the object that the method was accessed on. If the method is not transferred to another object, this is generally an instance of the class.

Static methods are not properties of this. They are properties of the class itself. Therefore, they are generally accessed on the class, and this is the value of the class (or a subclass). Static initialization blocks are also evaluated with this set to the current class.

Field initializers are also evaluated in the context of the class. Instance fields are evaluated with this set to the instance being constructed. Static fields are evaluated with this set to the current class. This is why arrow functions in field initializers are bound to the class.

class C {
  instanceField = this;
  static staticField = this;
}

const c = new C();
console.log(c.instanceField === c); // true
console.log(C.staticField === C); // true

Derived class constructors

Unlike base class constructors, derived constructors have no initial this binding. Calling super() creates a this binding within the constructor and essentially has the effect of evaluating the following line of code, where Base is the base class:

Warning: Referring to this before calling super() will throw an error.

Derived classes must not return before calling super(), unless the constructor returns an object (so the this value is overridden) or the class has no constructor at all.

class Base {}
class Good extends Base {}
class AlsoGood extends Base {
  constructor() {
    return { a: 5 };
  }
}
class Bad extends Base {
  constructor() {}
}

new Good();
new AlsoGood();
new Bad(); // ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor

Global context

In the global execution context (outside of any functions or classes; may be inside blocks or arrow functions defined in the global scope), the this value depends on what execution context the script runs in. Like callbacks, the this value is determined by the runtime environment (the caller).

At the top level of a script, this refers to globalThis whether in strict mode or not. This is generally the same as the global object — for example, if the source is put inside an HTML <script> element and executed as a script, this === window.

Note: globalThis is generally the same concept as the global object (i.e. adding properties to globalThis makes them global variables) — this is the case for browsers and Node — but hosts are allowed to provide a different value for globalThis that’s unrelated to the global object.

// In web browsers, the window object is also the global object:
console.log(this === window); // true

this.b = "MDN";
console.log(window.b); // "MDN"
console.log(b); // "MDN"

If the source is loaded as a module (for HTML, this means adding type="module" to the <script> tag), this is always undefined at the top level.

If the source is executed with eval(), this is the same as the enclosing context for direct eval, or globalThis (as if it’s run in a separate global script) for indirect eval.

function test() {
  // Direct eval
  console.log(eval("this") === this);
  // Indirect eval, non-strict
  console.log(eval?.("this") === globalThis);
  // Indirect eval, strict
  console.log(eval?.("'use strict'; this") === globalThis);
}

test.call({ name: "obj" }); // Logs 3 "true"

Note that some source code, while looking like the global scope, is actually wrapped in a function when executed. For example, Node.js CommonJS modules are wrapped in a function and executed with the this value set to module.exports. Event handler attributes are executed with this set to the element they are attached to.

Object literals don’t create a this scope — only functions (methods) defined within the object do. Using this in an object literal inherits the value from the surrounding scope.

const obj = {
  a: this,
};

console.log(obj.a === window); // true

Examples

this in function contexts

The value of this depends on how the function is called, not how it’s defined.

// An object can be passed as the first argument to call
// or apply and this will be bound to it.
const obj = { a: "Custom" };

// Variables declared with var become properties of the global object.
var a = "Global";

function whatsThis() {
  return this.a; // The value of this is dependent on how the function is called
}

whatsThis(); // 'Global'; this in the function isn't set, so it defaults to the global/window object in non–strict mode
obj.whatsThis = whatsThis;
obj.whatsThis(); // 'Custom'; this in the function is set to obj

Using call() and apply(), you can pass the value of this as if it’s an actual parameter.

function add(c, d) {
  return this.a + this.b + c + d;
}

const o = { a: 1, b: 3 };

// The first parameter is the object to use as 'this'; subsequent
// parameters are used as arguments in the function call
add.call(o, 5, 7); // 16

// The first parameter is the object to use as 'this', the second is an
// array whose members are used as arguments in the function call
add.apply(o, [10, 20]); // 34

this and object conversion

In non–strict mode, if a function is called with a this value that’s not an object, the this value is substituted with an object. null and undefined become globalThis. Primitives like 7 or 'foo' are converted to an object using the related constructor, so the primitive number 7 is converted to a Number wrapper class and the string 'foo' to a String wrapper class.

function bar() {
  console.log(Object.prototype.toString.call(this));
}

bar.call(7); // [object Number]
bar.call("foo"); // [object String]
bar.call(undefined); // [object Window]

The bind() method

Calling f.bind(someObject) creates a new function with the same body and scope as f, but the value of this is permanently bound to the first argument of bind, regardless of how the function is being called.

function f() {
  return this.a;
}

const g = f.bind({ a: "azerty" });
console.log(g()); // azerty

const h = g.bind({ a: "yoo" }); // bind only works once!
console.log(h()); // azerty

const o = { a: 37, f, g, h };
console.log(o.a, o.f(), o.g(), o.h()); // 37,37, azerty, azerty

this in arrow functions

Arrow functions create closures over the this value of the enclosing execution context. In the following example, we create obj with a method getThisGetter that returns a function that returns the value of this. The returned function is created as an arrow function, so its this is permanently bound to the this of its enclosing function. The value of this inside getThisGetter can be set in the call, which in turn sets the return value of the returned function.

const obj = {
  getThisGetter() {
    const getter = () => this;
    return getter;
  },
};

We can call getThisGetter as a method of obj, which sets this inside the body to obj. The returned function is assigned to a variable fn. Now, when calling fn, the value of this returned is still the one set by the call to getThisGetter, which is obj. If the returned function is not an arrow function, such calls would cause the this value to be globalThis or undefined in strict mode.

const fn = obj.getThisGetter();
console.log(fn() === obj); // true

But be careful if you unbind the method of obj without calling it, because getThisGetter is still a method that has a varying this value. Calling fn2()() in the following example returns globalThis, because it follows the this from fn2, which is globalThis since it’s called without being attached to any object.

const fn2 = obj.getThisGetter;
console.log(fn2()() === globalThis); // true

This behavior is very useful when defining callbacks. Usually, each function expression creates its own this binding, which shadows the this value of the upper scope. Now, you can define functions as arrow functions if you don’t care about the this value, and only create this bindings where you do (e.g. in class methods). See example with setTimeout().

this with a getter or setter

this in getters and setters is based on which object the property is accessed on, not which object the property is defined on. A function used as getter or setter has its this bound to the object from which the property is being set or gotten.

function sum() {
  return this.a + this.b + this.c;
}

const o = {
  a: 1,
  b: 2,
  c: 3,
  get average() {
    return (this.a + this.b + this.c) / 3;
  },
};

Object.defineProperty(o, "sum", {
  get: sum,
  enumerable: true,
  configurable: true,
});

console.log(o.average, o.sum); // 2, 6

As a DOM event handler

When a function is used as an event handler, its this is set to the element on which the listener is placed (some browsers do not follow this convention for listeners added dynamically with methods other than addEventListener()).

// When called as a listener, turns the related element blue
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";
}

// Get a list of every element in the document
const elements = document.getElementsByTagName("*");

// Add bluify as a click listener so when the
// element is clicked on, it turns blue
for (const element of elements) {
  element.addEventListener("click", bluify, false);
}

this in inline event handlers

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

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

The above alert shows button. Note, however, that only the outer code has its this set this way:

<button onclick="alert((function () { return this; })());">
  Show inner this
</button>

In this case, the inner function’s this isn’t set, so it returns the global/window object (i.e. the default object in non–strict mode where this isn’t set by the call).

Bound methods in classes

Just like with regular functions, the value of this within methods depends on how they are called. Sometimes it is useful to override this behavior so that this within classes always refers to the class instance. To achieve this, bind the class methods in the constructor:

class Car {
  constructor() {
    // Bind sayBye but not sayHi to show the difference
    this.sayBye = this.sayBye.bind(this);
  }
  sayHi() {
    console.log(`Hello from ${this.name}`);
  }
  sayBye() {
    console.log(`Bye from ${this.name}`);
  }
  get name() {
    return "Ferrari";
  }
}

class Bird {
  get name() {
    return "Tweety";
  }
}

const car = new Car();
const bird = new Bird();

// The value of 'this' in methods depends on their caller
car.sayHi(); // Hello from Ferrari
bird.sayHi = car.sayHi;
bird.sayHi(); // Hello from Tweety

// For bound methods, 'this' doesn't depend on the caller
bird.sayBye = car.sayBye;
bird.sayBye(); // Bye from Ferrari

Note: Classes are always in strict mode. Calling methods with an undefined this will throw an error if the method tries to access properties on this.

Note, however, that auto-bound methods suffer from the same problem as using arrow functions for class properties: each instance of the class will have its own copy of the method, which increases memory usage. Only use it where absolutely necessary. You can also mimic the implementation of Intl.NumberFormat.prototype.format(): define the property as a getter that returns a bound function when accessed and saves it, so that the function is only created once and only created when necessary.

this in with statements

Although with statements are deprecated and not available in strict mode, they still serve as an exception to the normal this binding rules. If a function is called within a with statement and that function is a property of the scope object, the this value is set to the scope object, as if the obj1. prefix exists.

const obj1 = {
  foo() {
    return this;
  },
};

with (obj1) {
  console.log(foo() === obj1); // true
}

Specifications

Specification
ECMAScript Language Specification
# sec-this-keyword

Browser compatibility

BCD tables only load in the browser

See also

Время на прочтение
7 мин

Количество просмотров 131K

Автор материала, перевод которого мы сегодня публикуем, говорит, что когда она работала в сфере бухучёта, там применялись понятные термины, значения которых легко найти в словаре. А вот занявшись программированием, и, в частности, JavaScript, она начала сталкиваться с такими понятиями, определения которых в словарях уже не найти. Например, это касается ключевого слова this. Она вспоминает то время, когда познакомилась с JS-объектами и функциями-конструкторами, в которых использовалось это ключевое слово, но добраться до его точного смысла оказалось не так уж и просто. Она полагает, что подобные проблемы встают и перед другими новичками, особенно перед теми, кто раньше программированием не занимался. Тем, кто хочет изучить JavaScript, в любом случае придётся разобраться с this. Этот материал направлен на то, чтобы всем желающим в этом помочь.

Что такое this?

Предлагаю вашему вниманию моё собственное определение ключевого слова this. This — это ключевое слово, используемое в JavaScript, которое имеет особое значение, зависящее от контекста в котором оно применяется.

Причина, по которой this вызывает столько путаницы у новичков, заключается в том, что контекст this меняется в зависимости от его использования.

This можно считать динамическим ключевым словом. Мне нравится, как понятие «контекст» раскрыто в этой статье Райана Морра. По его словам, контекст всегда является значением ключевого слова this, которое ссылается на объект, «владеющий» кодом, выполняемым в текущий момент. Однако, тот контекст, который имеет отношение к this, это не то же самое, что контекст выполнения.

Итак, когда мы пользуемся ключевым словом this, мы, на самом деле, обращаемся с его помощью к некоему объекту. Поговорим о том, что это за объект, рассмотрев несколько примеров.

Ситуации, когда this указывает на объект window

Если вы попытаетесь обратиться к ключевому слову this в глобальной области видимости, оно будет привязано к глобальному контексту, то есть — к объекту window в браузере.

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

Попробуйте выполнить этот код, например, в консоли браузера:

console.log(this);

// в консоль выводится объект Window
// Window { postMessage: ƒ, 
// blur: ƒ, 
// focus: ƒ, 
// close: ƒ, 
// frames: Window, …}

function myFunction() {
  console.log(this);
}

// Вызовем функцию
myFunction(); 

// функция выводит тот же объект Window! 
// Window { postMessage: ƒ, 
// blur: ƒ, 
// focus: ƒ, 
// close: ƒ, 
// frames: Window, …}

Использование this внутри объекта

Когда this используется внутри объекта, это ключевое слово ссылается на сам объект. Рассмотрим пример. Предположим, вы создали объект dog с методами и обратились в одном из его методов к this. Когда this используется внутри этого метода, это ключевое слово олицетворяет объект dog.

var dog = {
  name: 'Chester',
  breed: 'beagle',
  intro: function(){
    console.log(this);
  }
};

dog.intro();

// в консоль выводится представление объекта dog со всеми его свойствами и методами
// {name: "Chester", breed: "beagle", intro: ƒ}
//    breed:"beagle"
//    intro:ƒ ()
//    name:"Chester"
//    __proto__:Object

This и вложенные объекты

Применение this во вложенных объектах может создать некоторую путаницу. В подобных ситуациях стоит помнить о том, что ключевое слово this относиться к тому объекту, в методе которого оно используется. Рассмотрим пример.

var obj1 = {
  hello: function() {
    console.log('Hello world');
    return this;
  },
  obj2: {
      breed: 'dog',
      speak: function(){
            console.log('woof!');
            return this;
        }
    }
};
 
console.log(obj1);
console.log(obj1.hello());  // выводит 'Hello world' и возвращает obj1
console.log(obj1.obj2);
console.log(obj1.obj2.speak());  // выводит 'woof!' и возвращает obj2

Особенности стрелочных функций

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

var objReg = {
  hello: function() {
    return this;
  }
};
 
var objArrow = {
    hello: () => this
};
 
objReg.hello(); // возвращает, как и ожидается, объект objReg 
objArrow.hello(); // возвращает объект Window!

Если, озадачившись рассматриваемым вопросом, заглянуть на MDN, там можно найти сведения о том, что стрелочные функции имеют более короткую форму записи, чем функциональные выражения и не привязаны к собственным сущностям this, arguments, super или new.target. Стрелочные функции лучше всего подходят для использования их в роли обычных функций, а не методов объектов, их нельзя использовать в роли конструкторов.

Прислушаемся к MDN и не будем использовать стрелочные функции в качестве методов объектов.

Использование this в обычных функциях

Когда обычная функция находится в глобальной области видимости, то ключевое слово this, использованное в ней, будет привязано к объекту window. Ниже приведён пример, в котором функцию test можно рассматривать в виде метода объекта window.

function test() {
  console.log('hello world');
  console.log(this);
}

test();

// hello world
// Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}

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

function test() {
  'use strict';
  return this;
}

console.log( test() );
//функция возвращает undefined, а не объект Window

Обращение к this из функции, которая была объявлена за пределами объекта, а потом назначена в качестве его метода

Рассмотрим пример с уже известным нам объектом dog. В качестве метода этого объекта можно назначить функцию chase, объявленную за его пределами. Тут в объекте dog никаких методов не было, до тех пор, пока мы не создали метод foo, которому назначена функция chase. Если теперь вызвать метод dog.foo, то будет вызвана функция chase. При этом ключевое слово this, к которому обращаются в этой функции, указывает на объект dog. А функция chase, при попытке её вызова как самостоятельной функции, будет вести себя неправильно, так как при таком подходе this будет указывать на глобальный объект, в котором нет тех свойств, к которым мы, в этой функции, обращаемся через this.

var dog = {
  breed: 'Beagles',
  lovesToChase: 'rabbits'
};

function chase() {
  console.log(this.breed + ' loves chasing ' + this.lovesToChase + '.'); 
}

dog.foo = chase;
dog.foo(); // в консоль попадёт Beagles loves chasing rabbits.

chase(); //так эту функцию лучше не вызывать

Ключевое слово new и this

Ключевое слово this находит применение в функциях-конструкторах, используемых для создания объектов, так как оно позволяет, универсальным образом, работать со множеством объектов, создаваемых с помощью такой функции. В JavaScript есть и стандартные функции-конструкторы, с помощью которых, например, можно создавать объекты типа Number или String. Подобные функции, определяемые программистом самостоятельно, позволяют ему создавать объекты, состав свойств и методов которых задаётся им самим.

Как вы уже поняли, мне нравятся собаки, поэтому опишем функцию-конструктор для создания объектов типа Dog, содержащих некоторые свойства и методы.

function Dog(breed, name, friends){
    this.breed = breed;
    this.name = name;
    this.friends = friends;	
    this.intro = function() {
        console.log(`Hi, my name is ${this.name} and I’m a ${this.breed}`);
        return this;
    }; 
}

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

Вот как можно работать со стандартными конструкторами JavaScript.

var str = new String('Hello world');
/*******
Строки можно создавать так, но лучше этого не делать, используя подход, применённый при объявлении переменной str2 ниже. Одна из причин подобной рекомендации заключается в том, что в JavaScript строки удобно создавать, пользуясь строковыми литералами, когда строкой считается всё, включённое в двойные или одинарные кавычки. То же самое касается и других примитивных значений. Стоит отметить, что мне, на практике, не встречалась ситуация, когда надо было бы использовать конструкторы для создания значений примитивных типов.
*******/

var str2 = 'Hello world';
// когда строка объявлена так, система, всё равно, позволяет работать с ней как с объектом

Теперь поработаем с только что созданной функцией-конструктором Dog.

// Создадим новый экземпляр объекта типа Dog
var chester = new Dog('beagle', 'Chester', ['Gracie', 'Josey', 'Barkley']);
chester.intro();        // выводит Hi, my name is Chester and I'm a beagle
console.log(chester);   // выводит Dog {breed: "beagle", name: "Chester", friends: Array(3), intro: ƒ}

Вот ещё один пример использования функций-конструкторов.

var City = function(city, state) {
  this.city = city || "Phoenix";
  this.state = state || "AZ";
  this.sentence = function() {
    console.log(`I live in ${this.city}, ${this.state}.`);
  };
};

var phoenix = new City(); // используем параметры по умолчанию
console.log(phoenix); // выводит в консоль строковое представление объекта
phoenix.sentence(); // выводит I live in Phoenix, AZ.

var spokane = new City('Spokane', 'WA');
console.log(spokane); // выводит сам объект
spokane.sentence(); // выводит I live in Spokane, WA.

О важности ключевого слова new

При вызове функции-конструктора с использованием ключевого слова new ключевое слово this указывает на новый объект, который, после некоторой работы над ним, будет возвращён из этой функции. Ключевое слово this в данной ситуации весьма важно. Почему? Всё дело в том, что с его помощью можно, используя единственную функцию-конструктор, создавать множество однотипных объектов.

Это позволяет нам масштабировать приложение и сокращать дублирование кода. Для того чтобы понять важность этого механизма, подумайте о том, как устроены учётные записи в социальных сетях. Каждая учётная запись может представлять собой экземпляр объекта, создаваемый с помощью функции-конструктора Friend. Каждый такой объект можно заполнять уникальными данными о пользователе. Рассмотрим следующий код.

// Функция-конструктор
var Friend = function(name, password, interests, job){
  this.fullName = name;
  this.password = password;
  this.interests = interests;
  this.job = job;
};

function sayHello(){
   // раскомментируйте следующую строчку, чтобы узнать, на что указывает this
   // console.log(this); 
  return `Hi, my name is ${this.fullName} and I'm a ${this.job}. Let's be friends!`;
}

// Мы можем создать один или несколько экземпляров объекта типа Friend, используя ключевое слово new
var john = new Friend('John Smith', 'badpassword', ['hiking', 'biking', 'skiing'], 'teacher'); 

console.log(john); 

// Назначим функцию ключу greeting объекта john
john.greeting = sayHello; 

// Вызовем новый метод объекта
console.log( john.greeting() ); 

// Помните о том, что sayHello() не стоит вызывать как обычную функцию
console.log( sayHello() ) ;

Итоги

На самом деле, особенности использования ключевого слова this в JavaScript не ограничиваются вышеописанными примерами. Так, в череду этих примеров можно было бы включить использование функций call, apply и bind. Так как материал этот рассчитан на начинающих и ориентирован на разъяснение основ, мы их здесь не касаемся. Однако если сейчас у вас сформировалось начальное понимание this, то и с этими методами вы вполне сможете разобраться. Главное — помните о том, что если что-то с первого раза понять не удаётся, не прекращайте учиться, практикуйтесь, читайте материалы по интересующей вас теме. В одном из них вам обязательно попадётся нечто такое (какая-то удачная фраза, например), что поможет понять то, что раньше понять не удавалось.

Уважаемые читатели! Возникали ли у вас сложности с пониманием ключевого слова this в JavaScript?

this is a keyword in JavaScript that is a property of an execution context. Its main use is in functions and constructors.
The rules for this are quite simple (if you stick to best practices).

Technical description of this in the specification

The ECMAScript standard defines this via the abstract operation (abbreviated AO) ResolveThisBinding:

The [AO] ResolveThisBinding […] determines the binding of the keyword this using the LexicalEnvironment of the running execution context. [Steps]:

  1. Let envRec be GetThisEnvironment().
  2. Return ? envRec.GetThisBinding().

Global Environment Records, module Environment Records, and function Environment Records each have their own GetThisBinding method.

The GetThisEnvironment AO finds the current running execution context’s LexicalEnvironment and finds the closest ascendant Environment Record (by iteratively accessing their [[OuterEnv]] properties) which has a this binding (i.e. HasThisBinding returns true). This process ends in one of the three Environment Record types.

The value of this often depends on whether code is in strict mode.

The return value of GetThisBinding reflects the value of this of the current execution context, so whenever a new execution context is established, this resolves to a distinct value. This can also happen when the current execution context is modified. The following subsections list the five cases where this can happen.

You can put the code samples in the AST explorer to follow along with specification details.

1. Global execution context in scripts

This is script code evaluated at the top level, e.g. directly inside a <script>:

<script>
// Global context
console.log(this); // Logs global object.

setTimeout(function(){
  console.log("Not global context");
});
</script>

When in the initial global execution context of a script, evaluating this causes GetThisBinding to take the following steps:

The GetThisBinding concrete method of a global Environment Record envRec […] [does this]:

  1. Return envRec.[[GlobalThisValue]].

The [[GlobalThisValue]] property of a global Environment Record is always set to the host-defined global object, which is reachable via globalThis (window on Web, global on Node.js; Docs on MDN). Follow the steps of InitializeHostDefinedRealm to learn how the [[GlobalThisValue]] property comes to be.

2. Global execution context in modules

Modules have been introduced in ECMAScript 2015.

This applies to modules, e.g. when directly inside a <script type="module">, as opposed to a simple <script>.

When in the initial global execution context of a module, evaluating this causes GetThisBinding to take the following steps:

The GetThisBinding concrete method of a module Environment Record […] [does this]:

  1. Return undefined.

In modules, the value of this is always undefined in the global context. Modules are implicitly in strict mode.

3. Entering eval code

There are two kinds of eval calls: direct and indirect. This distinction exists since the ECMAScript 5th edition.

  • A direct eval call usually looks like eval(); or (eval)(); (or ((eval))();, etc.).1 It’s only direct if the call expression fits a narrow pattern.2
  • An indirect eval call involves calling the function reference eval in any other way. It could be eval?.(), (, eval)(), window.eval(), eval.call(,), etc. Given const aliasEval1 = eval; window.aliasEval2 = eval;, it would also be aliasEval1(), aliasEval2(). Separately, given const originalEval = eval; window.eval = (x) => originalEval(x);, calling eval() would also be indirect.

See chuckj’s answer to “(1, eval)(‘this’) vs eval(‘this’) in JavaScript?” and Dmitry Soshnikov’s ECMA-262-5 in detail – Chapter 2: Strict Mode (archived) for when you might use an indirect eval() call.

PerformEval executes the eval code. It creates a new declarative Environment Record as its LexicalEnvironment, which is where GetThisEnvironment gets the this value from.

Then, if this appears in eval code, the GetThisBinding method of the Environment Record found by GetThisEnvironment is called and its value returned.

And the created declarative Environment Record depends on whether the eval call was direct or indirect:

  • In a direct eval, it will be based on the current running execution context’s LexicalEnvironment.
  • In an indirect eval, it will be based on the [[GlobalEnv]] property (a global Environment Record) of the Realm Record which executed the indirect eval.

Which means:

  • In a direct eval, the this value doesn’t change; it’s taken from the lexical scope that called eval.
  • In an indirect eval, the this value is the global object (globalThis).

What about new Function? — new Function is similar to eval, but it doesn’t call the code immediately; it creates a function. A this binding doesn’t apply anywhere here, except when the function is called, which works normally, as explained in the next subsection.

4. Entering function code

Entering function code occurs when calling a function.

There are four categories of syntax to invoke a function.

  • The EvaluateCall AO is performed for these three:3
    • Normal function calls
    • Optional chaining calls
    • Tagged templates
  • And EvaluateNew is performed for this one:3
    • Constructor invocations

The actual function call happens at the Call AO, which is called with a thisValue determined from context; this argument is passed along in a long chain of call-related calls. Call calls the [[Call]] internal slot of the function. This calls PrepareForOrdinaryCall where a new function Environment Record is created:

A function Environment Record is a declarative Environment Record that is used to represent the top-level scope of a function and, if the function is not an ArrowFunction, provides a this binding. If a function is not an ArrowFunction function and references super, its function Environment Record also contains the state that is used to perform super method invocations from within the function.

In addition, there is the [[ThisValue]] field in a function Environment Record:

This is the this value used for this invocation of the function.

The NewFunctionEnvironment call also sets the function environment’s [[ThisBindingStatus]] property.

[[Call]] also calls OrdinaryCallBindThis, where the appropriate thisArgument is determined based on:

  • the original reference,
  • the kind of the function, and
  • whether or not the code is in strict mode.

Once determined, a final call to the BindThisValue method of the newly created function Environment Record actually sets the [[ThisValue]] field to the thisArgument.

Finally, this very field is where a function Environment Record’s GetThisBinding AO gets the value for this from:

The GetThisBinding concrete method of a function Environment Record envRec […] [does this]:

[…]
3. Return envRec.[[ThisValue]].

Again, how exactly the this value is determined depends on many factors; this was just a general overview. With this technical background, let’s examine all the concrete examples.

Arrow functions

When an arrow function is evaluated, the [[ThisMode]] internal slot of the function object is set to “lexical” in OrdinaryFunctionCreate.

At OrdinaryCallBindThis, which takes a function F:

  1. Let thisMode be F.[[ThisMode]].
  2. If thisMode is lexical, return NormalCompletion(undefined).
    […]

which just means that the rest of the algorithm which binds this is skipped. An arrow function does not bind its own this value.

So, what is this inside an arrow function, then? Looking back at ResolveThisBinding and GetThisEnvironment, the HasThisBinding method explicitly returns false.

The HasThisBinding concrete method of a function Environment Record envRec […] [does this]:

  1. If envRec.[[ThisBindingStatus]] is lexical, return false; otherwise, return true.

So the outer environment is looked up instead, iteratively. The process will end in one of the three environments that have a this binding.

This just means that, in arrow function bodies, this comes from the lexical scope of the arrow function, or in other words (from Arrow function vs function declaration / expressions: Are they equivalent / exchangeable?):

Arrow functions don’t have their own this […] binding. Instead, [this identifier is] resolved in the lexical scope like any other variable. That means that inside an arrow function, this [refers] to the [value of this] in the environment the arrow function is defined in (i.e. “outside” the arrow function).

Function properties

In normal functions (function, methods), this is determined by how the function is called.

This is where these “syntax variants” come in handy.

Consider this object containing a function:

const refObj = {
    func: function(){
      console.log(this);
    }
  };

Alternatively:

const refObj = {
    func(){
      console.log(this);
    }
  };

In any of the following function calls, the this value inside func will be refObj.1

  • refObj.func()
  • refObj["func"]()
  • refObj?.func()
  • refObj.func?.()
  • refObj.func``

If the called function is syntactically a property of a base object, then this base will be the “reference” of the call, which, in usual cases, will be the value of this. This is explained by the evaluation steps linked above; for example, in refObj.func() (or refObj["func"]()), the CallMemberExpression is the entire expression refObj.func(), which consists of the MemberExpression refObj.func and the Arguments ().

But also, refObj.func and refObj play three roles, each:

  • they’re both expressions,
  • they’re both references, and
  • they’re both values.

refObj.func as a value is the callable function object; the corresponding reference is used to determine the this binding.

The optional chaining and tagged template examples work very similarly: basically, the reference is everything before the ?.(), before the ``, or before the ().

EvaluateCall uses IsPropertyReference of that reference to determine if it is a property of an object, syntactically. It’s trying to get the [[Base]] property of the reference (which is e.g. refObj, when applied to refObj.func; or foo.bar when applied to foo.bar.baz). If it is written as a property, then GetThisValue will get this [[Base]] property and use it as the this value.

Note: Getters / Setters work the same way as methods, regarding this. Simple properties don’t affect the execution context, e.g. here, this is in global scope:

const o = {
    a: 1,
    b: this.a, // Is `globalThis.a`.
    [this.a]: 2 // Refers to `globalThis.a`.
  };

Calls without base reference, strict mode, and with

A call without a base reference is usually a function that isn’t called as a property. For example:

func(); // As opposed to `refObj.func();`.

This also happens when passing or assigning methods, or using the comma operator. This is where the difference between Reference Record and Value is relevant.

Note function j: following the specification, you will notice that j can only return the function object (Value) itself, but not a Reference Record. Therefore the base reference refObj is lost.

const g = (f) => f(); // No base ref.
const h = refObj.func;
const j = () => refObj.func;

g(refObj.func);
h(); // No base ref.
j()(); // No base ref.
(0, refObj.func)(); // Another common pattern to remove the base ref.

EvaluateCall calls Call with a thisValue of undefined here. This makes a difference in OrdinaryCallBindThis (F: the function object; thisArgument: the thisValue passed to Call):

  1. Let thisMode be F.[[ThisMode]].

[…]

  1. If thisMode is strict, let thisValue be thisArgument.
  2. Else,
    1. If thisArgument is undefined or null, then
      1. Let globalEnv be calleeRealm.[[GlobalEnv]].
      2. […]
      3. Let thisValue be globalEnv.[[GlobalThisValue]].
    2. Else,
      1. Let thisValue be ! ToObject(thisArgument).
      2. NOTE: ToObject produces wrapper objects […].

[…]

Note: step 5 sets the actual value of this to the supplied thisArgument in strict mode — undefined in this case. In “sloppy mode”, an undefined or null thisArgument results in this being the global this value.

If IsPropertyReference returns false, then EvaluateCall takes these steps:

  1. Let refEnv be ref.[[Base]].
  2. Assert: refEnv is an Environment Record.
  3. Let thisValue be refEnv.WithBaseObject().

This is where an undefined thisValue may come from: refEnv.WithBaseObject() is always undefined, except in with statements. In this case, thisValue will be the binding object.

There’s also Symbol.unscopables (Docs on MDN) to control the with binding behavior.

To summarize, so far:

function f1(){
  console.log(this);
}

function f2(){
  console.log(this);
}

function f3(){
  console.log(this);
}

const o = {
    f1,
    f2,
    [Symbol.unscopables]: {
      f2: true
    }
  };

f1(); // Logs `globalThis`.

with(o){
  f1(); // Logs `o`.
  f2(); // `f2` is unscopable, so this logs `globalThis`.
  f3(); // `f3` is not on `o`, so this logs `globalThis`.
}

and:

"use strict";

function f(){
  console.log(this);
}

f(); // Logs `undefined`.

// `with` statements are not allowed in strict-mode code.

Note that when evaluating this, it doesn’t matter where a normal function is defined.

.call, .apply, .bind, thisArg, and primitives

Another consequence of step 5 of OrdinaryCallBindThis, in conjunction with step 6.2 (6.b in the spec), is that a primitive this value is coerced to an object only in “sloppy” mode.

To examine this, let’s introduce another source for the this value: the three methods that override the this binding:4

  • Function.prototype.apply(thisArg, argArray)
  • Function.prototype. {call, bind} (thisArg, ...args)

.bind creates a bound function, whose this binding is set to thisArg and cannot change again. .call and .apply call the function immediately, with the this binding set to thisArg.

.call and .apply map directly to Call, using the specified thisArg. .bind creates a bound function with BoundFunctionCreate. These have their own [[Call]] method which looks up the function object’s [[BoundThis]] internal slot.

Examples of setting a custom this value:

function f(){
  console.log(this);
}

const myObj = {},
  g = f.bind(myObj),
  h = (m) => m();

// All of these log `myObj`.
g();
f.bind(myObj)();
f.call(myObj);
h(g);

For objects, this is the same in strict and non-strict mode.

Now, try to supply a primitive value:

function f(){
  console.log(this);
}

const myString = "s",
  g = f.bind(myString);

g();              // Logs `String { "s" }`.
f.call(myString); // Logs `String { "s" }`.

In non-strict mode, primitives are coerced to their object-wrapped form. It’s the same kind of object you get when calling Object("s") or new String("s"). In strict mode, you can use primitives:

"use strict";

function f(){
  console.log(this);
}

const myString = "s",
  g = f.bind(myString);

g();              // Logs `"s"`.
f.call(myString); // Logs `"s"`.

Libraries make use of these methods, e.g. jQuery sets the this to the DOM element selected here:

$("button").click(function(){
  console.log(this); // Logs the clicked button.
});

Constructors, classes, and new

When calling a function as a constructor using the new operator, EvaluateNew calls Construct, which calls the [[Construct]] method. If the function is a base constructor (i.e. not a class extends{}), it sets thisArgument to a new object created from the constructor’s prototype. Properties set on this in the constructor will end up on the resulting instance object. this is implicitly returned, unless you explicitly return your own non-primitive value.

A class is a new way of creating constructor functions, introduced in ECMAScript 2015.

function Old(a){
  this.p = a;
}

const o = new Old(1);

console.log(o);  // Logs `Old { p: 1 }`.

class New{
  constructor(a){
    this.p = a;
  }
}

const n = new New(1);

console.log(n); // Logs `New { p: 1 }`.

Class definitions are implicitly in strict mode:

class A{
  m1(){
    return this;
  }
  m2(){
    const m1 = this.m1;
    
    console.log(m1());
  }
}

new A().m2(); // Logs `undefined`.

super

The exception to the behavior with new is class extends{}, as mentioned above. Derived classes do not immediately set their this value upon invocation; they only do so once the base class is reached through a series of super calls (happens implicitly without an own constructor). Using this before calling super is not allowed.

Calling super calls the super constructor with the this value of the lexical scope (the function Environment Record) of the call. GetThisValue has a special rule for super calls. It uses BindThisValue to set this to that Environment Record.

class DerivedNew extends New{
  constructor(a, a2){
    // Using `this` before `super` results in a ReferenceError.
    super(a);
    this.p2 = a2;
  }
}

const n2 = new DerivedNew(1, 2);

console.log(n2); // Logs `DerivedNew { p: 1, p2: 2 }`.

5. Evaluating class fields

Instance fields and static fields were introduced in ECMAScript 2022.

When a class is evaluated, ClassDefinitionEvaluation is performed, modifying the running execution context. For each ClassElement:

  • if a field is static, then this refers to the class itself,
  • if a field is not static, then this refers to the instance.

Private fields (e.g. #x) and methods are added to a PrivateEnvironment.

Static blocks are currently a TC39 stage 3 proposal. Static blocks work the same as static fields and methods: this inside them refers to the class itself.

Note that in methods and getters / setters, this works just like in normal function properties.

class Demo{
  a = this;
  b(){
    return this;
  }
  static c = this;
  static d(){
    return this;
  }
  // Getters, setters, private modifiers are also possible.
}

const demo = new Demo;

console.log(demo.a, demo.b()); // Both log `demo`.
console.log(Demo.c, Demo.d()); // Both log `Demo`.

1: (o.f)() is equivalent to o.f(); (f)() is equivalent to f(). This is explained in this 2ality article (archived). Particularly see how a ParenthesizedExpression is evaluated.

2: It must be a MemberExpression, must not be a property, must have a [[ReferencedName]] of exactly «eval», and must be the %eval% intrinsic object.

3: Whenever the specification says “Let ref be the result of evaluating X.”, then X is some expression that you need to find the evaluation steps for. For example, evaluating a MemberExpression or CallExpression is the result of one of these algorithms. Some of them result in a Reference Record.

4: There are also several other native and host methods that allow providing a this value, notably Array.prototype.map, Array.prototype.forEach, etc. that accept a thisArg as their second argument. Anyone can make their own methods to alter this like (func, thisArg) => func.bind(thisArg), (func, thisArg) => func.call(thisArg), etc. As always, MDN offers great documentation.


Just for fun, test your understanding with some examples

For each code snippet, answer the question: “What is the value of this at the marked line? Why?”.

To reveal the answers, click the gray boxes.

  1. if(true){
      console.log(this); // What is `this` here?
    }
    

    globalThis. The marked line is evaluated in the initial global execution context.

  2. const obj = {};
    
    function myFun(){
      return { // What is `this` here?
        "is obj": this === obj,
        "is globalThis": this === globalThis
      };
    }
    
    obj.method = myFun;
    
    console.log(obj.method());
    
       

    obj. When calling a function as a property of an object, it is called with the this binding set to the base of the reference obj.method, i.e. obj.

  3. const obj = {
        myMethod: function(){
          return { // What is `this` here?
            "is obj": this === obj,
            "is globalThis": this === globalThis
          };
        }
      },
      myFun = obj.myMethod;
    
    console.log(myFun());
    
       

    globalThis. Since the function value myFun / obj.myMethod is not called off of an object, as a property, the this binding will be globalThis.

    This is different from Python, in which accessing a method (obj.myMethod) creates a bound method object.

  4. const obj = {
        myFun: () => ({ // What is `this` here?
          "is obj": this === obj,
          "is globalThis": this === globalThis
        })
      };
    
    console.log(obj.myFun());
    
       

    globalThis. Arrow functions don’t create their own this binding. The lexical scope is the same as the initial global scope, so this is globalThis.

  5. function myFun(){
      console.log(this); // What is `this` here?
    }
    
    const obj = {
        myMethod: function(){
          eval("myFun()");
        }
      };
    
    obj.myMethod();
    

    globalThis. When evaluating the direct eval call, this is obj. However, in the eval code, myFun is not called off of an object, so the this binding is set to the global object.

  6. function myFun() {
      // What is `this` here?
      return {
        "is obj": this === obj,
        "is globalThis": this === globalThis
      };
    }
    
    const obj = {};
    
    console.log(myFun.call(obj));
    
       

    obj. The line myFun.call(obj); is invoking the special built-in function Function.prototype.call, which accepts thisArg as the first argument.

  7. class MyCls{
      arrow = () => ({ // What is `this` here?
        "is MyCls": this === MyCls,
        "is globalThis": this === globalThis,
        "is instance": this instanceof MyCls
      });
    }
    
    console.log(new MyCls().arrow());
    
       

    It’s the instance of MyCls. Arrow functions don’t change the this binding, so it comes from lexical scope. Therefore, this is exactly the same as with the class fields mentioned above, like a = this;. Try changing it to static arrow. Do you get the result you expect?

What Does 'this' Mean in JavaScript? The this Keyword Explained with Examples

To understand what this truly means in JavaScript, let’s take a look at a very similar concept in the English Language: Polysemy.

Let’s consider the word «run«. Run is a single word which could mean many different things depending on the context.

  • “I will run home” – means to move quickly on foot
  • “She ran the 1500m” – means to run in a race
  • “He is running for president” – means vying for an official position
  • “The app is running” – means the software application is still open and active
  • “Go for a run” – means running as a form of exercise

and the list goes on.

A similar scenario plays out when you use the this keyword in your JavaScript code. When you do so, it automatically resolves to an object or scope depending on the context at which is was defined.

What are the possible contexts? And how can we use that information to deduce which object a this call will resolve to?

this Context

When used in a function, the this keyword simply points to an object to which it is bound. It answers the question of where it should get some value or data from:

function alert() { 
  console.log(this.name + ' is calling'); 
}
A function with a «this» reference

In the function above, the this keyword is referring to an object to which it is bound so it gets the «name» property from there.

But how do you know which object the function is bound to? How do you find out what this is referring to?

To do so, we need to take a detailed look at how functions are bound to objects.

Types of Binding in JavaScript

There are generally four kinds of bindings:

  • Default Binding
  • Implicit Binding
  • Explicit Binding
  • Constructor Call Binding

Default Binding in JavaScript

One of the first rules to remember is that if the function housing a this reference is a standalone function, then that function is bound to the global object.

function alert() { 
  console.log(this.name + ' is calling'); 
}

const name = 'Kingsley'; 
alert(); // Kingsley is calling
Standalone function

As you can see, name() is a standalone, unattached function, so it is bound to the global scope. As a result, the this.name reference resolves to the global variable const name = 'Kingsley'.

This rule, however, doesn’t hold if name() were to be defined in strict mode:

function alert() { 
  'use strict'; 
  console.log(this.name + ' is calling'); 
}

const name = 'Kingsley'; 
alert(); // TypeError: `this` is `undefined`
undefined in strict mode

When set in strict mode, the this reference is set to undefined.

Implicit Binding in JavaScript

Another scenario to look out for is whether the function is attached to an object (its context) at the call site.

According to the binding rule in JavaScript, a function can use an object as its context only if that object is bound to it at the call site. This form of binding is known as implicit binding.

Here is what I mean by that:

function alert() { 
  console.log(this.age + ' years old'); 
}

const myObj = {
  age: 22,
  alert: alert
}

myObj.alert() // 22 years old

Put simply, when you call a function using dot notation, this is implicitly bound to the object the function is being called from.

In this example, since alert is being called from myObj, the this keyword is bound to myObj. So when alert is called with myObj.alert(), this.age is 22, which is the age property of myObj.

Let’s look at another example:

function alert() { 
  console.log(this.age + ' years old'); 
}

const myObj = {
  age: 22,
  alert: alert,
  nestedObj: {
    age: 26,
    alert: alert
  }
}

myObj.nestedObj.alert(); // 26 years old

Here, because alert is ultimately being called from nestedObj, this is implicitly bound to nestedObj instead of myObj.

An easy way to figure out which object this is implicitly bound to is to look at which object is to the left of the dot (.):

function alert() { 
  console.log(this.age + ' years old'); 
}

const myObj = {
  age: 22,
  alert: alert,
  nestedObj: {
    age: 26,
    alert: alert
  }
}

myObj.alert(); // `this` is bound to `myObj` -- 22 years old
myObj.nestedObj.alert(); // `this` is bound to `nestedObj` -- 26 years old

Explicit binding in JavaScript

We saw that implicit binding had to do with having a reference in that object.

But what if we want to force a function to use an object as its context without putting a property function reference on the object?

We have two utility methods to achieve this: call() and apply().

Along with a couple other set of utility functions, these two utilities are available to all functions in JavaScript via the [[Prototype]] mechanism.

To explicitly bind a function call to a context, you simply have to invoke the call() on that function and pass in the context object as parameter:

function alert() { 
  console.log(this.age + ' years old'); 
}

const myObj = {
  age: 22
}

alert.call(myObj); // 22 years old

Now here’s the fun part. Even if you were to pass around that function multiple times to new variables (currying), every invocation will use the same context because it has been locked (explicitly bound) to that object. This is called hard binding.

function alert() { 
  console.log(this.age); 
} 

const myObj = { 
  age: 22 
}; 

const bar = function() { 
  alert.call(myObj); 
}; 

bar(); // 22
setTimeout(bar, 100); // 22 
// a hard-bound `bar` can no longer have its `this` context overridden 
bar.call(window); // still 22
Hard binding

Hard binding is a perfect way to lock a context into a function call and truly make that function into a method.

Constructor Call Binding in JavaScript

The final and perhaps most interesting kind of binding is the new binding which also accentuates the unusual behavior of JavaScript in comparison to other class-based languages.

When a function is invoked with the new keyword in front of it, otherwise known as a constructor call, the following things occur:

  1. A brand new object is created (or constructed)
  2. The newly constructed object is [[Prototype]]-linked to the function that constructed it
  3. The newly constructed object is set as the this binding for that function call.

Let’s see this in code to get a better understanding:

function giveAge(age) { 
  this.age = age; 
} 

const bar = new giveAge(22); 
console.log(bar.age); // 22

By calling giveAge(...) with new in front of it, we’ve constructed a new object and set that new object as the this for the call of foo(...). So new is the final way that you can bind a function call’s this .

Wrapping Up

In summary,

  • The this keyword, when used in a function, binds that function to a context object
  • There are four kinds of bindings: default binding, implicit binding, explicit binding and constructor call binding (new)
  • Knowing these four rules will help you easily discern the context for a this reference.

An Image Explaining the 'this' keyword

An Image Explaining the ‘this’ keyword
An Image Explaining the 'this' keyword
An Image Explaining the ‘this’ keyword

If you liked or benefited from this article and would like to support me, you can buy me a coffee here.

You can also reach me on Twitter. Be sure to check out my blog for more JavaScript and programming related content.

Thanks and see you soon.



Learn to code for free. freeCodeCamp’s open source curriculum has helped more than 40,000 people get jobs as developers. Get started

Рассказывает Дмитрий Павлютин 

Тайна this

Долгое время ключевое слово this оставалось для меня загадкой. Это мощный инструмент, но разобраться в нём нелегко.

С точки зрения Java, PHP или любого другого обычного языка this расценивается как экземпляр текущего объекта в методе класса, не больше и не меньше. Чаще всего его нельзя использовать вне метода, и этот подход не вызывает непонимания.

В JavaScript this — это текущий контекст исполнения функции. Поскольку функцию можно вызвать четырьмя способами:

  • вызов функции: alert('Hello World!'),
  • вызов метода: console.log('Hello World!'),
  • вызов конструктора: new RegExp('\d'),
  • непрямой вызов: alert.call(undefined, 'Hello World!'),

и каждый из них определяет свой контекст, поведение this слегка не соответствует ожиданиям начинающих разработчиков. Кроме того, strict mode также влияет на контекст исполнения.

Ключом к пониманию ключевого слова this является осознание принципов вызова функции и его влияния на контекст. В этой статье рассказывается про вызовы функций, влияние вызовов на this и типичные ловушки при идентификации контекста.

Прежде чем мы начнём, давайте познакомимся с несколькими терминами:

  • Вызов — это исполнение кода тела функции. Например, вызовом функции parseInt будет parseInt('15').
  • Контекстом вызова является значение this в теле функции.
  • Область видимости функции — это набор переменных, объектов и функций, к которым можно получить доступ из тела функции.

Содержание:

  1. Тайна this
  2. Вызов функции
    2.1. this при вызове функции
    2.2. this при вызове функции в strict mode
    2.3. Ловушка: this во внутренней функции
  3. Вызов метода
    3.1. this при вызове метода
    3.2. Ловушка: отделение метода от его объекта
  4. Вызов конструктора
    4.1. this при вызове конструктора
    4.2. Ловушка: как не забыть про new
  5. Непрямой вызов
    5.1. this при непрямом вызове
  6. Связанная функция
    6.1. this в связанной функции
  7. Стрелочная функция
    7.1. this в стрелочной функции
    7.2. Ловушка: определение метода стрелочной функцией
  8. Заключение

Вызов функции

Вызов функции совершается, когда за выражением, являющимся объектом функции, следуют открывающая скобка (, разделённый запятыми список аргументов и закрывающая скобка ), например, parseInt('18'). Выражение не может быть аксессором myObject.myFunction, который совершает вызов метода. Например, [1,5].join(',') — это вызов не функции, а метода.

Простой пример вызова функции:

function hello(name) {  
  return 'Hello ' + name + '!';
}
// Function invocation
var message = hello('World');  
console.log(message); // => 'Hello World!'

hello('World') — это вызов функции: hello расценивается как объект функции, за которым в скобках следует аргумент 'World'.

Более сложный пример — немедленно вызываемая функция:

var message = (function(name) {
   return 'Hello ' + name + '!';
})('World');
console.log(message); // => 'Hello World!'

Это тоже вызов функции: первая пара скобок (function(name) {...}) расценивается как объект функции, за которым в скобках следует аргумент: ('World').

this при вызове функции

this — это глобальный объект при вызове функции

Глобальный объект определяется средой исполнения. В веб-браузере это объект window.

В вызове функции контекстом исполнения является глобальный объект. Давайте проверим контекст следующей функции:

function sum(a, b) {
   console.log(this === window); // => true
   this.myNumber = 20; // add 'myNumber' property to global object
   return a + b;
}
// sum() is invoked as a function
// this in sum() is a global object (window)
console.log(sum(15, 16));     // => 31
console.log(window.myNumber); // => 20

Когда вызывается sum(15, 16), JavaScript автоматически инициализирует this как глобальный объект, являющийся window в браузере.

Когда this используется вне области видимости какой-либо функции (самая внешняя область видимости: контекст глобального исполнения), он также относится к глобальному объекту:

console.log(this === window); // => true
this.myString = 'Hello World!';
console.log(window.myString); // => 'Hello World!'
<script type="text/javascript">
   console.log(this === window); // => true
</script>

this при вызове функции в strict mode

this принимает значение undefined при вызове функции в strict mode

Strict mode был введён в ECMAScript 5.1 и представляет собой более надёжную систему защиты и проверки ошибок. Для активации поместите директиву 'use strict' вверху тела функции. Этот режим влияет на контекст исполнения, заставляя this быть undefined. Контекст исполнения перестаёт быть глобальным объектом, в отличие от предыдущего случая.

Пример функции, запущенной в strict mode:

function multiply(a, b) {
  'use strict'; // enable the strict mode
  console.log(this === undefined); // => true
  return a * b;
}
// multiply() function invocation with strict mode enabled
// this in multiply() is undefined
console.log(multiply(2, 5));

Когда multiply(2, 5) вызывается this становится undefined.

Strict mode активен не только в текущей области видимости, но и во всех вложенных:

function execute() {  
   'use strict'; // activate the strict mode

   function concat(str1, str2) {
     // the strict mode is enabled too
     console.log(this === undefined); // => true
     return str1 + str2;
   }
   // concat() is invoked as a function in strict mode
   // this in concat() is undefined
   console.log(concat('Hello', ' World!'));
}

execute();

'use strict' вставлена вверху тела execute, что активирует strict mode внутри её области видимости. Поскольку concat объявлена внутри области видимости execute, она наследует strict mode. И вызов concat('Hello', ' World!') приводит к тому, что this становится undefined.

Один файл JavaScript может содержать как «строгие», так и «нестрогие» функции. Поэтому возможно иметь в одном скрипте разные контексты исполнения для одного типа вызова:

function nonStrictSum(a, b) {
  // non-strict mode
  console.log(this === window); // => true
  return a + b;
}
function strictSum(a, b) {
  'use strict';
  // strict mode is enabled
  console.log(this === undefined); // => true
  return a + b;
}
// nonStrictSum() is invoked as a function in non-strict mode
// this in nonStrictSum() is the window object
console.log(nonStrictSum(5, 6)); // => 11
// strictSum() is invoked as a function in strict mode
// this in strictSum() is undefined
console.log(strictSum(8, 12)); // => 20

Ловушка: this во внутренней функции

Обычной ошибкой при работе с вызовом функции является уверенность в том, что this во внутренней функции такой же, как и во внешней.

Вообще-то контекст внутренней функции зависит только от вызова, а не от контекста внешней функции.

Чтобы получить ожидаемый this, модифицируйте контекст внутренней функции при помощи непрямого вызова (используя .call() или .apply(), об этом позже) или создайте связанную функцию (используя .bind(), об этом тоже поговорим позже).

Следующий пример вычисляет сумму двух чисел:

var numbers = {
   numberA: 5,
   numberB: 10,
   sum: function() {
     console.log(this === numbers); // => true
     function calculate() {
       // this is window or undefined in strict mode
       console.log(this === numbers); // => false
       return this.numberA + this.numberB;
     }
     return calculate();
   }
};
console.log(numbers.sum()); // => NaN or throws TypeError in strict mode

numbers.sum() — это вызов метода объекта, поэтому контекстом sum является объект numbers. Функция calculate определена внутри sum, поэтому вы можете ожидать, что this — это объект numbers и в calculate(). Тем не менее, calculate() — это вызов функции, а не метода, и поэтому его this — это глобальный объект window или undefined в strict mode. Даже если контекстом внешней функции sum является объект numbers, у него здесь нет власти.

Результатом вызова numbers.sum() является NaN или ошибка TypeError: Cannot read property 'numberA' of undefined в strict mode. Точно не ожидаемый результат 5 + 10 = 15, а всё потому, что calculate вызвана некорректно.

Для решения проблемы функция calculate должна быть исполнена в том же контексте, что и метод sum, чтобы получить доступ к значениям numberA и numberB. Это можно сделать при помощи метода .call():

var numbers = {
   numberA: 5,
   numberB: 10,
   sum: function() {
     console.log(this === numbers); // => true
     function calculate() {
       console.log(this === numbers); // => true
       return this.numberA + this.numberB;
     }
     // use .call() method to modify the context
     return calculate.call(this);
   }
};
console.log(numbers.sum()); // => 15

calculate.call(this) исполняет функцию calculate, но дополнительно модифицирует контекст в соответствии с первым параметром. Теперь this.numberA + this.numberB эквивалентно numbers.numberA + numbers.numberB и функция возвращает ожидаемый результат 5 + 10 = 15.

Вызов метода

Метод — это функция, хранящаяся в объекте. Пример:

var myObject = {
  // helloFunction is a method
  helloFunction: function() {
    return 'Hello World!';
  }
};
var message = myObject.helloFunction();
console.log(message);

helloFunction — это метод в myObject. Для доступа к методу нужно использовать аксессор: myObject.helloFunction.

Вызов метода совершается, когда за выражением в виде аксессора, расценивающемся как объект функции, следует пара скобок и разделенный запятыми список аргументов между ними.

В прошлом примере myObject.helloFunction() — это вызов метода helloFunction объекта myObject. Также вызовами метода являются: [1, 2].join(',') или /s/.test('beautiful world').

Важно отличать вызов функции от вызова метода. Главным отличием является то, что для вызова метода необходим аксессор (<expression>.functionProperty() или <expression>['functionProperty']()), а для вызова функции — нет (<expression>()).

console.log(
  ['Hello', 'World'].join(', ') // method invocation
);
console.log(
  ({ ten: function() { return 10; } }).ten() // method invocation
);
var obj = {};
obj.myFunction = function() {
  return new Date().toString();
};
console.log(
  obj.myFunction() // method invocation
);

var otherFunction = obj.myFunction;
console.log(otherFunction());     // function invocation
console.log(parseFloat('16.60')); // function invocation
console.log(isNaN(0));            // function invocation

this при вызове метода

this — это объект, которому принадлежит метод

При вызове метода, принадлежащего объекту, this становится этим объектом.

Давайте создадим объект, метод которого увеличивает число на 1:

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

Вызов calc.increment() сделает контекстом функции increment объект calc. Поэтому можно спокойно использовать this.num.

Объект JavaScript наследует метод своего прототипа. Когда вызывается метод, унаследованный от объекта, контекстом всё равно является сам объект:

var myDog = Object.create({  
  sayName: function() {
     console.log(this === myDog); // => true
     return this.name;
  }
});
myDog.name = 'Milo';  
// method invocation. this is myDog
console.log(myDog.sayName()); // => 'Milo'

Object.create() создаёт новый объект myDog и создаёт прототип. Объект myDog наследует метод sayName. Когда исполняется myDog.sayName(), myDog является контекстом исполнения.

В синтаксисе ECMAScript 6 class контекст вызова метода — тоже сам объект:

/* jshint esnext: true */

class Planet {  
  constructor(name) {
    this.name = name;    
  }
  getName() {
    console.log(this === earth); // => true
    return this.name;
  }
}
var earth = new Planet('Earth');  
// method invocation. the context is earth
console.log(earth.getName()); // => 'Earth'

Ловушка: отделение метода от его объекта

Метод объекта можно переместить в отдельную переменную. При вызове метода с использованием этой переменной вы можете подумать, что this — это объект, в котором определён метод.

На самом деле, если метод вызван без объекта, происходит вызов функции, и this становится глобальным объектом window или undefined. Создание связанной функции исправляет контекст — им становится объект, в котором содержится метод.

Следующий пример создаёт конструктор Animal и его экземпляр — myCat. Затем через 1 секунду setTimeout() логирует информацию об объекте myCat:

function Animal(type, legs) {  
  this.type = type;
  this.legs = legs;  
  this.logInfo = function() {
    console.log(this === myCat); // => false
    console.log('The ' + this.type + ' has ' + this.legs + ' legs');
  };
}
var myCat = new Animal('Cat', 4);  
// logs "The undefined has undefined legs"
// or throws a TypeError, in strict mode
setTimeout(myCat.logInfo, 1000);

Вы можете подумать, что setTimeout вызовет myCat.logInfo(), которая запишет информацию об объекте myCat. Но метод отделяется от объекта, когда передаётся в качестве параметра: setTimout(myCat.logInfo), и через секунду происходит вызов функции. Когда logInfo вызывается как функция, this становится глобальным объектом или undefined (но не объектом myCat), поэтому информация об объекте выводится некорректно.

Функцию можно связать с объектом, используя метод .bind(). Если отделённый метод связан с объектом myCat, проблема контекста решается:

function Animal(type, legs) {  
  this.type = type;
  this.legs = legs;  
  this.logInfo = function() {
    console.log(this === myCat); // => true
    console.log('The ' + this.type + ' has ' + this.legs + ' legs');
  };
}
var myCat = new Animal('Cat', 4);  
// logs "The Cat has 4 legs"
setTimeout(myCat.logInfo.bind(myCat), 1000);

myCat.logInfo.bind(myCat) возвращает новую функцию, исполняемую в точности как logInfo, но this которой остаётся myCat даже в случае вызова функции.

Вызов конструктора

Вызов конструктора совершается, когда за ключевым словом new следует выражение, расцениваемое как объект функции, и пара скобок с разделённым запятыми списком аргументов. Пример: new RegExp('\d').

В этом примере объявляется функция Country, которая затем вызывается в качестве конструктора:

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
var france = new Country('France', false);  
// Constructor invocation
var unitedKingdom = new Country;

france.travel(); // Travel to France

new Country('France', false) — это вызов конструктора функции Country. Результатом исполнения является новые объект, чьё поле name равняется 'France'.

Если конструктор вызван без аргументов, скобки можно опустить: new Country.

Начиная с ECMAScript 6, JavaScript позволяет определять конструкторы ключевым словом class:

/* jshint esnext: true */

class City {  
  constructor(name, traveled) {
    this.name = name;
    this.traveled = false;
  }
  travel() {
    this.traveled = true;
  }
}
// Constructor invocation
var paris = new City('Paris', false);  
paris.travel();

new City('Paris') — это вызов конструктора. Инициализация объекта управляется специальным методом класса: constructorthis которого является только что созданным объектом.

Вызов конструктора создаёт новый пустой объект, наследующий свойства от прототипа конструктора. Ролью функции-конструктора является инициализация объекта. Как вы уже знаете, контекст этого типа вызова называется экземпляром. Это — тема следующей главы.

Когда перед аксессором myObject.myFunction идёт ключевое слово new, JavaScript совершит вызов конструктора, а не метода. Возьмём в качестве примера new myObject.myFunction(): сперва при помощи аксессора extractedFunction = myObject.myFunction функция извлекается, а затем вызывается как конструктор для создания нового объекта: new extractedFunction().

this в вызове конструктора

this — это только что созданный объект

Контекстом вызова конструктора является только что созданный объект. Он используется для инициализации объекта данными из аргументом функции-конструктора.

Давайте проверим контекст в следующем примере:

function Foo () {  
  console.log(this instanceof Foo); // => true
  this.property = 'Default Value';
}
// Constructor invocation
var fooInstance = new Foo();  
console.log(fooInstance.property); // => 'Default Value'

new Foo() делает вызов конструктора с контекстом fooInstance. Объект инициализируется внутри Foo: this.property задаётся значением по умолчанию.

Тоже самое происходит при использовании class, только инициализация происходит в методе constructor:

/* jshint esnext: true */

class Bar {
  constructor() {
    console.log(this instanceof Bar); // => true
    this.property = 'Default Value';
  }
}
// Constructor invocation
var barInstance = new Bar();
console.log(barInstance.property); // => 'Default Value'

Когда исполняется new Bar(), JavaScript создаёт пустой объект и делает его контекстом метода constructor. Теперь вы можете добавлять свойства, используя this: this.property = 'Default Value'.

Ловушка: как не забыть про new

Некоторые функции JavaScript создают экземпляры при вызове не только в качестве конструктора, но и функции. Например, RegExp:

var reg1 = new RegExp('\w+');
var reg2 = RegExp('\w+');

console.log(reg1 instanceof RegExp);      // => true
console.log(reg2 instanceof RegExp);      // => true
console.log(reg1.source === reg2.source); // => true

При исполнении new RegExp('\w+') и RegExp('\w+') JavaScript создаёт эквивалентные объекты регулярных выражений.

Использование вызова функции для создания объектов потенциально опасно (если опустить фабричный метод), потому что некоторые конструкторы могут не инициализировать объект при отсутствии ключевого слова new.

Следующий пример иллюстрирует проблему:

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

Vehicle — это функция, задающая свойства type и wheelsCount объекту-контексту. При исполнении Vehicle('Car', 4) возвращается объект car, обладающий корректными свойствами: car.type равен 'Car' а car.wheelsCount — 4. Легко подумать, что всё работает как надо.

Тем не менее, this — это объект window при вызове функции, и Vehicle('Car', 4) задаёт свойства объекта window — упс, что-то пошло не так. Новый объект не создан.

Обязательно используйте оператор new, когда ожидается вызов конструктора:

function Vehicle(type, wheelsCount) {  
  if (!(this instanceof Vehicle)) {
    throw Error('Error: Incorrect invocation');
  }
  this.type = type;
  this.wheelsCount = wheelsCount;
  return this;
}
// Constructor invocation
var car = new Vehicle('Car', 4);  
console.log(car.type);               // => 'Car'  
console.log(car.wheelsCount);        // => 4  
console.log(car instanceof Vehicle); // => true

// Function invocation. Generates an error.
var brokenCar = Vehicle('Broken Car', 3);

new Vehicle('Car', 4) работает верно: новый объект создан и инициализирован, поскольку присутствует слово new.

В вызове функции добавлена верификация: this instanceof Vehicle, чтобы убедиться, что у контекста исполнения верный тип объекта. Если this — не Vehicle, генерируется ошибка. Таким образом, если исполняется  Vehicle('Broken Car', 3) (без new), то выбрасывается исключение: Error: Incorrect invocation.

Непрямой вызов

Непрямой вызов производится, когда функция вызывается методами .call() или .apply().

Функции в JavaScript — объекты первого класса, то есть функция — это объект типа Function.

Из списка методов этой функции два, .call() и .apply(), используются для вызова функции с настраиваемым контекстом:

  • Метод .call(thisArg[, arg1[, arg2[, ...]]]) принимает в качестве первого аргумента thisArg контекст вызова, а список аргументов arg1, arg2, ... передаётся вызываемой функции.
  • Метод .apply(thisArg, [args]) принимает в качестве первого аргумента thisArg контекст вызова, а array-like объект [args] передаётся вызываемой функции в качестве аргумента.

Следующий пример демонстрирует непрямой вызов:

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

increment.call() и increment.apply() оба вызывают функцию-инкремент с аргументом 10.

Главным отличием между ними является то, что .call() принимает список аргументов, например, myFunction.call(thisValue, 'value1', 'value2'), а .apply() принимает эти значения в виде array-like объекта: myFunction.apply(thisValue, ['value1', 'value2']).

this при непрямом вызове

this — это первый аргумент .call() или .apply()

Очевидно, что при непрямом вызове this — значение, передаваемое .call() или .apply() в качестве первого аргумента. Пример:

var rabbit = { name: 'White Rabbit' };
function concatName(string) {
  console.log(this === rabbit); // => true
  return string + this.name;
}
// Indirect invocations
console.log(concatName.call(rabbit, 'Hello '));  // => 'Hello White Rabbit'
console.log(concatName.apply(rabbit, ['Bye '])); // => 'Bye White Rabbit'

Непрямой вызов может пригодиться, когда функцию нужно вызвать в особом контексте, например, решить проблему при вызове функции, где this — всегда window или undefined. Его также можно использовать для симуляции вызова метода объекта.

Ещё одним примером использования является создание иерархии классов в ES5 для вызова родительского конструктора:

function Runner(name) {  
  console.log(this instanceof Rabbit); // => true
  this.name = name;  
}
function Rabbit(name, countLegs) {  
  console.log(this instanceof Rabbit); // => true
  // Indirect invocation. Call parent constructor.
  Runner.call(this, name);
  this.countLegs = countLegs;
}
var myRabbit = new Rabbit('White Rabbit', 4);  
console.log(myRabbit); // { name: 'White Rabbit', countLegs: 4 }

Runner.call(this, name) в Rabbit создаёт непрямой вызов родительской функции для инициализации объекта.

Связанная функция

Связанная функция — это функция, связанная с объектом. Обычно она создаётся из обычной функции при помощи метода .bind(). У двух функций совпадают тела и области видимости, но различаются контексты.

Метод .bind(thisArg[, arg1[, arg2[, ...]]]) принимает в качестве первого аргумента thisArg контекст вызова связанной функции, а необязательный список аргументов arg1, arg2, ... передаётся вызываемой функции. Он возвращает новую функцию, связанную с thisArg.

Следующий код создаёт связанную функцию и вызывает её:

function multiply(number) {  
  'use strict';
  return this * number;
}
// create a bound function with context
var double = multiply.bind(2);  
// invoke the bound function
console.log(double(3));  // => 6  
console.log(double(10)); // => 20

multiply.bind(2) возвращает новый объект функции double, который связан с числом 2. Код и область видимости у multiply и double совпадают.

В отличие от методов .apply() и .call(), сразу вызывающих функцию, метод .bind() возвращает новую функцию, которую впоследствии нужно будет вызвать с уже заданным this.

this в связанной функции

this — это первый аргумент .bind()

Ролью .bind() является создание новой функции, чей вызов будет иметь контекст, заданный в первом аргументе .bind(). Это — мощный инструмент, позволяющий создавать функции с заранее определённым значением this.

Давайте посмотрим, как настроить this связанной функции:

var numbers = {  
  array: [3, 5, 10],
  getNumbers: function() {
    return this.array;    
  }
};
// Create a bound function
var boundGetNumbers = numbers.getNumbers.bind(numbers);  
console.log(boundGetNumbers()); // => [3, 5, 10]  
// Extract method from object
var simpleGetNumbers = numbers.getNumbers;  
console.log(simpleGetNumbers()); // => undefined or throws an error in strict mode

numbers.getNumbers.bind(numbers) возвращает функцию boundGetNumbers, которая связана с объектом numbers. Затем boundGetNumbers() вызывается с this, равным numbers, и возвращает корректный объект.

Функцию numbers.getNumbers можно извлечь в переменную simpleGetNumbers и без связывания. При дальнейшем вызове функции simpleGetNumbers() задаёт this как window или undefined, а не numbers. В этом случае simpleGetNumbers() не вернет корректное значение.

.bind() создаёт перманентную контекстную ссылку и хранит её. Связанная функция не может изменить контекст, используя .call() или .apply() с другим контекстом — даже повторное связывание не даст эффекта.

Только вызов связанной функции как конструктора может изменить контекст, но это не рекомендуется (используйте нормальные функции).

В следующем примере сперва объявляется связанная функция, а затем производится попытка изменить контекст:

function getThis() {  
  'use strict';
  return this;
}
var one = getThis.bind(1);  
// Bound function invocation
console.log(one()); // => 1  
// Use bound function with .apply() and .call()
console.log(one.call(2));  // => 1  
console.log(one.apply(2)); // => 1  
// Bind again
console.log(one.bind(2)()); // => 1  
// Call the bound function as a constructor
console.log(new one());     // => Object

Только new one() изменяет контекст связанной функции, в остальных типах вызова this всегда равен 1.

Стрелочная функция

Стрелочная функция нужна для более короткой формы объявления функции и лексического связывания контекста.

Её можно использовать следующим образом:

/* jshint esnext: true */

var hello = (name) => {  
  return 'Hello ' + name;
};
console.log(hello('World')); // => 'Hello World'  
// Keep only even numbers
console.log([1, 2, 5, 6].filter(item => item % 2 === 0)); // => [2, 6]

Стрелочные функции используют облегчённый синтаксис, убирая ключевое слово function. Можно даже опустить return, когда у функции есть лишь одно выражение.

Стрелочная функция анонимна, что означает, что её свойство name — пустая строка ''. Таким образом, у неё нет лексического имени, которое нужно для рекурсии и управления хэндлерами.

Кроме того, она не предоставляет объект arguments, в отличие от обычной функции. Тем не менее, это можно исправить, используя rest-параметры ES6:

/* jshint esnext: true */

var sumArguments = (...args) => {  
   console.log(typeof arguments); // => 'undefined'
   return args.reduce((result, item) => result + item);
};
console.log(sumArguments.name);      // => ''  
console.log(sumArguments(5, 5, 6)); // => 16

this в стрелочной функции

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

Стрелочная функция не создаёт свой контекст исполнения, а заимствует this из внешней функции, в которой она определена.

Следующий пример показывает прозрачность контекста:

/* jshint esnext: true */

class Point {  
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  log() {
    console.log(this === myPoint);
    setTimeout(()=> {
      console.log(this === myPoint);      // => true
      console.log(this.x + ':' + this.y); // => '95:165'
    }, 1000);
  }
}
var myPoint = new Point(95, 165);  
myPoint.log();

setTimeout вызывает стрелочную функцию в том же контексте (метод myPoint), что и метод log(). Как мы видим, стрелочная функция «наследует» контекст той функции, в которой определена.

Если попробовать использовать в этом примере обычную функцию, она создаст свой контекст (window или undefined). Поэтому для того, чтобы код работал корректно, нужно вручную привязать контекст: setTimeout(function() {...}.bind(this)). Это громоздко, поэтому проще использовать стрелочную функцию.

Если стрелочная функция определена вне всех функций, её контекст — глобальный объект:

/* jshint esnext: true */

var getContext = () => {
   console.log(this === window); // => true
   return this;
};
console.log(getContext() === window); // => true

Стрелочная функция связывается с лексическим контекстом раз и навсегдаthis нельзя изменить даже при помощи метод смены контекста:

/* jshint esnext: true */

var numbers = [1, 2];  
(function() {  
  var get = () => {
    return this;
  };
  console.log(this === numbers); // => true
  console.log(get()); // => [1, 2]
  // Use arrow function with .apply() and .call()
  console.log(get.call([0]));  // => [1, 2]
  console.log(get.apply([0])); // => [1, 2]
  // Bind
  console.log(get.bind([0])()); // => [1, 2]
}).call(numbers);

Функция, вызываемая непрямым образом с использованием .call(numbers), задаёт this значение numbers.  Стрелочная функция get также получает numbers в качестве this, поскольку принимает контекст лексически. Неважно, как вызывается get, её контекстом всегда будет numbers. Непрямой вызов с другим контекстом (используя .call() или .apply()), повторное связывание (с использованием .bind()) не принесут эффекта.

Стрелочную функцию нельзя использовать в качестве конструктора. Если вызвать new get(), JavaScript выбросит ошибку: TypeError: get is not a constructor.

Ловушка: определение метода стрелочной функцией

Вы можете захотеть использовать стрелочную функцию для объявления метода. Справедливо: их объявления гораздо короче по сравнению с обычным выражением: (param) => {...} вместо function(param) {..}.

В этом примере демонстрируется определение метода format() класса Period с использованием стрелочной функции:

/* jshint esnext: true */

function Period (hours, minutes) {  
  this.hours = hours;
  this.minutes = minutes;
}
Period.prototype.format = () => {  
  console.log(this === window); // => true
  return this.hours + ' hours and ' + this.minutes + ' minutes';
};
var walkPeriod = new Period(2, 30);  
console.log(walkPeriod.format());

Так как format — стрелочная функция, определённая в глобальном контексте, её this — это объект window. Даже если format исполняется в качестве метода объекта walkPeriod.format(), window остаётся контекстом вызова. Так происходит, потому что стрелочная функция имеет статический контекст, не изменяемый другими типами вызовов.

this — это window, поэтому this.hours и this.minutes становятся undefined. Метод возвращает строку 'undefined hours and undefined minutes', что не является желаемым результатом.

Функциональное выражение решает проблему, поскольку обычная функция изменяет свой контекст в зависимости от вызова:

function Period (hours, minutes) {  
  this.hours = hours;
  this.minutes = minutes;
}
Period.prototype.format = function() {  
  console.log(this === walkPeriod); // => true
  return this.hours + ' hours and ' + this.minutes + ' minutes';
};
var walkPeriod = new Period(2, 30);  
console.log(walkPeriod.format());

walkPeriod.format() — это вызов метода с контекстом walkPeriod. this.hours принимает значение 2, а this.minutes — 30, поэтому метод возвращает корректный результат: '2 hours and 30 minutes'.

Заключение

Поскольку вызов функции имеет наибольшее влияние на this, отныне не спрашивайте:

Откуда берется this?

а спрашивайте:

Как функция вызывается?

А в случае со стрелочной функцией спросите:

Каков this там, где объявлена стрелочная функция?

Такой подход к this убережет вас от лишней головной боли.

Не путайтесь в контекстах! 🙂

Перевод статьи «Gentle explanation of ‘this’ keyword in JavaScript»

Like this post? Please share to your friends:
  • This word in english тест 12
  • This word i don think it means
  • This word has two spelling
  • This word has something to do with water
  • This word has a similar meaning to bags