- Ang mga function ng arrow ay nagbibigay ng maigsi na syntax at capture
thisleksikal mula sa kanilang nakapaligid na saklaw, sa halip na lumikha ng sarili nilang pagkakaugnay. - Ang halaga ng mga
thissa mga regular na function ay nakadepende sa kung paano sila tinatawag, na nakakaapekto sa mga function, method, constructor, class, at callback. - Ang mga arrow function ay mainam para sa mga callback at array method, ngunit hindi magandang pagpipilian para sa mga object method, DOM event handler, at constructor.
- Pag-unawa kung kailan
thisAng pagkakaiba sa pagitan ng dinamiko at leksikal ay mahalaga upang maiwasan ang mga banayad na pagkakamali at upang pumili sa pagitan ng mga arrow at tradisyonal na tungkulin.
Kung nakapag-log ka na this sa iba't ibang function ng JavaScript at nakakuha ng iba't ibang resulta, hindi ka nag-iisa. Maraming developer ang nakakaranas ng mga pagkakataon kung saan ini-print ng isang method ang inaasahang object, habang ini-print naman ng arrow function window, at isang nested arrow ang biglang "mahiwagang" tumuturo pabalik sa nakapalibot na bagay. Ang pag-unawa kung bakit iyon nangyayari ang susi sa pagsulat ng nahuhulaan at walang bug na code.
Mga tungkulin ng palaso at ang this Ang keyword ay isa sa pinakamahalaga (at hindi nauunawaan) na mga kombinasyon sa modernong JavaScript. Ang mga arrow function ay parang mas maikling syntax lamang, ngunit sa huli ay binabago nila kung paano this ay pinangangasiwaan, kung paano kumikilos ang mga callback, at kahit kailan mo dapat o hindi dapat gamitin ang mga ito bilang mga pamamaraan. Talakayin natin ang lahat nang paunti-unti, mula sa syntax hanggang sa konteksto ng pagpapatupad, gamit ang simpleng Ingles at maraming praktikal na halimbawa.
Sintaks ng tungkulin ng palaso nang walang kalituhan
Ang mga arrow function ay mga function expression na isinusulat gamit ang => sintaks sa halip na ang function keyword Sa konsepto, maaari mong isipin ang mga ito bilang isang compact na paraan ng pagsulat: "kunin ang mga parameter na ito, suriin ang expression o block ng code na ito, at ibalik ang isang value." Sa ilalim, ang mga ito ay mga function pa rin, ngunit magkakaiba ang kanilang paggana sa ilang mahahalagang paraan.
Ang pinakasimpleng arrow function ay direktang tumutugma sa isang regular na function expression. Halimbawa, ang klasikong ekspresyon ng tungkuling ito:
const multiplyByTwo = function (value) { return value * 2; };
Maaaring isulat muli bilang isang arrow function tulad nito:
const multiplyByTwo = (value) => { return value * 2; };
Nagniningning ang mga function ng arrow kapag ang katawan ay iisang ekspresyon lamang. Kung ang katawan ng pahayag ay isa lamang pahayag na nagbabalik ng isang bagay, maaari mong alisin ang parehong kulot na panaklong at ang tahasang return, na nagpapagana ng isang implicit return:
const multiplyByTwo = value => value * 2;
Kapag may eksaktong iisang parameter, maaari mong alisin ang mga nakapalibot na panaklong, ngunit sa partikular na kasong iyon lamang. So x => x * 2 ay balido, ngunit kung mayroon kang zero o maraming parameter, dapat mong panatilihin ang mga panaklong:
- Mga parametrong sero:
() => 42 - Isang parametro:
x => x * 2or(x) => x * 2 - Dalawa o higit pang mga parameter:
(x, y) => x + y
Kapag kailangan mo ng higit sa isang pahayag sa katawan ng pangungusap, dapat kang gumamit ng mga kulot na panangga at isang tahasang return. Sa sitwasyong iyon, ang mga arrow function ay kumikilos tulad ng mga regular na function patungkol sa mga return: hindi return, walang ibinalik na halaga.
const feedCat = (status) => {
if (status === 'hungry') {
return 'Feed the cat';
} else {
return 'Do not feed the cat';
}
};
Mag-ingat kapag nagbabalik ng mga literal ng object mula sa mga arrow function, dahil ang mga brace ng object ay maaaring mapagkamalang isang function body. Para maiwasan ang kalabuan na iyon, balutin ang literal na bagay sa loob ng panaklong upang malaman ng JavaScript na ito ay isang ekspresyong ibabalik:
const toObject = value => ({ result: value });
Isa pang bagay: ang mga arrow function ay palaging mga expression, hindi kailanman mga deklarasyon. Nangangahulugan ito na dapat silang italaga sa isang variable, property, o ipasa bilang isang argument; hindi sila maaaring tumayo nang mag-isa tulad ng function myFunc() {}, at hindi sila itinataas sa parehong paraan tulad ng mga deklarasyon ng function, kaya hindi mo sila matatawag bago pa man sila ma-define.
Ano ang eksaktong ay this sa JavaScript?
Ang keyword this ay isang dynamic binding na nililikha ng JavaScript para sa iyo kapag nagsagawa ito ng isang function o isang class method. Maaari mo itong isipin bilang isang hindi nakikitang parameter na ang halaga ay nakadepende sa kung paano at saan tinatawag ang function. Ginagawa nitong makapangyarihan at flexible, ngunit isa rin itong malaking pinagmumulan ng kalituhan.
Sa isang hindi mahigpit na tungkulin, this palaging nagre-resolve sa isang uri ng object; sa strict mode, maaari itong literal na maging anumang value, kabilang ang undefined. Nagpapasya ang JavaScript sa halagang iyon batay sa konteksto ng pagpapatupad: regular na function, method call, constructor call, class, global scope, o arrow function.
Sa pinakamataas na antas ng isang klasikong iskrip (hindi isang modyul), this ay tumutukoy sa globalThis, na karaniwang sa browser window bagay. Kaya ang sumusunod na paghahambing sa isang browser ay magiging totoo:
console.log(this === window); // true
Sa mga tungkuling hindi mga palaso, this ay ganap na tinutukoy ng lugar ng tawag. Kung tumawag ka obj.method(), pagkatapos ay sa loob method ang halaga ng this is objKung kukunin mo ang parehong function na iyon at tatawagin itong standalone bilang fn() sa mahigpit na mode, this nagiging undefined; sa hindi mahigpit na mode, ang JavaScript ay "mga pamalit" this sa globalThis.
Mahalaga, ang mahalaga ay hindi kung saan tinukoy ang function, kundi kung paano ito tinatawag. Ang isang method ay maaaring manatili sa prototype chain o mailipat sa ibang object at makikita pa rin this gaya ng anumang bagay na aktwal na ginagamit sa oras ng pagtawag. Ang pagpasa ng isang pamamaraan ay kadalasang nagbabago nito this maliban na lang kung hayagan mo itong aayusin.
Mayroon ding mga kagamitan para sa pagkontrol this tahasan: call, apply, bind, at Reflect.apply. Binibigyang-daan ka nitong "iturok" ang ninanais this halaga: fn.call(obj, arg1, arg2) ipapatupad fn sa this itakda sa objAng parehong mga patakaran sa pagpapalit ay nalalapat sa hindi mahigpit na paraan: kung pumasa ka null or undefined as this, papalitan sila ng globalThis; ang mga primitive ay nakakabit sa kanilang mga wrapper object.
Ang mga callback ay nagdaragdag ng isa pang patong ng indirection, dahil this ay kinokontrol ng sinumang tatawag sa iyong callback. Mga pamamaraan ng pag-ulit ng array, ang Promise Ang constructor, at mga katulad na API ay karaniwang tumatawag ng mga callback gamit ang this itakda sa undefined (o ang pandaigdigang bagay sa sloppy mode). Ang ilang mga API, tulad ng Array.prototype.forEach or Set.prototype.forEach, tumanggap ng hiwalay thisArg parameter na magagamit mo para itakda ang mga callback this.
Sinasadya ng ibang mga API na tumawag ng mga callback gamit ang custom this halaga. Halimbawa, ang reviver pagtatalo sa JSON.parse at ang replacer para JSON.stringify tumanggap this nakatali sa object na nagmamay-ari ng property na kasalukuyang pinoproseso. Ang mga event handler sa DOM ay nakatali sa element na ikinakabit sa kanila kapag isinulat sa "klasikong" paraan.
Ang pangunahing ideya: ang mga arrow function ay hindi lumilikha ng sarili nilang this
Ang katangiang tumutukoy sa mga arrow function ay hindi sila kailanman lumilikha ng bago this nagbubuklod. Sa halip, isinasara nila (o "nahuhuli") ang this mula sa nakapalibot na leksikal na kapaligiran sa sandaling nalikha ang mga ito. Kapag ang palaso ay gumana sa ibang pagkakataon, muling ginagamit nito ang nakuhang halagang iyon, anuman ang paraan ng pagtawag mo rito.
Sa pagsasagawa, ang isang arrow function ay kumikilos na parang permanente itong naka-auto-bound sa this ng panlabas na saklaw nito. Ito ang dahilan kung bakit ang mga pamamaraan tulad ng call, apply, at bind hindi mababago this para sa isang arrow function: ang thisArg binabalewala lang ang argumento. Maaari ka pa ring magpasa ng mga regular na parameter sa mga ito, ngunit ang this naka-lock ang halaga.
Isaalang-alang ang snippet na ito sa pandaigdigang saklaw ng isang script file:
const arrow = () => console.log(this);
arrow();
Dahil ang palaso ay tinukoy sa pandaigdigang kodigo, ang this ay ang pandaigdigan this (karaniwang window sa isang browser script), at hindi iyon kailanman magbabago. Pagtawag arrow bilang isang simpleng function, ang pagtatalaga nito sa isang property, o pagpapasa nito ay palaging magtatala ng parehong global object kapag ginamit sa kontekstong ito.
Ang talagang kawili-wiling pag-uugali ay lumilitaw kapag naglagay ka ng mga arrow function sa loob ng mga regular na function o method. Dahil kinukuha ng palaso ang panlabas na tungkulin this, ito ay nagiging isang makapangyarihang kasangkapan para sa mga callback na kailangang sumangguni pabalik sa kanilang naglalamang object nang walang karaniwang .bind(this) seremonya
const counter = {
id: 42,
start() {
setTimeout(() => {
console.log(this.id); // uses counter.id
}, 1000);
},
};
If start ay gumagamit ng tradisyonal na anonymous na function sa loob setTimeout, kakailanganin mong manu-manong i-bind this o i-save ito sa isang variable. Gamit ang mga arrow, natural na namamana ang callback this mula start, Kung saan ay counter, Kaya this.id mga kopya 42 tulad ng inilaan
Ipinapaliwanag din ng leksikal na pagbubuklod na ito ang klasikong "bakit this tanong na "baguhin" kapag gumagamit ng mga arrow sa mga literal ng object. Tingnan ang dalawang bagay na ito:
const obj1 = {
speak() {
console.log(this);
}
};
const obj2 = {
speak: () => {
console.log(this);
}
};
Pagtawag obj1.speak() mga kopya obj1, Dahil speak ay isang regular na pamamaraan at this ay nakatakda batay sa lugar ng tawag. Sa pamamagitan ng kaibahan, obj2.speak() nagla-log sa panlabas this (madalas window sa mga browser), dahil hindi ginagamit ng arrow ang object bilang nito thisAng literal na bagay mismo ay hindi lumilikha ng bago this saklaw; tanging ang katawan ng function lamang ang gumagawa niyan, at nilalaktawan ng mga arrow function ang hakbang na iyon.
Ngayon isaalang-alang ang isang object method na lumilikha at agad na tumatawag ng inner arrow:
const obj3 = {
speak() {
(() => {
console.log(this);
})();
}
};
obj3.speak();
Sa sitwasyong ito, ang panloob na arrow function ay magmamana this mula speak, Kung saan ay obj3 kapag tinawag bilang obj3.speak(). Kahit na ang arrow ay isang nested, agad na tinatawag na function, nakaturo pa rin ito sa obj3, hindi ang pandaigdigang bagay. Iyan ang esensya ng leksikal this: sinusundan nito ang nakapalibot na saklaw, hindi ang lugar ng tawag ng palaso mismo.
this sa mga function, object, at constructor
Para talagang maging dalubhasa sa mga tungkulin ng palaso at this, nakakatulong na makita kung paano this gumagana sa bawat pangunahing konteksto: mga regular na function, methods, constructor, classes, at ang pandaigdigang saklaw. Kapag malinaw na ang mga patakarang iyon, mas madaling mangatwiran ang pag-uugali ng palaso.
Sa isang payak na punsiyon (hindi palaso), this ay 100% nakasalalay sa kung paano ginagamit ang function. Kung tumawag ka fn() sa mahigpit na mode, this is undefined; sa sloppy mode, ang pagpapalit ay gumagawa this maging globalThisKung tatawag ka obj.fn(), Pagkatapos this is objIlipat fn sa ibang bagay o sa isang baryabol at ang halaga ng this ay kikilos nang naaayon.
Sa isang pamamaraang tinukoy sa isang literal na bagay, this ay ang bagay na pinaggagamitan ng paraan, hindi kinakailangan ang bagay kung saan orihinal na tinukoy ang paraan. If obj.__proto__ may hawak na method at tatawagin mo obj.method(), pagkatapos ay sa loob method, this is obj, hindi ang prototipo.
Ang mga konstruktor ay isa pang espesyal na kaso: kapag tinawag mo ang isang function gamit ang new, this ay nakatali sa bagong gawang instance ng object. Halimbawa, sa function User(name) { this.name = name; }, tumatawag new User('Alex') set this sa bago User object. Kung ang constructor ay tahasang nagbabalik ng isang non-primitive object, ang ibinalik na object na iyon ay papalit sa this bilang ang pangwakas na halaga ng new pagpapahayag.
Ang syntax ng klase ay nakabatay sa mga patakarang ito na may dalawang pangunahing konteksto: instance at static. Sa loob ng isang constructor o isang instance method, this tumuturo sa class instance na iyong ginagamit. Sa loob ng mga static na pamamaraan o mga static na initialization block, this tumutukoy sa klase mismo (o sa hinangong klase kapag tinawag sa pamamagitan ng mana). Ang mga patlang ng instance ay sinusuri gamit ang this nakatali sa bagong pagkakataon; nakikita ang mga static na patlang this bilang tagapagbuo ng klase.
Ang mga derivative class constructor ay kumikilos nang bahagyang naiiba: hanggang sa tawagin mo super(), walang magagamit this. Pagsusumikap super() nagpapasimula this sa pamamagitan ng pagtatalaga sa base constructor; ang pagbabalik bago gawin iyon sa isang derived constructor ay pinapayagan lamang kung tahasan kang magbabalik ng ibang object.
Sa pandaigdigang konteksto, this nakadepende sa kung paano binabalot at pinapatakbo ng JavaScript environment ang iyong code. Sa isang klasikong script ng browser, nasa pinakamataas na antas this ay ang pandaigdigang bagay; sa isang ES module, pinakamataas na antas this ay laging undefinedAng mga module ng Node.js CommonJS ay internally wrapped at karaniwang isinasagawa gamit ang this itakda sa module.exportsAng mga inline na katangian ng event handler sa HTML ay isinasagawa gamit ang this nakatakda sa elementong ikinakabit ang mga ito.
Isang banayad ngunit mahalagang detalye: ang mga literal ng bagay mismo ay hindi nagpapakilala ng bago this saklaw. Pagsulat const obj = { value: this }; sa loob ng isang script ay gagawin obj.value pantay ang panlabas this, hindi ang object. Tanging ang mga function body (at class body) ang lumilikha ng isang nakalaang this pagbubuklod; sadyang nilalaktawan ng mga palaso ang hakbang na ito at nagmamana.
Bakit mahusay ang mga arrow function para sa mga callback (at kapag hindi)
Dahil ang mga arrow ay gumagana malapit sa ibabaw this, ang mga ito ay perpektong akma para sa maraming senaryo ng callback kung saan gusto mong patuloy na tumukoy ang callback sa nakapalibot na bagay o konteksto. Ito ay partikular na madaling gamitin sa mga timer, promise, at mga array method tulad ng map, filter, at reduce.
Isipin ang isang pamamaraan na kailangang paulit-ulit na i-update ang ilang ari-arian gamit ang setInterval. Gamit ang isang tradisyonal na tungkulin, this sa loob ng callback ay magiging default sa global object (o maging undefined sa mahigpit na mode), kaya this.count hindi ituturo ang iyong instance. Gamit ang isang arrow function, natural na ginagamit ng callback ang this ng panlabas na pamamaraan.
function Counter() {
this.count = 0;
setInterval(() => {
this.count++;
}, 1000);
}
Salamat sa palaso, this sa loob ng interval callback ay tumutukoy sa Counter halimbawa, hindi window. Kung ang callback na iyon ay isang regular na function, kakailanganin mo .bind(this) o isang intermediate variable tulad ng const self = this; para mapanatili ang sanggunian.
Pinapasimple rin ng mga arrow function ang code gamit ang mga array method, kung saan madalas ay wala kang pakialam sa this sa lahat. Kapag ipinasa mo ang isang tradisyonal na function bilang isang callback, ang implicit this ay karaniwang undefined, at baka makalimutan mo iyon. Ipinapakita ng mga arrow na ang function ay isa lamang purong pagmamapa ng mga input sa mga output.
const numbers = [1, 2, 3];
const doubled = numbers.map(n => n * 2);
Gayunpaman, may mga mahahalagang pagkakataon kung saan ang mga arrow function ay ang maling pagpili, lalo na kapag kailangan mo ng isang dynamic this. Dalawang klasikong anti-pattern ang gumagamit ng mga arrow function bilang mga object method at bilang mga DOM event handler na umaasa sa this pagiging elemento.
Isaalang-alang ang isang bagay na sumusubaybay sa buhay ng isang pusa:
const cat = {
lives: 9,
jump: () => {
this.lives--; // bug: this is not cat
},
};
cat.jump();
Dahil sa jump ay isang palaso, this hindi tumutukoy sa cat ngunit sa kahit ano pa man this ay kung saan nilikha ang literal na bagay (kadalasan ang pandaigdigang bagay). Ang sinadya this.lives-- alinman sa paghagis (sa strict mode) o tahimik na pag-mutate ng isang bagay na walang kaugnayan. Ang paggamit ng isang regular na syntax ng method dito ang tamang hakbang.
Magkatulad ang mga tagapakinig ng kaganapan sa DOM: ang karaniwang padron this.classList.toggle('on') sa loob ng isang event callback ay nakasalalay sa this bilang elementong nagpasimula ng kaganapan. Gamit ang isang arrow function, this hindi na nakaturo sa elemento, kaya nasisira ang code.
const button = document.getElementById('press');
button.addEventListener('click', () => {
this.classList.toggle('on'); // this is not button
});
Sa sitwasyong ito, ang handler ay dapat na isang normal na tungkulin upang this ay nakatali ng browser sa elementong button. Ang mga arrow function ay hindi gumagana bilang mga drop-in na pamalit kung inaasahan ng iyong lohika this upang maging ang dinamikong target ng kaganapan.
Ang isa pang banayad na disbentaha ay ang mga function ng arrow ay hindi nagpapakilala sa syntax. Kadalasan ay wala silang sariling pangalan (maliban sa anumang variable na nakatalaga sa kanila), na maaaring gawing mas hindi gaanong naglalarawan ang mga stack trace at mas mahirap ang recursion. Sa karamihan ng mga totoong code, isa itong madaling palitan, ngunit sulit itong tandaan.
Mga espesyal na kaso: mga getter, setter, mga pamamaraan ng nakatali at mga kakaibang sulok
Ang mga getter at setter ay sumusunod sa parehong tuntunin ng "call site": this ay ang bagay kung saan naa-access ang property, hindi ang kung saan ito orihinal na tinukoy. Kung ang isang getter ay minana mula sa isang prototype at tinawag mo ito sa isang nagmula na bagay, this Ang nasa loob ng getter ay tumutukoy sa hinangong bagay.
Mga pamamaraang nakatali na nilikha gamit ang Function.prototype.bind magbibigay sa iyo ng pag-uugali na medyo katulad ng mga arrow function, ngunit sa antas ng mga normal na function. Kapag tumawag ka f.bind(obj), lumikha ka ng isang bagong function na ang this ay permanenteng nakatakda sa obj, kahit paano pa ito gamitin. Maaari itong maging kapaki-pakinabang sa mga klase kapag kailangan mong panatilihin this kahit na ang isang pamamaraan ay hiwalay.
class Example {
constructor() {
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log(this); // always the instance
}
}
Ang downside ng parehong bound methods at arrow functions na ginagamit bilang mga instance field ay ang bawat instance ay nakakakuha ng sarili nitong kopya ng function, na maaaring magpataas ng paggamit ng memory. Karaniwang katanggap-tanggap ang ganitong trade-off kapag maliit na bilang lamang ng mga madalas na hiwalay na method ang iyong bind, ngunit ito ay isang bagay na dapat malaman sa performance-critical code.
Mayroon ding ilang mga lumang kaso sa sulok kung saan this kumikilos nang iba, tulad ng sa loob ng isang hindi na ginagamit with pahayag. Sa loob ng a with (obj) { ... } bloke, na tumatawag sa isang function na isang katangian ng obj epektibong kumikilos na parang ikaw ang sumulat obj.method(), Kaya this ay nakasalalay sa objDapat iwasan ng modernong code with, ngunit ang pag-unawa sa eksepsiyon na ito ay nagpapaliwanag na this ay nakadepende pa rin sa kung paano nabubuo ang isang function call.
Ang mga inline event handler sa HTML ay mayroon ding espesyal na panuntunan: nakikita ng nakapalibot na inline handler code this bilang elemento, ngunit ang mga panloob na tungkuling tinukoy sa loob ng handler na iyon ay babalik sa regular this panuntunan. Kaya ang isang panloob na tradisyonal na tungkulin, na hindi nakatali sa anuman, ay karaniwang makakakita this as globalThis (O undefined sa strict mode), hindi ang elemento.
Panghuli, tandaan na ang mga arrow function ay walang prototype ari-arian at hindi maaaring gamitin bilang mga constructor na may new. Pagtatangka new MyArrow() magtatapon ng TypeError. Kung kailangan mo ng function na maaaring magsilbing constructor, dapat kang gumamit ng regular na function o class.
Ang pagsasaalang-alang sa mga detalyeng ito ay ginagawang mas madali ang pagpili sa pagitan ng mga arrow function at mga tradisyonal na function. Gumamit ng mga arrow kung saan mo gustong gamitin ang leksikal this at maigsi at sintaks, at babalik sa mga regular na function tuwing kailangan mo ang dynamic, call-site-driven this semantika ng pag-uugali o konstruktor.
Kapag naunawaan mo na kung paano this ay nakatali sa bawat sitwasyon, ang mga function ng arrow ay nagiging isang makapangyarihang kakampi sa halip na isang nakakagulat na mapagkukunan ng mga bug. Pinapadali nila ang mga karaniwang pattern tulad ng mga callback at simpleng transpormasyon, habang ang mga regular na function ay patuloy na humahawak sa mga tungkuling umaasa sa kanilang sarili. this pagbigkis, tulad ng mga pamamaraan, mga konstruktor, at mga dynamic na tagapangasiwa ng kaganapan.