Scope, Closure և Lexical Environment
Scope, Closure և Lexical Environment
Երբ գրում ես JavaScript կոդ, հաճախ տեսնում ես՝ ինչ-որ փոփոխականներ «հասանելի են», իսկ որոշներն՝ ուղղակի սխալ են տալիս։ Ինչու՞ է դա այդպես։ Ինչի՞ց է կախված, թե փոփոխականը որտեղ է հասանելի։ Պատասխանը գալիս է այս երեք կարևոր հասկացություններից՝ Scope, Closure և Lexical Environment։
Այս հոդվածում ամեն ինչ կբացատրեմ՝ շատ պարզ և օրինակներով։ Հասկանալով սա՝ կկարողանաս գրել ավելի ճշգրիտ և անվտանգ կոդ, ու չես վախենա “ReferenceError”-ներից։
Scope-ը (միջավայր) նշանակում է՝ որտեղից է փոփոխականը տեսանելի կամ հասանելի։ Այսինքն՝ JavaScript-ը որոշում է, արդյոք այս կամ այն փոփոխականը կարող ես օգտագործել տվյալ հատվածում, թե ոչ։
Գոյություն ունեն երեք հիմնական տեսակի scope-եր․
// Global Scope
let globalVar = "Hello";
function sayHi() {
// Function Scope
let name = "Anna";
console.log(globalVar); // հասանելի է
console.log(name); // հասանելի է
}
console.log(name); // Error: name is not defined
let և const պահվում են block scope-ում, իսկ var միայն function scope-ում։
if (true) {
let x = 10;
var y = 20;
}
console.log(x); // Error: x is not defined
console.log(y); // 20
Այս տարբերությունը կարևոր է՝ հասկանալու, թե երբ է փոփոխականը «ապրում» ու երբ «սատկում»։
Lexical Environment-ը մի fancy բառ է, որը իրականում նշանակում է՝ որտեղ է գրված քո կոդը։ JavaScript-ը որոշում է փոփոխականների հասանելիությունը՝ ըստ այն բանի, թե որտեղ ես դրանց գրել՝ **ոչ թե ըստ այն բանի, թե որտեղից ես կանչել**։
function outer() {
let name = "Armen";
function inner() {
console.log(name);
}
inner();
}
outer(); // կտպի Armen
Այս օրինակում՝ `inner()` ֆունկցիան կարող է օգտագործել `name` փոփոխականը, որովհետև գրած է նրա ներսում։ Սա է «lexical environment»-ի էությունը։ Եթե ֆունկցիան գրված է մի միջավայրում, այն հիշում է այդ միջավայրի փոփոխականները։
Closure-ը այն դեպքում է, երբ մի ֆունկցիա «հիշում է» իր արտաքին միջավայրի փոփոխականները՝ նույնիսկ երբ այդ ֆունկցիան կանչվում է այլ տեղից։
function outer() {
let counter = 0;
return function inner() {
counter++;
console.log("Counter:", counter);
};
}
const count = outer();
count(); // Counter: 1
count(); // Counter: 2
Ահա closure-ի կախարդանքը։ Թեև outer() արդեն ավարտվել է, նրա մեջ սահմանված inner() ֆունկցիան **շարունակում է հասանելիություն ունենալ counter-ին**։ Closure-ը «փակում է» արտաքին արժեքները իր հետ։
Այդ պատճառով closure-ները հաճախ օգտագործվում են private փոփոխականներ ստեղծելու համար։
function createUser(name) {
let password = "secret123";
return {
getName: function () {
return name;
},
checkPassword: function (guess) {
return guess === password;
}
};
}
const user = createUser("Ani");
console.log(user.getName()); // Ani
console.log(user.checkPassword("wrong")); // false
console.log(user.checkPassword("secret123")); // true
Այստեղ password փոփոխականը հասանելի է միայն closure-ով՝ արտաքինից հնարավոր չէ փոխել այն։ Սա նման է class-ի մեջ private դաշտ ունենալուն։
JavaScript-ում scope-ը որոշում է՝ որտեղ է փոփոխականը հասանելի, lexical environment-ը որոշում է՝ որ միջավայրում է ֆունկցիան ստեղծվել, իսկ closure-ը ֆունկցիային թույլ է տալիս «հիշել» այդ միջավայրը՝ անկախ կանչելու տեղից։
Եթե սա լավ հասկանալիս լինես՝ շատ ավելի վստահ կգրես կոդ, կկարողանաս debug անել և կկառուցես ավելի կոկիկ նախագծեր։