this բանալի բառը JavaScript ծրագրավորման լեզվում: Օգտագործման առանձնահատկությունները՝ բացատրություններով: Ինչո՞վ է այն տարբերվում այլ օբյեկտ կողմնորոշված լեզուներում առկա նույնատիպ հասկացությունից:
this բանալի բառի վարքագիծը JavaScript-ում բավականին մեծ տարբերություններ ունի այլ ծրագրավորման լեզուներում՝ մասնավորապես Java-ում և PHP-ում նույնանման բանալի բառի դրսևորած վարքագծից։ Երևի նաև այս պատճառով է, որ այն դժվար է ընկալվում այլ ծրագրավորման լեզուներից հետո JavaScript սովորող ծրագրավորողների մոտ՝ պատճառ դառնալով դժգոհությունների, թե իբր այն տարօրինակ ձևով է աշխատում։
Իրականում ոչ մի տարօրինակություն գոյություն չունի, ուղղակի շատ ծրագրավորման լեզուներում ընդունված է, որ this-ը պետք է լինի ֆիքսված՝ այսինքն մեթոդը, որը ստեղծվել է օբյեկտի մեջ, որպես this-ի արժեք իր մեջ միշտ կրում է հղումը դեպի այն օբյեկտը, որի մեջ ստեղծվել է։ JavaScript-ում այդպես չէ, այստեղ this-ը ֆիքսված չէ, այն «ազատ» է, և ընդհանրապես կապ չունի թե մեթոդը որ օբյեկտի մեջ է ստեղծվել, կապ ունի միայն, թե որ օբյեկտն է տվյալ մեթոդը կանչում։
Բնականաբար խոսել այն մասին թե դա լավ է կամ վատ, իրականում անիմաստ է։ «Ազատ» this-ը համեմատած ֆիքսված this-ի հետ, ունի ինչպես մեծ առավելություններ, այնպես էլ մի շարք թերություններ։ Նախ դա մեզ հնարավորություն է տալիս ֆունկցիան բազմակի անգամ օգտագործել որպես մեթոդ՝ տարբեր օբյեկտների վրա, հետևաբար այն լեզվին տալիս է նաև բարձր ճկունություն՝ անհասանելի ծրագրավորման լեզուների մեծ մասի համար։ Մյուս կողմից չափազանց բարձր ճկունությունն իր հերթին մեծացնում է սխալներ անելու հավանականությունը։ Հետևաբար մեզ անհրաժեշտ է լավ պատկերացնել այն, որպեսզի կարողանանք օգտագործել բոլոր առավելությունները, որը մեզ տալիս է «ազատ» this-ի կոնցեպտը, և հնարավորինս խուսափել բոլոր այն խնդիրներից և վտանգներից, որն իր հետ բերում է լեզվի չափազանց բարձր ճկունությունը։
Մինչև առաջ անցնելը նախ փորձենք հասկանալ թե ինչ են իրենցից ներկայացնում ՄԵԹՈԴՆԵՐԸ։ Կան բազմաթիվ բնութագրումներ և ձևակերպումներ թե ինչ է մեթոդը։ Կարճ և կոնկրետ՝ JavaScript-ում մեթոդը ֆունկցիա է, որը պահվում է օբյեկտի մեջ։ Որպեսզի մենք օբյեկտի մեջ պահվող մեթոդին հասանելիություն ստանանք՝ կարող ենք օգտագործել կետ . օպերատորը։ Ու ընդհանրապես, ինչպես գիտենք, օբյեկտի հատկությանը կամ մեթոդին հասանելիություն ստանալու համար կա՛մ պետք է օգտագործենք կետ օպերատորը, կա՛մ էլ ինչպես ընդունված է ասել քառակուսի փակագծերը []:
Այժմ ստեղծենք որևէ օբյեկտ, որն իր մեջ կունենա հատկություններ և մեթոդներ, ապա այդ օբյեկտի վրա փորձենք հասկանալ թե ինչ է this-ը։
const person = {
firstName: "John",
lastName: "Smith",
age: 25,
func: function () {
console.log(this);
},
};
Ունենք սովորական օբյեկտ, որի մեջ կան հատկություններ, որոնք կրում են ինֆորմացիա person օբյեկտի անվան, ազգանվան և տարիքի մասին։ Օբյեկտի մեջ կա նաև func մեթոդը (Ինչպես հիշում ենք JavaScript-ում մեթոդը օբյեկտի մեջ գտնվող ֆունկցիա է), որի կանչը պետք է ընդամենը կոնսոլում տպի this-ի արժեքը։ Ինչպե՞ս կարող ենք դրսից կանչել այդ մեթոդը՝ պետք է գրենք այն օբյեկտի անունը, որի մեջ գտնվում է մեթոդը, ապա կետ օպերատորը, հետո մեթոդի անունը և վերջում փակագծերը, որոնք ինչպես գիտենք օգտագործվում են ֆունկցիաների/մեթոդների կանչի համար։
person.func(); // {firstName: "John", lastName: "Smith", func: ƒ}
Եվ այսպես, կանչելով person օբյեկտի func մեթոդը, որը պետք է կոնսոլում ուղղակի տպեր this-ը, ստանում ենք նույն person օբյեկտը։
Այժմ դիտարկենք ուրիշ օրինակ, որպեսզի ավելի լավ պատկերացնենք թե ինչ է this-ը։ Ստեղծենք ոչ թե մեթոդ, այլ սովորական ֆունկցիա, որը կանչի դեպքում պետք է կոնսոլում տպի this-ը։
function foo() {
console.log(this);
}
Եթե այժմ այս ֆունկցիան կանչենք՝ ապա կախված նրանից, թե մենք խիստ ռեժիմում ենք, թե ոչ, կստանանք տարբեր արդյունքներ։ Հիշեցնեմ թե ինչ է խիստ ռեժիմը։ Քանի-որ JavaScript-ը ստեղծվել էր անհավանական կարճ ժամկետում՝ 10 օրից մինչև 2 շաբաթ՝ ըստ տարբեր աղբյուրների, նրա մեջ շատ կային կառուցվածքային թերություններ, որոնք չափազանց դժվարացնում էին լեզվի օգտագործումը, և ES5 ստանդարտում որոշեցին ավելացնել խիստ ռեժիմը, որտեղ այդ կառուցվածքային թերություններից շատերն ուղղված են, և կոդը որոշ դեպքերում կարող է այլ կերպ աշխատել։ Որպեսզի ապահովեն հետադարձ համատեղելիություն, և մինչ այդ գրված սքրիփթները «փուլ չգան», այդ ռեժիմը ակտիվ չէ։ Եթե ուզում ենք մեր գրած կոդը ենթարկվի խիստ ռեժիմի կանոններին, պետք է սքրիփթի սկզբնամասում գրվի "use strict" հրահանգը։ Հրահանգը գրել պետք չէ, եթե մենք օգտագործում ենք մոդուլներ կամ կլասսների սինթաքսը, այդ դեպքում այն ավտոմատ ակտիվանում է։ Խիստ ռեժիմի մասին մանրամասն կարդացեք այստեղ։
Եվ այսպես փորձենք foo ֆունկցիան կանչել սկզբից սովորական, իսկ հետո խիստ ռեժիմում և տեսնենք թե this-ը կախված ռեժիմից ինչ արժեքներ է ընդունում։
foo(); // {window: Window, self: Window, …}
Բրաուզերի միջավայրում ֆունկցիայի կանչը կոնսոլում կտպի Window գլոբալ օբյեկտը, node.js-ի միջավայրում՝ Object[global] գլոբալ օբյեկտը։ Այսինքն սովորական ռեժիմում ինտերպրետատորը ֆունկցիաներին որպես this-ի սկզբնական արժեք, կապում է գլոբալ օբյեկտը։
"use strict";
foo(); // undefined
Խիստ ռեժիմում այդպիսի սկզբնական վերագրում չի լինում, և ֆունկցիայի կանչը թե բրաուզերային, թե node.js-ի միջավայրերում վերադարձնում է undefined, այսինքն ֆունկցիայի this-ը որոշված չէ, գոյություն չունի։ Սա տրամաբանական է, քանի որ ֆունկցիան մենք չենք ստեղծել որևէ օբյեկտի մեջ, այն չի հանդիսանում որևէ օբյեկտի մեթոդ, հետևաբար նրա this-ն էլ անորոշ է, գոյություն չունի։
Այժմ ստեղծենք մեկ այլ ֆունկցիա, որը պետք է ուղղակի կոնսոլում տպի ողջույնի խոսք։ Անվանենք այն ասենք թե greeting:
function greeting() {
console.log("hello " + this.firstName);
}
Ինչպես տեսնում ենք այս ֆունկցիայի կանչը կոնսոլում պետք է տպի "hello" ողջույնի խոսքը, ուղղված this.firstName-ին։ Բայց մենք տեսանք, որ ժամանակակից ռեժիմում՝ խիստ ռեժիմում, ֆունկցիայի this-ը անորոշ է, ֆունկցիան ոչ մի օբյեկտի մեջ ստեղծված չէ, հետևաբար նրա this-ը undefined է։ Հիմա եթե ֆունկցիան կանչենք, կստանանք սխալ, քանի-որ ինտերպրետատորը փորձելու է this-ից կարդալ firstName հատկությունը, իսկ քանզի this-ը դա undefined է, կոնսոլում կստանանք Uncaught TypeError։
greeting(); // Uncaught TypeError
Իսկ այժմ փորձենք այս ֆունկցիան կանչել մեր person օբյեկտի կոնտեքստում։ Օբյեկտի մեջ ստեղծենք sayHello մեթոդը, որին կվերագրենք մեր ստեղծած greeting ֆունկցիան։
const person = {
firstName: "John",
lastName: "Smith",
age: 25,
func: function () {
console.log(this);
},
sayHello: greeting,
};
Այստեղ ոչ մի բարդ բան չենք արել։ Ուղղակի նույն person օբյեկտի մեջ ավելացրել ենք sayHello մեթոդը, որը հղվում է օբյեկտից դուրս ստեղծված greeting ֆունկցիային։ Այսինքն երբ կանչում ենք sayHello մեթոդը՝ դա նույնն է, թե մենք կանչում ենք greeting ֆունկցիան, ուղղակի այդ կանչը կատարվում է օբյեկտի մեջից։
person.sayHello(); // hello John
Ամեն ինչ տրամաբանական է՝ ինչպես ամենավերևում գտնվող օրինակի մեջ համոզվել էինք, person օբյեկտի մեջ գտնվող մեթոդի համար որպես this հանդես էր գալիս հենց person օբյեկտը, հետևաբար person օբյեկտի մեջից կանչելու դեպքում "hello " + this.firstName արտահայտությունը համարժեք է "hello " + person.firstName արտահայտությանը։
Ստեղծենք մեկ ուրիշ օբյեկտ, նման person օբյեկտին, ասենք թե person2:
const person2 = {
firstName: "Joseph",
lastName: "Biden",
age: 78,
sayHello: greeting,
};
person2 օբյեկտի մեջ նույնպես պարունակվում են հատկություններ, որոնք նկարագրում են օբյեկտի անունը, ազգանունը և տարիքը։ Կա նաև sayHello մեթոդը, որը կրկին հղվում է greeting ֆունկցիային։ Կետ օպերատորի օգնությամբ հասանելիություն ստանանք person2 օբյեկտի պարունակությանը, և կանչենք sayHello մեթոդը։
person2.sayHello(); // hello Joseph
Ինչպես տեսնում ենք թե՛ person և թե՛ person2 օբյեկտների մեջ գտնվող sayHello մեթոդը հղվում է նույն greeting ֆունկցիային, սակայն կախված նրանից, թե որ օբյեկտի վրա ենք կանչում ֆունկցիան, this-ը հանդիսանում է հենց այդ օբյեկտը։
Եվ այստեղ ձևակերպենք this-ի ամենակարևոր կանոնը։ Երբ ֆունկցիան կանչվում է մեթոդի սինթաքսով՝ object.method(), կանչի ժամանակ this-ը ՄԻՇՏ հանդիսանում է ԿԵՏԻՑ առաջ եղած օբյեկտը։ Մենք կարող ենք ստեղծել տաս, հարյուր, հազար այդպիսի օբյեկտներ, և նրանց մեջ ներդնել մեթոդ, որը ինչպես person-ի և person2-ի դեպքում՝ հղում կունենա մի ֆունկցիայի, որի մարմնում օգտագործվում է this բանալի բառը։ Այդ this-ը տարբեր օբյեկտների դեպքում կունենա տարբեր արժեքներ, այսինքն եթե կանչվել է person օբյեկտի վրա, this-ը կլինի person օբյեկտը, եթե կանչվել է person2 օբյեկտի վրա, ապա this-ը կհանդիսանա person2 օբյեկտը, եթե կանչվել է ինչ-որ ասենք թե admin օբյեկտի վրա, this-ը կհանդիսանա admin օբյեկտը և այդպես շարունակ։
Եթե հիմա ստեղծենք մեկ այլ օբյեկտ, ասենք թե visitor, և նրա մեջ լինի մեթոդ, որը կհղվի greeting ֆունկցիային, որին իրենց հերթին հղում ունեն նաև person և person2 օբյեկտների մեջ գտնվող մեթոդներ, կախված թե որ օբյեկտի կոնտեքստում է կանչվելու այդ ֆունկցիան, մենք որպես this-ի արժեք կունենաք տվյալ օբյեկտը։
const visitor = {
firstName: "George",
lastName: "Washington",
sayHello: greeting,
};
Հիմա եթե կանչենք visitor օբյեկտի sayHello մեթոդը, որը կրկին հղվում է greeting ֆունկցիային, ապա ըստ this-ի ամենակարևոր կանոնի, որն է՝ կետից առաջ գրված օբյեկտը հանդիսանում է տվյալ ֆունկցիայի this-ը, մենք որպես this-ի արժեք կունենանք visitor օբյեկտը։
visitor.sayHello(); // hello George
Իսկ ի՞նչ կլինի, եթե մենք օբյեկտի մեթոդը վերագրենք մեկ ուրիշ փոփոխականի, և փորձենք մեթոդը կանչել այդ փոփոխականի օգնությամբ։ Օրինակ person2-ի sayHello մեթոդը վերագրենք ասենք թե ինչ-որ func փոփոխականի։
const func = person2.sayHello;
Հիմա func փոփոխականի մեջ մենք պատճենել ենք person2 օբյեկտի sayHello մեթոդը։ Ավելի ճիշտ՝ քանի-որ ֆունկցիաները նույնպես հանդիսանում են օբյեկտներ, այսինքն հղումային տիպ են, func փոփոխականի մեջ մենք հիմա ունենք ոչ թե հենց person2 օբյեկտի sayHello մեթոդը ուղղակիորեն՝ այլ հղումը դեպի նա։ Փորձենք կանչել func ֆունկցիան՝
func(); // Uncaught TypeError
Մենք ստանում ենք տիպային սխալ։ Բայց չէ՞ որ func փոփոխականը նույնպես հղվում է person2 օբյեկտի sayHello մեթոդի վրա։ Այո՛, հղվում է, բայց մենք func()-կանչում ենք ուղղակիորեն, ֆունկցիայի կանչից առաջ չկա կետ, որից առաջ օբյեկտ, որն էլ հենց հանդիսանալու էր this-ը։ Մենք func-ը փաստացի որևէ օբյեկտի մեջից չենք կանչում, իսկ ինչպես արդեն գիտենք, եթե ֆունկցիան չի հանդիսանում մեթոդ, չի կապված որևէ օբյեկտի հետ, գլոբալ տարածության մեջ նրա կանչի դեպքում this === undefined, եթե իհարկե խիստ ռեժիմը ակտիվ է։ Իսկ փորձելով undefined արժեքից կետ օպերատորի օգնությամբ հասանելիություն ստանալ firstName հատկությանը, մենք բնականաբար կստանանք տիպային սխալ, քանի-որ undefined-ը օբյեկտ չէ, որ տրված հատկությունն ունենա կամ չունենա, այն պրիմիտիվ տիպ է։
Սա այն ամենակարևոր հիմքն է, որն անհրաժեշտ է իմանալ this բանալի բառի մասին։ Մնացած ամեն ինչ հասկանալի կլինի, եթե հասկացվի այսքանը (ֆունկցիայի call, apply և bind մեթոդները և այլն)։ Ամփոփելով այս թեման, կարելի է շեշտել this-ի աշխատանքի հիմնական կարևոր առանձնահատկություններն ու կանոնները՝
JavaScript-ում this-ը տարբերվում է մի շարք այլ հանրաճանաչ ծրագրավորման լեզուների this-ի ցուցաբերած վարքագծից՝ նրա արժեքը որոշվում է միայն կոդի կատարման ժամանակ։ Այսինքն ցանկացած ֆունկցիա հայտարարելուց, մենք նրա մեջ կարող ենք օգտագործել this-ը։ Սակայն այդ this-ը իմաստ չի ունենա այնքան ժամանակ, քանի դեռ մենք ֆունկցիան չենք կանչել ինչ-որ կոնտեքստում։
Երբ ֆունկցիայի կանչը տեղի է ունենում գլոբալ տարածության մեջ, ապա this-ի արժեքը լինում է անորոշ՝ undefined: Եթե կոդը գրելիս չի օգտագործվում ժամանակակից խիստ ռեժիմը, ապա this-ի արժեքը դառնում է գլոբալ օբյեկտը։
Երբ ֆունկցիան կանչվում է մեթոդի սինթաքսով՝ object.method(), ապա this-ը ՄԻՇՏ հանդիսանում է ԿԵՏ-ից առաջ գտնվող ՕԲՅԵԿՏԸ։