Стрелочные функции в JavaScript

Опубликовано: 5 Января, 2022

Предпосылка: это в JavaScript

В этом посте мы обсудили еще несколько связанных с этим функций в JavaScript.

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

Стрелочные функции, представленные в ES6, обеспечивают краткий способ написания функций на JavaScript.
Еще одно существенное преимущество, которое он предлагает, заключается в том, что он не связывает себя this . Другими словами, контекст внутри стрелочных функций определяется лексически или статически.

Что мы имеем в виду?

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

Выход:

[Объект Object] 
undefined не определено лет

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

Это происходит потому, что каждая обычная функция без стрелок определяет свой собственный this или context в зависимости от их вызова. Контекст включающих объектов / функций не влияет на эту тенденцию к автоматическому определению своего собственного контекста.

Как решить эту проблему?

На ум приходит одно очевидное решение: что, если функция не определила свой собственный контекст? Что если бы она унаследовала контекст от info() , потому что это означало бы, что эта функция () получит this как было определено в info()

Что ж, именно это и делают стрелочные функции: они сохраняют значение this из включаемых в них
контекст.
То есть, в приведенном выше примере, если бы функция, определенная как обратный вызов для setTimeout() была стрелочной функцией, она унаследовала бы значение this из охватывающего его контекста - info()

<!DOCTYPE html>
<html>
<body>
<script>
let People = function (person, age) {
this .person = person;
this .age = age;
this .info = function () {
// logs People
document.write( this );
setTimeout(() => {
// arrow function to make lexical "this" binding
// here this=People."this" has been inherited
document.write( this .person + " is " + this .age
+ " years old" );
}, 3000);
}
}
let person1 = new People( 'John' , 21);
// logs : John is 21 years old after 3 seconds
person1.info();
</script>
</body>
</html>

Выход:

[Объект Object] 
Джону 21 год

Таким образом, независимо от того, вызывалась ли стрелочная функция с использованием вызова функции или вызова метода, она сохраняет значение] this из своего окружающего контекста. Другими словами, this же, как и непосредственно за ее пределами.

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

это в отдельных методах:

Когда метод из любого объекта отделен от него или сохраняется в переменной, например: let separated = People.info , он теряет ссылку на вызывающий объект.

Обратите внимание на отсутствие открывающих и закрывающих круглых скобок после info . Это указывает на то, что мы не вызываем метод немедленно.

Например :

<!DOCTYPE html>
<html>
<body>
<script>
let People = function (person, age) {
this .person = person;
this .age = age;
this .info = function () {
// logs People
document.write( this + ' ' );
// here this=People
document.write( this .person + " is " + this .age +
" years old" + '<br>' );
}
}
let person1
= new People( 'John' , 21);
// logs : John is 21 years old
person1.info();
// separating the method info() from its
// object by storing it in a variable
let separated = person1.info;
// logs : undefined is undefined years old
separated();
</script>
</body>
</html>

Выход:

[object Object] Джону 21 год
[окно объекта] undefined не определено лет

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

Таким образом, когда мы называем separated() мы видим , что this теперь установлено на глобальный объект окна.

Как решить эту проблему?
Один из способов - bind значение объекта с методом при хранении метода separated . Это гарантирует, что все ссылки на this ссылаются на этот связанный объект даже в отдельном методе.
Это можно сделать с помощью bind() следующим образом:

<!DOCTYPE html>
<html>
<body>
<script>
let People = function (person, age) {
this .person = person;
this .age = age;
this .info = function () {
// logs People
document.write( this + ' ' );
// here this=People
document.write( this .person + " is " + this .age +
" years old" + '<br>' );
}
}
let person1
= new People( 'John' , 21);
// logs : John is 21 years old
person1.info();
let separated = person1.info.bind(person1);
/*
the bind(person1) statement ensures that "this" always
refers to person1 inside the bound method- info()
*/
// logs : undefined is undefined years old
separated();
</script>
</body>
</html>

Выход:

[object Object] Джону 21 год
[object Object] Джону 21 год

Примечание: мы могли бы использовать любой объект для bind() метода info() вместо code.person1, и выходные данные изменились бы соответствующим образом. Не обязательно привязывать метод к его родительскому объекту.

Если вы зашли так далеко, то заметили бы, что мы пару раз bind()
Давайте теперь изучим, что на самом деле представляют собой bind(), call() and apply()

связать, позвонить и применить

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

связывать()

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

Общий синтаксис привязки:

boundfunction = someFunction.bind (someObject, additionalParams);

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

<!DOCTYPE html>
<html>
<body>
<script>
let fruit = function (person, color) {
this .person = person;
this .color = color;
this .displayInfo = function () {
document.write( this .person + " is " + this .color + '<br>' );
}
}
let bindingObj
= {
// creating an object using object literal syntax
person : "Banana" ,
color : "Yellow" ,
}
// Constructor invocation to create an object fruit1
let fruit1
= new fruit( "Orange" , "orange" );
// logs :Orange is orange
// Method invocation of displayInfo()
fruit1.displayInfo();
// binding the separated method to a new object
let newBound = fruit1.displayInfo.bind(bindingObj);
// logs : Banana is Yellow
newBound();
</script>
</body>
</html>

Выход:

Апельсин оранжевый
Банан желтый

Обратите внимание, что мы вызываем displayInfo () для fruit1, используя вызов метода: fruit1.displayInfo и поэтому можем ожидать, что он будет иметь контекст fruit1 . Однако, похоже, это не так, так как «Banana is Yellow» регистрируется вместо «Orange is orange».

Это происходит потому, что мы явно привязываем значение this внутри displayInfo () с помощью команды bind() к bindingObj .
Как мы видим, явная привязка объекта к функции отменяет ее обычные правила контекста и принудительно устанавливает все this значения для привязанного объекта внутри него.

Часто при передаче функций он теряет свой контекст, например:

<!DOCTYPE html>
<html>
<body>
<script>
let noBinding = {
persons : "John" ,
passAround() {
// displayInfo function is passed
// as a callback to setTimeout
setTimeout( this .displayInfo, 3000);
},
displayInfo() {
// logs the Window object
document.write( this + ' ' );
// logs undefined
document.write( this .persons);
}
}
noBinding.passAround();
</script>
</body>
</html>

Выход:

[окно объекта] undefined

Чтобы предотвратить сброс контекста внутри функции, переданной как обратный вызов, мы явно привязываем значение this чтобы оно было таким же, как оно было внутри passAround() , то есть: к объекту binding

<!DOCTYPE html>
<html>
<body>
<script>
let binding = {
persons : "John" ,
passAround() {
// displayInfo function is passed as a callback
// to setTimeout binding the context of displayInfo
// to "this" = binding in this execution context
setTimeout( this .displayInfo.bind( this ), 3000);
},
displayInfo() {
// logs the binding object
document.write( this + ' ' );
// logs John
document.write( this .persons);
}
}
binding.passAround();
</script>
</body>
</html>

Выход:

[объект Объект] Джон

call () и apply ()

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

Синтаксис:
звоните :

function.call (thisValue, arg1, arg2, ...)

применить :

function.apply (thisValue, [arg1, arg2, ...])

Первым аргументом в обоих случаях является значение this которое мы хотим, чтобы вызываемая функция имела.

По сути, единственное различие между этими двумя методами заключается в том, что в apply второй аргумент является объектом массива аргументов, а при call все аргументы отправляются в формате, разделенном запятыми.

Например:

<!DOCTYPE html>
<html>
<body>
<script>
// Create two different objects to test call() and apply()
let banana = { person : 'Banana' };
let orange = { person : 'Orange' };
function fruits(adj)
{
document.write( this + ' ' );
return ( this .person + " is " + adj + '<br>' );
}
// call() and apply() will immediately invoke
// the function they are being called on
// logs : Banana is yummy
document.write(fruits.call(banana, 'yummy' ));
// logs: Orange is sour
document.write(fruits.apply(orange, [ 'sour' ]));
</script>
</body>
</html>

Выход:

[object Object] Банан вкусный
[объект Объект] Апельсин кислый

Примечание . Оба call() и apply() доступны в прототипе объекта Function по умолчанию.

это со слушателями событий

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

<!DOCTYPE html>
<html>
<body>
<button>Click me to see console logging < /button>
<script>
let elem = document.querySelector( 'btn' )
elem.addEventListener( 'click' , function () {
// logs: btn
document.write( this )
})
</script>
</body>
</html>

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

К счастью, это можно легко решить, используя currentTarget для элемента следующим образом:

<!DOCTYPE html>
<html>
<body>
<button>Click me to see console logging < /button>
<script>
function outerfunc(elem)
{
this .clickHandler = function () {
elem.addEventListener( 'click' , (e) => {
// logs the<button />element
document.write(e.currentTarget);
// this has the value of outerfunc
this .displayInfo();
})
}
;
this .displayInfo = function () {
document.write( ' Correctly identified!' );
}
}
let button = document.body.querySelector( 'button' );
let test = new outerfunc(button);
test.clickHandler()
</script>
</body>
</html>

Выход:

[объект HTMLButtonElement] Определено правильно!

Как мы видим, использование currentTarget позволяет нам получить доступ к элементу, к которому добавлен прослушиватель событий, в то время как this позволяет нам получить доступ к контексту включающей функции, то есть: this позволяет нам успешно вызвать метод displayInfo ().