Ако програмирате на JavaScript от известно време, със сигурност ще разпознаете ключовата дума Това ти е причинило повече от едно главоболиеИ откакто се появиха стрелковите функции в ES6, нещата станаха още по-сложни... или по-прости, в зависимост от това как го гледате.
В тази статия ще разгледаме по-подробно как работи Това се отнася за традиционните функции и функциите със стрелки.Защо понякога изглежда, че сочи към конкретен обект, а друг път към глобален обект, и в кои ситуации има смисъл да се използват стрелкови функции и в кои е по-добре да се избягват?
Que es exactamente this в JavaScript
Запазената дума this Това е препратка към контекста на изпълнение на функцията, която се изпълнява в момента. За разлика от други езици, в JavaScript решението не се основава на това къде е дефинирана функцията, а по-скоро на нейното изпълнение. как да се извика.
Това означава, че една и съща функция може да бъде извикана по различни начини и във всеки от тях, this може да сочи към различен обектТова не е нещо, което можеш да промениш с директно задание (не можеш да го направиш this = algo), но можете да му повлияете със специфични механизми, като например call, apply y bind.
Освен това, поведението им варира между строг режим и нестрог режимВ нестрог режим, ако извикате функция „гола“ (без обект пред нея), this Обикновено това е глобалният обект (в браузъра, window), докато в строг режим може да бъде undefinedТова разграничение е важно при сравняване на примерни кодове от различни източници.
Това в глобален контекст и при нормални функции
В браузърите, когато не сте вътре в модул или функция, глобалният контекст е обектът. windowи там this посочете този обектТоест, ако въведете следното в конзолата:
console.log(this === window); // true en un entorno de navegador no estricto
В рамките на функция, декларирана по „класически“ начин (нормална функция), стойността на this Зависи как се нарича тази функция.Ако го извикате без предходен обект, в нестрог режим this Обикновено е глобалният и, строго погледнато, ще бъде undefinedЕто защо понякога, когато преместваме код от един сайт на друг, Това вече не е това, което очаквахте..
Това в обектни методи, дефинирани с нормални функции
Когато дефинирате метод върху обект, използвайки традиционен синтаксис, this в рамките на метода, препратка към самия обект от който е бил извикан този метод.
Например, ако имате нещо подобно:
const obj = {
speak() {
console.log(this);
}
};
obj.speak();
Поканата за представяне на предложения obj.speak() прави this в speak бъди единственият objТова е поведението, което хората обикновено очакват интуитивно: методът говори „от името“ на обекта.
Ако използвате класическа функция вместо съкратения синтаксис, ефектът е същият, защото Ключът се крие в това как се извиква методътНяма значение дали сте използвали съкращението на метода или ключовата дума function вътре в обекта.
Това в методи, дефинирани със стрелкови функции
Нещата се променят, когато дефинирате метода със стрелкова функция. Нещо подобно:
const obj2 = {
speak: () => {
console.log(this);
}
};
obj2.speak();
В този случай, при изпълнението obj2.speak() ще видите това this Вече не е obj2но външният лексикален контекст към този обект, който в класически браузърен скрипт обикновено е глобалният обект window.
Това е озадачаващо за първи път, защото очаквате метод на обекта да сочи към самия обект. Въпреки това, Функциите със стрелки не създават свои собствени thisТе наследяват стойността на this на обхвата, в който са били дефинирани. Ако този обхват е глобален, те наследяват глобалния обхват; ако е друг, те ще наследят този друг обхват.
Следователно, често повтаряна препоръка в съвременната документация е: Не използвайте функции със стрелки като методи на обекти когато имате нужда this насочете се към този обект.
Лексикален обхват на this функции със стрелки
Ключовата разлика между нормалните функции и стрелковите функции е, че последните имат лексикална връзка за thisПросто казано: те не решават своите this не когато се обаждат един на друг, а когато Crean.
Представете си този пример:
const obj3 = {
speak() {
(() => {
console.log(this);
})();
}
};
obj3.speak();
Тук може да изглежда, че както вътре speak изпълняваме функция със стрелка, Това би трябвало да се „нулира“ до глобалнотоНо се случва точно обратното: функцията със стрелка улавя this на функцията, която го заобикалякоето в този случай е методът speak извикан като obj3.speak()Следователно, стойността на this Показаният на конзолата е този от obj3.
Това означава, функциите със стрелки нямат свои собствени thisа по-скоро използват повторно това от непосредственото им обкръжениеТова е изключително полезно при вложени обратни извиквания, таймери, обещания и навсякъде другаде, където с класическите функции трябваше да се борите с .bind или с трикове като const that = this;.
Практически примери за загуба и запазване this
Един от класическите проблеми в JavaScript е, че при дефиниране на функция вътре в метод, губиш препратката към this който сочеше към обекта и накрая стигате до глобалния или до undefined.
Нека вземем типичния случай на използване setTimeout в метод на обект с традиционна функция:
const persona = {
nombre: 'Agustin',
decirNombre: function() {
setTimeout(function() {
console.log(this.nombre);
}, 3000);
}
};
persona.decirNombre(); // Muestra undefined
тук това във функцията, предадена на setTimeout Това вече не е обектът personaТази функция за обратно извикване се изпълнява в глобалния контекст (в браузър, window), така this.nombre Опитва се да прочете свойство в глобалното, което не съществува, и в крайна сметка се оказва undefined.
Преди съществуването на стрелкови функции, често срещано решение е било да се съхранява стойността на this в спомагателна променлива за да го „плъзнете“ във функцията:
const persona = {
nombre: 'Agustin',
decirNombre: function() {
let that = this; // aquí this es persona
setTimeout(function() {
console.log(that.nombre);
}, 3000);
}
};
Благодарение на тази променлива се поддържа правилната препратка към обекта. Но това е донякъде грозен и повтарящ се трик. С функциите със стрелки този проблем е значително опростен:
const persona = {
nombre: 'Agustin',
decirNombre: function() {
setTimeout(() => {
console.log(this.nombre);
}, 3000);
}
};
Тук функцията със стрелка не създава своя собствена this, така наследява this на метода decirNombreкой е обектът personaРезултатът: „Agustin“ се показва правилно, без да са необходими междинни променливи или .bind.
извикване, прилагане и свързване: контролиране на стойността на this
В допълнение към „естествения“ начин за задаване на контекста чрез извикване на метод, JavaScript ни дава инструменти за принуждава стойността на this при нормални функции: call, apply y bind.
Методи call() y apply() Те извикват функцията незабавно, което ви позволява да предадете обекта, който искате да използвате като this. Разликата е в това call получава аргументите един по един, докато apply Те се получават в масив. bind(), вместо това, връща нова функция с this „прикачено“ към посочената от вас стойностза да можеш да ѝ се обадиш по-късно, когато ти е удобно.
При функциите със стрелки обаче тези методи не са полезни за промяна this защото стойността му е лексикално свързана. Можеш да използваш call, apply o bind да предава аргументи, но не и да променя контекста на стрелковите функции, което е много важна разлика с обикновените функции.
Основен синтаксис на функциите със стрелки
Отвъд поведението на thisФункциите със стрелки предоставят по-компактен и изразителен синтаксис за много ситуации. Общата форма е:
(arg1, arg2, ..., argN) => expresion
Тази форма автоматично връща резултата от израза вдясно от стрелката, така че Няма нужда да се пише думата return когато имате само един прост израз.
Някои общи синтактични точки:
- Без параметри:
() => 42или дори_ => 42ако не ви интересува името на аргумента. - С един единствен параметър:
Скобите са по избор, можете да напишетеx => x * 2o(x) => x * 2. - С множество параметри:
Скобите са задължителни:(x, y) => x + y.
Когато имате нужда от няколко оператора, можете да използвате тяло на блока с ключове:
const sumar = (x, y) => {
const resultado = x + y;
return resultado;
};
В този случай, тъй като има ключове, Вече няма имплицитно връщане; ако не сложите returnфункцията ще върне undefinedТова се отнася както за функциите със стрелки, така и за традиционните функции.
Връщане на литерални обекти със стрелкови функции
Има един малък, но много често срещан синтактичен детайл: когато функция със стрелка връща буквален обект директноТрябва да го оградите в скоби, за да не го сбърка интерпретаторът с блок.
Например:
x => ({ y: x })
Без тези скоби, JavaScript би интерпретирал къдравите скоби като начало на тялото на функцията, а не като обект. Това е прост трик, но причинява много глупави грешки, ако го забравите.
Функции със стрелки: анонимни и без прототип
Функциите на стрелките са синтактично анонименТе нямат собствени имена, което може донякъде да усложни нещата. съобщения за грешки и грешкизащото в трасирането не виждате директно идентификатора на функцията, освен ако не сте го присвоили на константа с разпознаваемо име.
Освен това, функциите на стрелките Те не притежават собственост prototype и те не могат да бъдат използвани като строителни фирмиАко се опитате да ги призовете с newЩе получите грешка. За да създавате обекти с помощта на конструктори или класове, все още трябва да използвате нормални функции или синтаксис. class.
Друго следствие е, че Те не са подходящи за шаблони, които изискват вътрешно саморефериране., като например някои форми на рекурсия или обработчици на събития, които трябва да се отпишат, използвайки this или собственото име на функцията.
Където функциите със стрелки блестят
Голямата сила на стрелковите функции е именно тяхната лексикално свързване на thisТе са идеални в ситуации, в които искате обратното извикване, което предавате на друга функция, да поддържа this на околността.
Например, в обект с метод, който стартира таймер и трябва да поддържа достъп свойствата на самия обект, използвайки this:
const contador = {
id: 42,
iniciar() {
setTimeout(() => {
console.log(this.id); // this es contador
}, 1000);
}
};
В ES5 беше обичайно да се налага да се поставя .bind(this) към обратното повикване или запазване this в друга променлива. С функции със стрелки, Кодът става по-чист и по-близо до действителното намерение..
Те са много практични и с методи за масиви, като например map, filter, reduce и компания, защото намаляване на синтактичния шум когато логиката на функцията е кратка:
const numeros = [1, 2, 3];
const dobles = numeros.map(n => n * 2);
Когато се използват пестеливо, тези компактни форми улесняват проследяването на потока от данни с един поглед.
Кога да избягвате функции със стрелки
Въпреки че функциите със стрелки са много полезни, те не са заместител на обикновените функции. Има няколко ясни случая, при които Най-добре е да не ги използвате.:
- Методи на обекти, които зависят от
this:
Ако дефинирате метод катоsaltos: () => { this.vidas--; }вътре в обектgato,thisНяма да сочи към котката, а към външната среда и свойството няма да се актуализира както очаквате. - Обратни извиквания на DOM събития, които се нуждаят от
thisдинамичен:
В манипулатор катоboton.addEventListener('click', () => { this.classList.toggle('on'); });,thisВероятно няма да е натиснатият бутон, а по-високият контекст, който ще ви даде грешка при въвеждане. - Конструктори или функции, които се нуждаят
prototype:
Тъй като не може да се използва сnewФункциите със стрелки не са подходящи за създаване на инстанции или за шаблони, базирани на прототипи.
Във всички тези случаи, a Нормалната функция остава подходящият инструмент защото това позволява this Динамично е свързано с начина, по който извиквате функцията.
Ако свикнете съзнателно да избирате между нормална функция и функция със стрелки в зависимост от това, от което се нуждаете this и от контекста, Вашият код ще бъде по-предсказуем и четим. за всеки, който го запази след това.
В крайна сметка, разбирането как стойността на this в JavaScript и как функциите със стрелки наследяват тази стойност Ключът към спирането на затрудненията с неочаквани резултати, възползването от синтактичната захар на ES6 и писането на методи, обратни извиквания и обработчици на събития, които правят точно това, което сте имали предвид, е разбирането на лексикалната област, в която са създадени.