చాలా మంది JavaScript నేర్చుకోవడం ప్రారంభించేటప్పుడు నేరుగా ప్రాజెక్ట్లు చేయడం ద్వారా మొదలుపెడతారు. మీరు ఒక బటన్ను కనెక్ట్ చేస్తారు, కొంత డేటాను ఫెచ్ (fetch) చేస్తారు మరియు DOM మారడాన్ని గమనిస్తారు. అప్పుడు అబ్స్ట్రాక్షన్స్ విఫలమవుతాయి (abstractions leak). అర్థం కాని బగ్స్ బయటపడతాయి: వేరియబుల్స్ వాటి డిక్లరేషన్ కంటే ముందే ఉనికిలో ఉంటాయి, ఫంక్షన్లు తనకు సంబంధం లేని వేరియబుల్స్ను యాక్సెస్ చేస్తాయి, మరియు this కీవర్డ్ విండోను, ఒక బటన్ను లేదా అసలు దేనినీ సూచించకుండా ఉంటుంది. ఇంజిన్ లోపల నిజంగా ఏం జరుగుతుందో అర్థం చేసుకోవడానికి మీరు లోతుగా పరిశీలించాల్సిన సమయం వచ్చిందని మీరు గ్రహించే క్షణం అది.
Execution Context: రెండు దశల సెటప్
మీ బ్రౌజర్ లేదా Node.js లోని JavaScript ఇంజిన్ ఒక స్క్రిప్ట్ను ఎదుర్కొన్నప్పుడు, అది ఒక వ్యక్తి పేజీని చదివినట్లుగా పై నుండి కిందకు కేవలం చదవదు. బదులుగా, అది ఒక 'ఎగ్జిక్యూషన్ కాంటెక్స్ట్' (execution context)ను నిర్మిస్తుంది, ఇది ఒక నిర్దిష్ట కోడ్ ముక్కను రన్ చేయడానికి అవసరమైన అన్నింటినీ కలిగి ఉండే ఒక కంటైనర్. ప్రతి ఎగ్జిక్యూషన్ కాంటెక్స్ట్ రెండు విభిన్న దశల ద్వారా వెళుతుంది.
మెమరీ క్రియేషన్ ఫేజ్ (Memory Creation Phase). ఈ ప్రారంభ దశలో, ఇంజిన్ మొత్తం స్కోప్ను స్కాన్ చేసి, అది కనుగొన్న ప్రతి వేరియబుల్ మరియు ఫంక్షన్ డిక్లరేషన్ కోసం మెమరీని కేటాయిస్తుంది. అది ఒక varను చూస్తే, దానికి కొంత స్థలాన్ని కేటాయించి, ప్లేస్హోల్డర్గా undefinedను నిల్వ చేస్తుంది. ఒకవేళ అది ఫంక్షన్ డిక్లరేషన్ను చూస్తే, పూర్తి ఫంక్షన్ బాడీని నిల్వ చేస్తుంది. అందుకే సాంప్రదాయ function కీవర్డ్తో డిక్లేర్ చేసిన ఫంక్షన్ను, అదే స్కోప్లో అంతకంటే ముందు ఉన్న లైన్ల నుండి కూడా పిలవవచ్చు. ఇంజిన్ దానిని అమలు చేయడం ప్రారంభించకముందే దాని గురించి తెలుసుకుంటుంది.
కోడ్ ఎగ్జిక్యూషన్ ఫేజ్ (Code Execution Phase). ఇప్పుడు ఇంజిన్ మీ కోడ్ను లైన్ బై లైన్ రన్ చేస్తుంది. అసైన్మెంట్లు ఇక్కడే జరుగుతాయి. ఎక్స్ప్రెషన్స్ ఎవాల్యుయేట్ చేయబడతాయి. ఫంక్షన్లు ఇన్వోక్ చేయబడతాయి. మీరు var name = "Alice"; అని రాస్తే, మొదటి దశలోని ప్లేస్హోల్డర్ చివరకు ఆ స్ట్రింగ్తో భర్తీ చేయబడుతుంది. ఈ రెండు దశల ప్రవర్తనను అర్థం చేసుకోవడం వల్ల చాలా సందేహాలు తొలగిపోతాయి. ఇంజిన్ అజాగ్రత్తగా ఉండదు; అది ఒక కచ్చితమైన సెటప్ విధానాన్ని అనుసరిస్తుంది.
వేరియబుల్స్ మరియు టెంపోరల్ డెడ్ జోన్ (Temporal Dead Zone)
var, let, మరియు const మధ్య ఎంచుకోవడం అనేది కేవలం శైలి (style)కి సంబంధించిన విషయం మాత్రమే కాదు. var అనేది ఫంక్షన్-స్కోప్డ్ (function-scoped), అంటే ఇది బ్రేసెస్ (braces)లను పూర్తిగా విస్మరిస్తుంది. దీనిని ఒక if బ్లాక్ లోపల డిక్లేర్ చేస్తే, అది బయటకు లీక్ అవుతుంది. ఆ ప్రవర్తన ప్రారంభ JavaScript కాలంలో అర్థవంతంగా ఉండవచ్చు, కానీ ఆధునిక అప్లికేషన్లలో ఇది నిర్వహణ సమస్యలకు (maintenance headaches) దారితీస్తుంది. let మరియు const బ్లాక్-స్కోప్డ్ (block-scoped). అవి కర్లీ బ్రేసెస్లను గౌరవిస్తాయి మరియు బ్లాక్ ముగియగానే మాయమవుతాయి.
హోయిస్టింగ్ (hoisting) వాటిని ఎలా పరిగణిస్తుంది అనే విషయంలో కూడా ఒక సూక్ష్మమైన కానీ కీలకమైన తేడా ఉంది. var డిక్లరేషన్లు హోయిస్ట్ చేయబడతాయి మరియు వెంటనే undefinedతో ప్రారంభించబడతాయి. let మరియు const కూడా సాంకేతికంగా హోయిస్ట్ చేయబడతాయి; డిక్లరేషన్ లైన్ చేరుకోకముందే అవి ఉన్నాయని ఇంజిన్కు తెలుసు. కానీ అవి ఇనిషియలైజ్ చేయబడవు. అవి 'టెంపోరల్ డెడ్ జోన్' (temporal dead zone) అని పిలవబడే ఒక మధ్యంతర స్థితిలో ఉంటాయి. మీరు డిక్లరేషన్ లైన్ అమలు కావడానికి ముందే వాటిని చదవడానికి ప్రయత్నిస్తే, మీకు undefined బదులుగా కచ్చితమైన ReferenceError వస్తుంది. ఆ క్రాష్ నిజానికి సహాయకారి. ఇనిషియలైజ్ చేయని డేటా ఆధారంగా లాజిక్ ముందుకు సాగకుండా ఇది నిరోధిస్తుంది.
లెక్సికల్ స్కోప్ మరియు క్లోజర్స్ (Lexical Scope and Closures)
స్కోప్ ఒక సాధారణ ప్రశ్నకు సమాధానం ఇస్తుంది: నేను ఈ వేరియబుల్ను ఎక్కడ యాక్సెస్ చేయగలను? JavaScript 'లెక్సికల్ స్కోప్'ను ఉపయోగిస్తుంది, అంటే ఒక ఫంక్షన్ యొక్క యాక్సెస్ హక్కులు అది సోర్స్ కోడ్లో భౌతికంగా ఎక్కడ వ్రాయబడిందో దాని ద్వారా నిర్ణయించబడతాయి, అది ఎక్కడ కాల్ చేయబడిందో దాని ద్వారా కాదు. మీరు ఒక ఫంక్షన్ను మరొక ఫంక్షన్ లోపల నిర్వచించినట్లయితే, లోపలి ఫంక్షన్ దాని పేరెంట్ నుండి వేరియబుల్స్ను చదవడానికి బయటకు చూడగలదు. కానీ బయటి ఫంక్షన్ లోపలికి చూడలేదు. ఈ సంబంధం స్టాటిక్ (static). మీరు ఆ లోపలి ఫంక్షన్ను వేరే మాడ్యూల్స్లోకి పంపవచ్చు, గ్లోబల్ వేరియబుల్లో నిల్వ చేయవచ్చు మరియు పూర్తిగా వేరే ఫైల్ నుండి కాల్ చేయవచ్చు. అది ఏ ఫైల్ నుండి పిలవబడినప్పటికీ, అది పుట్టిన స్కోప్ లోని వేరియబుల్స్ను గుర్తుంచుకుంటుంది.
ఈ ప్రవర్తన సహజంగా క్లోజర్స్ (closures)ను ఉత్పత్తి చేస్తుంది. ఒక లోపలి ఫంక్షన్ దాని బయటి స్కోప్ నుండి ఒక వేరియబుల్కు రిఫరెన్స్ను ఉంచుకున్నప్పుడు క్లోజర్ ఏర్పడుతుంది. బయటి ఫంక్షన్ ఎగ్జిక్యూషన్ పూర్తయి, దాని లోకల్ వేరియబుల్స్ గార్బేజ్ కలెక్ట్ చేయబడాల్సినప్పటికీ, లోపలి ఫంక్షన్కు అవి ఇంకా అవసరం కాబట్టి JavaScript వాటిని మెమరీలో ఉంచుతుంది. లోపలి ఫంక్షన్ తన చుట్టూ ఉన్న వాతావరణాన్ని (environment) తనతో పాటు తీసుకెళ్తుంది.
ఇది కేవలం ఒక సిద్ధాంతపరమైన విషయం మాత్రమే కాదు. ఎక్స్ప్లిసిట్ యాక్సెస్ మోడిఫైయర్స్ లేని భాషలో ప్రైవేట్ స్టేట్ను సృష్టించడానికి క్లోజర్స్ మీకు ఒక ఆచరణాత్మక మార్గాన్ని అందిస్తాయి.
function makeCounter() {
let count = 0;
return function() {
count = count + 1;
return count;
};
}
const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2
ఇక్కడ, count అనేది దాచబడింది. రిటర్న్ చేయబడిన ఫంక్షన్ వెలుపల ఏదీ దానిని రీసెట్ చేయలేదు లేదా నేరుగా చదవలేదు. ఇది కేవలం స్కోప్ మెకానిక్స్ ద్వారా నిర్మించబడిన ఒక ప్రైవేట్ వేరియబుల్.
ప్రాక్టిస్లో హోయిస్టింగ్ (Hoisting in Practice)
JavaScript "డిక్లరేషన్లను పైకి మారుస్తుంది" అని వినడం సర్వసాధారణం. ఇది ఒక ఉపయోగకరమైన అవగాహన (mental model) మాత్రమే, కానీ కోడ్ నిజంగా తిరిగి రాయబడదు. మెమరీ క్రియేషన్ దశ (memory creation phase) సమయంలో, ఇంజిన్ ఎగ్జిక్యూషన్ ప్రారంభం కావడానికి ముందే డిక్లరేషన్లను కేవలం రిజిస్టర్ చేస్తుంది. var x = 5; వంటి స్టేట్మెంట్, డిక్లరేషన్ మరియు ఇనిషియలైజేషన్ విడివిడిగా ఉన్నట్లుగా ప్రవర్తిస్తుంది. var x; అనే డిక్లరేషన్ ముందుగానే ప్రాసెస్ చేయబడి undefined గా ఇనిషియలైజ్ చేయబడుతుంది. x = 5; అనే అసైన్మెంట్ మీరు రాసిన చోటే ఉంటుంది మరియు ఎగ్జిక్యూషన్ దశలో రన్ అవుతుంది.
దీని వల్ల, var ఆశ్చర్యకరమైన ఫలితాలకు దారితీయవచ్చు. ఫంక్షన్ యొక్క పైభాగంలో ఉపయోగించిన వేరియబుల్, ఫంక్షన్ చివరలో పెద్ద అసైన్మెంట్ ఉన్నప్పటికీ undefined గా ఉండవచ్చు. let మరియు const ఉపయోగించడం వల్ల ఈ తప్పులకు దారితీసే అంశం (foot-gun) ఉండదు, ఎందుకంటే 'temporal dead zone' మీ డిక్లరేషన్లను వాటి వినియోగం (usage) కంటే పైన ఉంచేలా చేస్తుంది.
this విలువ ఎలా వస్తుంది
ఎగ్జిక్యూషన్ కాంటెక్స్ట్ మరియు స్కోప్ వేరియబుల్స్ ఎక్కడ ఉంటాయో నిర్ణయిస్తే, this ఏ ఆబ్జెక్ట్ ప్రస్తుతం నియంత్రణలో ఉందో నిర్ణయిస్తుంది. లెక్సికల్ వేరియబుల్స్ లాగా కాకుండా, this అనేది ఫంక్షన్ ఎక్కడ వ్రాయబడింది అనే దానిపై ఆధారపడి ఉండదు. ఫంక్షన్ను ఎలా పిలుస్తారు (call చేస్తారు) అనే దానిపైనే ఇది పూర్తిగా ఆధారపడి ఉంటుంది.
Default binding అనేది మీరు ఒక సాధారణ, స్టాండ్అలోన్ ఫంక్షన్ను పిలిచినప్పుడు జరుగుతుంది. non-strict modeలో, this గ్లోబల్ ఆబ్జెక్ట్కు మారుతుంది. బ్రౌజర్లో, అది window. ఎటువంటి కాంటెక్స్ట్ లేకుండా ఫంక్షన్ను పిలిస్తే, మీరు తెలియకుండానే గ్లోబల్ స్టేట్ను మార్చే అవకాశం ఉంది.
Implicit binding అనేది మీరు ఒక ఆబ్జెక్ట్పై మెథడ్గా ఫంక్షన్ను పిలిచినప్పుడు జరుగుతుంది. మీరు user.sayName() అని రాస్తే, ఆ కాల్ సమయంలో thisని userగా సెట్ చేయమని డాట్ (.) ఇంజిన్కు తెలియజేస్తుంది. ఫంక్షన్ ఎక్కడ నిర్వచించబడింది అనే దానికంటే, అది ఎక్కడ పిలవబడింది అనేది ఇక్కడ ముఖ్యం.
Explicit binding ద్వారా మీరు అన్నింటినీ మాన్యువల్గా ఓవర్రైడ్ చేయవచ్చు. call() మరియు apply() ఫంక్షన్ను వెంటనే పిలుస్తాయి, అదే సమయంలో thisని మీరు అందించే నిర్దిష్ట ఆబ్జెక్ట్గా మారుస్తాయి. వీటి మధ్య ఉన్న ఏకైక తేడా ఆర్గ్యుమెంట్లను పంపే విధానం: call కామాతో వేరు చేయబడిన జాబితాను తీసుకుంటుంది, అయితే apply ఒక అర్రేను తీసుకుంటుంది. bind() భిన్నంగా పనిచేస్తుంది. ఇది ఫంక్షన్ను వెంటనే పిలవదు. బదులుగా, మీరు అందించిన విలువకు thisని శాశ్వతంగా లాక్ చేస్తూ ఒక కొత్త ఫంక్షన్ను తిరిగి ఇస్తుంది. కాల్బ్యాక్లను (callbacks) పంపేటప్పుడు అవి తమ కాంటెక్స్ట్ను కోల్పోకుండా ఉండటానికి ఇది చాలా ఉపయోగకరంగా ఉంటుంది.
New binding అనేది మీరు ఫంక్షన్ కాల్ ముందు new కీవర్డ్ను ఉపయోగించినప్పుడు వస్తుంది. ఇంజిన్ ఒక కొత్త ఖాళీ ఆబ్జెక్ట్ను నిర్మిస్తుంది, దాని ప్రోటోటైప్ లింకేజీని సెట్ చేస్తుంది మరియు కన్స్ట్రక్టర్ లోపల ఉన్న thisని ఆ కొత్త ఇన్స్టెన్స్కు పాయింట్ చేస్తుంది.
ఎక్కువ కాలం మన్నే కోడ్ను రాయడం
మెకానిక్స్ను అర్థం చేసుకోవడం అనేది నైపుణ్యంలో సగం మాత్రమే. మిగిలిన సగం ఏమిటంటే, ఆరు నెలల తర్వాత కూడా మనుషులు చదవగలిగే కోడ్ను రాయడం.
DRY (Don't Repeat Yourself) అనేది స్పష్టంగా అనిపించినప్పటికీ, దీనిని నిరంతరం ఉల్లంఘిస్తుంటారు. మీరు మూడు వేర్వేరు ఫైళ్లలో ఒకే రకమైన వాలిడేషన్ లాజిక్ లేదా API కాల్ ప్యాటర్న్ను రాస్తున్నట్లు మీకు అనిపిస్తే, దానిని వేరు చేయండి (extract). ఒకే ఫంక్షన్ను రాయండి. ఒకే మూలం (one source of truth) ఉండటం వల్ల, అవసరాలు మారినప్పుడు ఒకే చోట అప్డేట్ చేస్తే సరిపోతుంది, ఇది సమయాన్ని చాలా ఆదా చేస్తుంది.
KISS (Keep It Simple, Stupid) అనేది అహంకారం నుండి రక్షణ. నెస్టెడ్ టెర్నరీస్ (Nested ternaries) మరియు వన్-లైనర్ క్లోజర్స్ (one-liner closures) తెలివిగా అనిపించవచ్చు, కానీ డీబగ్గింగ్ సమయంలో అవి గంటల కొద్దీ సమయాన్ని వృధా చేస్తాయి. నేను ముందుగా చూపిన క్లోజర్ ప్యాటర్న్ శక్తివంతమైనది, కానీ కేవలం చేయగలను కాబట్టి ఐదు స్థాయిల లోతుకు నెస్టెడ్ చేయడం తప్పు. సరళమైన కోడ్ టీమ్ మార్పులను, ప్రొడక్షన్ సమస్యలను మరియు అర్ధరాత్రి వచ్చే అలర్ట్లను తట్టుకోగలదు.
నిజమైన ప్రతిఫలం
ఎగ్జిక్యూషన్ను అధ్యయనం చేయడం
