Для чего необходимо ключевое слово this
Перейти к содержимому

Для чего необходимо ключевое слово this

  • автор:

Методы объекта, «this»

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

Так же, как и в реальном мире пользователь может совершать действия: выбирать что-то из корзины покупок, авторизовываться, оплачивать и т.д.

Такие действия в JavaScript представлены свойствами-функциями объекта (методы).

Пример метода

Добавляем метод sayHi объекту user :

Ключевое слово «this» в методах

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

Например, коду внутри user.sayHi() может понадобится имя пользователя, которое хранится в объекте user .

Для доступа к информации внутри объекта метод может использовать ключевое слово this .

Значение this — это объект «перед точкой», который использовался для вызова метода.

Здесь во время выполнения кода user.sayHi() значением this будет являться user (ссылка на объект user ).

Можно получить доступ к объекту и без ключевого слова this , ссылаясь на него через внешнюю переменную user (в которой хранится объект).

Но такой код не будет надежным. Если мы скопируем ссылку на объект user в другую переменную, например, admin = user , а затем перезапишем переменную user чем-то другим, тогда будет осуществлен доступ к неправильному объекту при вызове метода из admin .

Это показано ниже:

«this» не является фиксированным

В JavaScript ключевое слово «this» может использоваться в любой функции.

Значение this вычисляется во время выполнения кода и зависит от контекста.

Например, здесь одна и та же функция назначена двум разным объектам и имеет различное значение this при вызовах:

При вызове obj.f() значение this внутри f равно obj .

У стрелочных функций нет «this»

У стрелочных функций нет своего собственного this . Если мы используем this внутри стрелочных функций, то его значение берется из внешней «нормальной функции».

Например, здесь arrow() использует значение this из внешнего метода user.sayHi() :

Эта особенность стрелочных функций полезна, когда мы намерено хотим брать значение this из внешнего контекста.

Для чего необходимо ключевое слово this

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

Глобальный контекст

В глобальном контексте this ссылается на глобальный объект. В данном случае поведение не зависит от режима (строгий или нестрогий):

var currentDocument = this.document;

Но в данном случае использвание this избыточно.

Контекст функции

Если скрипт запускается в строгом режиме (директива «use strict»), то this ссылается непосредственно на контекст функции. Иначе this ссылается на внешний контекст. Например:

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

Если бы мы использовали строгий режим, то this в этом случае имело бы значение undefined:

Контекст объекта

В контексте объекта, в том числе в его методах, ключевое слово this ссылается на этот же объект:

Примеры

Рассмотрим более сложный пример:

Здесь определена глобальная переменная bar. И также в функции foo определена локальная переменная bar. Значение какой переменной будет выводиться в функции foo? Функция foo выводит значение глобальной переменной, так как данный скрипт запускается в нестрогом режиме, а значит ключеое слово this в функции foo ссылается на внешний контекст.

Иначе дело обстоит с объектами. Они определяют свой собственный контекст, в котором существует свое свойство bar. И при вызове метода foo внешним контекстом по отношению к функции будет контекст объектов o3 и o4.

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

Несмотря на то, что объект o2 использует метод foo из объекта o1, тем не менее функция o1.foo также будет искать значение для this.bar во внешнем котексте, то есть в контексте объекта o2. А в объекте o2 это значение равно bar: «bar2».

То же самое с глобальной переменной foo, которая ссылается на ту же функцию, что и метод o1.foo. В этом случае также будет происходить поиск значения для this.bar во внешним контексте, то есть в глобальном контексте, где определена переменная var bar = «bar3».

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

Здесь функция daz в качестве this.bar использует значение переменной bar из внешнего контекста, то есть значение глобальной переменной bar. Функция maz также в качестве this.bar использует значение переменной bar из внешнего контекста, а это значение this.bar из внешней функции daz, которое в свою очередь представляет значение глобальной переменной bar. Поэтому в итоге консоль выведет «bar2», а не «bar5».

Явная привязка

С помощью методов call() и apply() можно задать явную привязку функции к определенному контексту:

Во втором случае функция foo привязывается к объекту o3, который и определяет ее контекст. Поэтому во втором случае консоль выведет «bar3».

Метод bind

Метод f.bind(o) позволяет создать новую функцию с тем же телом и областью видимости, что и функция f, но с привязкой к объекту o:

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

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

Функция printCourses проходит по всем курсам из массива и при их выводе предваряет их значением свойства title. Однако на консоли при запуске программы мы увидим следующее:

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

Стрелочные функции также позволяют решить данную проблему:

Контекстом для стрелочной функции в данном случае будет выступать контекст объекта school. Соответственно, нам недо определять дополнительные переменые для передачи данных в функцию.

Для чего необходимо ключевое слово this

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:

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.

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

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.

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

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.

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.

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.

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:

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 bound 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.

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.

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 instance for instance fields and to the class for static fields.

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.

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.

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.

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.

Examples

this in function contexts

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

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

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.

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.

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. We will assume that getThisGetter is a non-strict function, which means it’s contained in a non-strict script and not further nested in a class or strict function.

We can call getThisGetter as a method of obj , which binds this to obj inside its body. 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 was not an arrow function, such calls would cause the this value to be globalThis , because getThisGetter is non-strict.

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.

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.

this in DOM event handlers

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

this in inline event handlers

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

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

In this case, the this parameter of the inner function is bound to globalThis (i.e. the default object in non–strict mode where this isn’t passed in 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:

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 bound to the scope object, as if the obj1. prefix exists.

О ключевом слове «this» языка JavaScript: особенности использования с пояснениями

Долгое время ключевое слово 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 в теле функции.
  • Область видимости функции — это набор переменных, объектов и функций, к которым можно получить доступ из тела функции.
Содержание:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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() :

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

Вызов метода

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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] передаётся вызываемой функции в качестве аргумента.

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

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() в качестве первого аргумента. Пример:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Функция, вызываемая непрямым образом с использованием .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 с использованием стрелочной функции:

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

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

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

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

Заключение

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

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

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

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *