प्रांप्ट और लागू संदर्भ
एक task list हज़ारों rows प्रदर्शित कर सकती है। Pagination results, live updates, और user actions गतिशील रूप से rows को सम्मिलित करते हैं या हटाते हैं। प्रत्येक row में edit, delete और status-toggle बटन होते हैं, और एक बटन में icon या text element हो सकता है। List में एक स्वतंत्र रूप से स्वामित्व वाली nested subtask list भी हो सकती है। बाहरी list पर केवल एक click listener पंजीकृत करें और control के data-action और उसकी row के data-task-id का उपयोग करके एक action dispatch करें।
उत्तर में यह बताना होगा कि एक event ancestors से अपने target तक और वापस ancestors की ओर कैसे यात्रा करता है, event.target को event.currentTarget से अलग करना होगा, और outer list को nested list के स्वामित्व वाले actions को संभालने से रोकना होगा। इसमें उन events को भी शामिल करना होगा जो सामान्य bubbling delegation का उपयोग नहीं कर सकते, किसी descendant द्वारा propagation रोकने का प्रभाव, Shadow DOM कैसे दृश्य पथ को बदलता है, और component के unmount होने पर listener को कैसे हटाया जाता है।
यह प्रश्न mid-level frontend, Web platform, और full-stack साक्षात्कारों के लिए उपयुक्त है। इसका मुख्य कौशल browser DOM event dispatch और UI ownership boundaries है, इसलिए यह frontend के अंतर्गत आता है। यह किसी userland publish-subscribe container या task, microtask, और rendering scheduling का परीक्षण नहीं करता है।
इंटरव्यूअर क्या मूल्यांकन कर रहा है
मजबूत उत्तर एक सटीक मॉडल से शुरू होते हैं। Dispatch, capture phase, target phase, और जब event bubble कर सकता है, bubble phase के माध्यम से एक event path का अनुसरण करता है। addEventListener डिफ़ॉल्ट रूप से एक bubbling listener पंजीकृत करता है; capture: true इसे तब चलाता है जब event target की ओर बढ़ रहा होता है। केवल यह कहना कि "events ऊपर की ओर bubble करते हैं", capture listeners, non-bubbling events, या Shadow DOM boundaries को स्पष्ट नहीं कर सकता।
कार्यान्वयन की गुणवत्ता routing logic में दिखाई देती है। target event के dispatch target की पहचान करता है; currentTarget उस object की पहचान करता है जिसका listener वर्तमान में चल रहा है। एक parent list handler में, पूर्व बटन के अंदर एक icon हो सकता है जबकि बाद वाला list हो सकता है। Tag-name की तुलना nested markup को मिस कर देती है। एक मजबूत उत्तर target से शुरू होता है, एक action control खोजने के लिए closest() का उपयोग करता है, और फिर यह साबित करता है कि control active delegation root से संबंधित है।
Ownership boundaries एक बुनियादी उत्तर को production-ready उत्तर से अलग करती हैं। contains() कंटेनर के बाहर के मैच को अस्वीकार कर सकता है, लेकिन कंटेनर के अंदर स्वयं एक अन्य स्वतंत्र रूप से delegated root शामिल हो सकता है। निकटतम delegation root को वर्तमान list के बराबर होने की आवश्यकता स्वामित्व को व्यक्त करती है और outer और nested lists दोनों को एक ही action को निष्पादित करने से रोकती है। Action names को DOM strings से मनमाने function names बनने के बजाय allowlist के माध्यम से भी गुजरना चाहिए।
सत्यापन योजना को देखने योग्य विफलताओं को लक्षित करना चाहिए: nested markup, dynamically जोड़ी गई rows, disabled buttons, nested roots, रुकी हुई propagation, focus events, Shadow DOM, asynchronous callbacks, और unmount cleanup। Delegation, listeners की संख्या को कम करता है और स्वाभाविक रूप से गतिशील descendants को कवर करता है, लेकिन एक छोटा स्थिर इंटरफ़ेस direct listeners के साथ अधिक स्पष्ट हो सकता है। Delegation एक डिज़ाइन विकल्प है, कोई अनिवार्य अनुकूलन नहीं।
उत्तर देने से पहले स्पष्ट करने योग्य प्रश्न
- किन events को delegate किया जा रहा है?
clickसामान्य रूप से bubble करता है और इस प्रॉम्प्ट के अनुकूल है।focus,blur,mouseenter, याmouseleaveको delegate करने के लिए capture, एक bubbling विकल्प, या direct listeners की आवश्यकता होती है; वह विकल्प कार्यान्वयन और शब्दार्थ दोनों को बदल देता है। - क्या list में nested components या Shadow DOM हो सकते हैं? एक flat list के लिए
contains()अक्सर पर्याप्त होता है। Nested ownership के लिए एक अलग root marker की आवश्यकता होती है, जबकि Shadow DOM को एक सार्वजनिक component-event contract की आवश्यकता होती है। - क्या button markup स्थिर है? यदि target कोई icon या text node हो सकता है, तो handler को
closest()की आवश्यकता होती है।matches()केवल तभी पर्याप्त होता है जब target का action element होना सुनिश्चित हो। stopPropagation()को कौन कॉल कर सकता है? यदि किसी child component को propagation रोकने की अनुमति है, तो ancestor bubble listener event को देखने का वादा नहीं कर सकता। Business actions को चुपचाप capture में स्थानांतरित करने के बजाय ownership को स्पष्ट करें।- Disabled स्थिति को कैसे दर्शाया जाता है? Native buttons
disabledका उपयोग करते हैं। एक custom control को सुलभ और कीबोर्ड व्यवहार की भी आवश्यकता होती है; अकेले CSS class, handler के लिए एक विश्वसनीय अनुबंध नहीं है। - Listener के जीवनकाल का स्वामी कौन है? Page-level root स्थायी हो सकता है। एक mountable component को एक हटाने योग्य फ़ंक्शन संदर्भ बनाए रखना चाहिए या
AbortSignalका उपयोग करना चाहिए, अन्यथा remounting एक ही क्लिक को कई बार संसाधित करेगा। - क्या किसी action को डिफ़ॉल्ट व्यवहार रद्द करने की आवश्यकता है?
preventDefault()को केवल तभी कॉल करें जब navigation, form submission, या कोई अन्य डिफ़ॉल्ट कार्रवाई उत्पाद के इरादे से टकराती हो। Default cancellation और propagation control स्वतंत्र निर्णय हैं।
30-सेकंड का उत्तर ढांचा
"एक event को उसके path के माध्यम से capture किया जाता है, target पर संभाला जाता है, और जब bubbles true होता है तो ancestors के माध्यम से वापस bubble किया जाता है। एक parent का डिफ़ॉल्ट listener bubble phase में चलता है, इसलिए एक listener गतिशील descendants को कवर कर सकता है। Handler में, मैं पहले सत्यापित करता हूं कि target एक Element है, फिर closest('[data-action]') को कॉल करता हूं। मैं contains की जांच करता हूं और आवश्यकता होती है कि निकटतम delegation root वर्तमान list हो ताकि एक nested list actions को बाहर लीक न कर सके। currentTarget listener वाली list है और केवल handler के चलने के दौरान ही विश्वसनीय होती है। मैं action allowlist के माध्यम से dispatch करता हूं और AbortController से साफ़ करता हूं। Non-bubbling events, stopPropagation, और Shadow DOM के लिए, मैं boundary के अनुसार capture, एक bubbling विकल्प, एक direct listener, या एक component-level custom event चुनता हूं, फिर nested targets, dynamic rows, और unmount/remount का परीक्षण करता हूं।"
चरण-दर-चरण गहन विश्लेषण
Event path से शुरुआत करें। मान लें कि एक बटन के अंदर एक आइकन पर एक क्लिक शुरू होता है। पथ में document, outer list, task row, button, और icon शामिल हैं। Capture listeners पथ के बाहरी हिस्से से target की ओर चलते हैं। Event के target पर पहुँचने पर लागू target listeners चलते हैं। यदि bubbles true है, तो ancestor bubble listeners अंदर से बाहर की ओर चलते हैं। Dispatch algorithm इस पथ का निर्माण करता है, और DOM संरचना और Shadow DOM boundaries दोनों इसे प्रभावित करते हैं।
target और currentTarget अलग-अलग सवालों के जवाब देते हैं। target पूछता है कि यह interaction कहाँ dispatch किया गया था और सामान्य DOM bubbling के दौरान सामान्य रूप से स्थिर रहता है। currentTarget पूछता है कि वर्तमान में किस object का listener चल रहा है; यह handlers के बीच बदलता है और handler के वापस लौटने के बाद null बन जाता है। किसी भी आवश्यक list संदर्भ या डेटा को समकालिक रूप से (synchronously) सहेजें। await के बाद event.currentTarget पर भरोसा न करें।
निम्नलिखित कार्यान्वयन matching, ownership, state, और action dispatch को अलग करता है। मान लें कि list root में data-delegation-root है, प्रत्येक row में data-task-id है, और actions data-action वाले native buttons का उपयोग करते हैं। openEditor, deleteTask, और toggleTask मौजूदा product functions के प्रतीक हैं:
const list = document.querySelector("#task-list");
if (!(list instanceof HTMLElement)) {
throw new Error("task list was not found");
}
const handlers = new Map([
["edit", (taskId) => openEditor(taskId)],
["delete", (taskId) => deleteTask(taskId)],
["toggle", (taskId) => toggleTask(taskId)],
]);
const controller = new AbortController();
list.addEventListener(
"click",
(event) => {
const target = event.target;
if (!(target instanceof Element)) return;
const action = target.closest("[data-action]");
if (!(action instanceof HTMLButtonElement)) return;
if (!list.contains(action)) return;
if (action.closest("[data-delegation-root]") !== list) return;
if (action.disabled) return;
const row = action.closest("[data-task-id]");
if (!(row instanceof HTMLElement) || !list.contains(row)) return;
const actionName = action.dataset.action;
const taskId = row.dataset.taskId;
if (!actionName || !taskId) return;
const handler = handlers.get(actionName);
if (!handler) return;
handler(taskId);
},
{ signal: controller.signal },
);
function cleanupTaskListDelegation() {
controller.abort();
}target.closest() वास्तविक icon से उसके button तक चलता है, इसलिए आंतरिक markup परिवर्तन routing को नहीं तोड़ते हैं। list.contains(action) साबित करता है कि मैच अभी भी list के भीतर है। फिर निकटतम-रूट जांच उन बटनों को अस्वीकार करती है जिनका स्वामित्व किसी nested list के पास है। Map केवल तीन ज्ञात कार्यों की अनुमति देता है; एक अज्ञात data-action सुरक्षित रूप से बाहर निकल जाता है। स्पष्ट disabled जांच handler अनुबंध का दस्तावेजीकरण करती है, इसलिए यदि कोड बाद में programmatic dispatch का उपयोग करता है या इसके markup को बदलता है तो भी एक disabled action अस्वीकार कर दिया जाता है।
Propagation नियंत्रणों को अलग-अलग स्पष्टीकरण की आवश्यकता होती है। stopPropagation() capture या bubble path के माध्यम से आगे की यात्रा को रोकता है। यह उसी तत्व पर डिफ़ॉल्ट कार्रवाई या अन्य listeners को नहीं रोकता है; बाद वाले के लिए stopImmediatePropagation() की आवश्यकता होती है। preventDefault() एक cancelable event की डिफ़ॉल्ट कार्रवाई को रद्द करता है लेकिन propagation को नहीं रोकता है। इन तीन APIs को समान मानने से links अभी भी navigate कर सकते हैं, ancestors events को मिस कर सकते हैं, या समान-नोड handlers अप्रत्याशित रूप से जारी रह सकते हैं।
प्रत्येक non-bubbling event के लिए एक रणनीति चुनें। एक ancestor capture के दौरान focus और blur का निरीक्षण कर सकता है, या bubbling focusin और focusout events का उपयोग कर सकता है। mouseenter और mouseleave को सीधे बाँधा (bind) जा सकता है। उन्हें bubbling mouseover और mouseout से बदलने के लिए relatedTarget फ़िल्टरिंग की भी आवश्यकता होती है क्योंकि descendants के बीच की गतिविधि अतिरिक्त events उत्पन्न करती है। एक scrollable element अक्सर direct listener के साथ सबसे स्पष्ट होता है। समान event नाम समान शब्दार्थ की गारंटी नहीं देते हैं।
Shadow DOM एक component boundary है; document-level delegation को component के अंदर के selectors पर निर्भर नहीं होना चाहिए। User-agent UI events जैसे click आम तौर पर एक shadow boundary के पार composed होते हैं, लेकिन एक बाहरी listener एक retargeted host को target के रूप में देख सकता है। composedPath() एक खुले shadow root के माध्यम से पथ को उजागर कर सकता है लेकिन एक बंद root के अंदर के nodes को उजागर नहीं करता है। एक Web Component जिसे एक कार्रवाई प्रकाशित करने की आवश्यकता होती है, उसे आंतरिक तत्वों को स्वयं संभालना चाहिए और एक प्रलेखित component event उत्सर्जित करना चाहिए जिसे bubble करने और boundary पार करने की अनुमति हो। Consumers तब एक स्थिर सार्वजनिक अनुबंध पर निर्भर करते हैं।
केवल listeners की गिनती करने के बजाय व्यवहार को सत्यापित करें। Capture, target, और bubble क्रम को लॉग करें; बटन टेक्स्ट और उसके आइकन दोनों पर क्लिक करें; एक पंक्ति जोड़ें और बिना rebind किए उस पर क्लिक करें; एक पंक्ति हटाएं और सुनिश्चित करें कि उसका पुराना नोड अब कार्य नहीं करता है; एक nested list पर क्लिक करें और सुनिश्चित करें कि केवल उसका स्वामी ही इसे संभालता है; disabled और अज्ञात कार्यों का परीक्षण करें; एक बच्चे को propagation रोकने दें और छूटे हुए ancestor event का निरीक्षण करें; focus रणनीति और Shadow DOM को कवर करें; cleanupTaskListDelegation() को कॉल करें और साबित करें कि क्लिक बंद हो गए हैं, फिर एक बार remount करें और साबित करें कि एक क्लिक को ठीक एक बार संभाला जाता है।
उच्च गुणवत्ता वाला नमूना उत्तर
"मैं पहले पुष्टि करूंगा कि हम एक bubbling click event को delegate कर रहे हैं। Browser event path के साथ target तक capture करता है, target को संभालता है, और फिर bubbling की अनुमति होने पर ancestors के माध्यम से लौटता है। एक parent का डिफ़ॉल्ट listener bubbling के दौरान चलता है। event.target dispatch target है और बटन के अंदर एक आइकन हो सकता है; event.currentTarget इस handler वाली list है और केवल handler के चलने के दौरान ही मान्य होती है।
मैं closest('[data-action]') के साथ target से शुरू करता हूँ, फिर सत्यापित करता हूँ कि परिणाम वर्तमान list के अंदर का एक बटन है। चूंकि इस list में एक अन्य delegated component हो सकता है, इसलिए मुझे action के निकटतम data-delegation-root को यह list होने की भी आवश्यकता है। मुझे इसकी data-task-id row मिलती है और edit, delete, या toggle को मौजूदा फ़ंक्शनों में dispatch करने के लिए एक Map allowlist का उपयोग करता हूँ। नई डाली गई rows स्वाभाविक रूप से उसी ancestor listener द्वारा कवर की जाती हैं, और एक AbortController unmount होने पर इसे हटा देता है।
मैं हर event को bubble delegation में बाध्य नहीं करूंगा। Focus capture या focusin का उपयोग कर सकता है, और mouseenter के शब्दार्थ mouseover जैसे नहीं होते हैं। StopPropagation को कॉल करने वाला बच्चा ancestor bubble handler को event देखने से रोकता है। Shadow DOM के पार, target को retarget किया जा सकता है और एक closed root अपने आंतरिक पथ को उजागर नहीं करता है, इसलिए किसी component को आंतरिक selectors पर पृष्ठ को निर्भर बनाने के बजाय एक स्थिर बाहरी event प्रकाशित करना चाहिए।
मेरे परीक्षण एक आंतरिक icon, dynamic rows, nested roots, disabled और अज्ञात actions, रुकी हुई propagation, Shadow DOM, और unmount/remount को कवर करते हैं। यदि असंबंधित व्यवहार वाले केवल कुछ स्थिर बटन हैं, तो मैं direct listeners का उपयोग करता हूँ। यहाँ Delegation का मुख्य मूल्य एक स्पष्ट स्वामित्व सीमा, गतिशील descendant समर्थन, और एक जीवनचक्र है—न कि कोई संदर्भ-मुक्त प्रदर्शन दावा।"
सामान्य गलतियाँ
- केवल
event.target.matches('button')की जाँच करना → एक आइकन क्लिक target को आइकन बनाता है →closest()के साथ action element तक चलें। - पहले
closest()मैच को निष्पादित करना → यह किसी अन्य कंटेनर या nested root से संबंधित हो सकता है →contains()और निकटतम स्वामित्व रूट दोनों की जाँच करें। currentTargetको क्लिक किया गया तत्व मानना → parent delegation में यह चल रहे handler वाली list है → स्रोत के लिए target और handling boundary के लिए currentTarget का उपयोग करें।awaitके बादevent.currentTargetको पढ़ना → handler वापस आ चुका है और मानnullहै → आवश्यक तत्व संदर्भों और डेटा को समकालिक रूप से सहेजें।- यह मानना कि
preventDefault()bubbling को रोकता है → एक डिफ़ॉल्ट रद्द हो सकता है जबकि ancestors अभी भी चल रहे हैं → default cancellation और propagation का निर्णय अलग से लें। - प्रत्येक event पर एक delegation template लागू करना → focus और mouseenter click की तरह bubble नहीं करते हैं → event semantics से capture, एक वैकल्पिक event, या direct binding चुनें।
- Document root से Shadow DOM internals का निरीक्षण करना → target retargeted होता है और एक closed root अपने आंतरिक पथ को छुपाता है → component को internals का स्वामित्व लेने दें और एक स्थिर event प्रकाशित करने दें।
- बिना cleanup के हर mount पर anonymous listeners जोड़ना → एक क्लिक बासी स्थिति के साथ बार-बार चलता है → एक हटाने योग्य संदर्भ बनाए रखें या AbortSignal के साथ जीवनकाल प्रबंधित करें।
- दावा करना कि delegation हमेशा तेज़ होता है → एक छोटा स्थिर इंटरफ़ेस routing और filtering की जटिलता को बढ़ाता है → node dynamics, ownership, और maintenance cost के आधार पर चुनें।
फॉलो-अप और प्रतिक्रियाएं
फॉलो-अप 1: एक child button stopPropagation को कॉल करता है, लेकिन outer delegation को अभी भी काम करना चाहिए। आप क्या करते हैं?
पहले तय करें कि click semantics का मालिक कौन है। यदि बच्चे को event को ब्लॉक करने का कोई अधिकार नहीं है, तो उस कॉल को हटा दें और component अनुबंध को ठीक करें। एक capture listener event के बटन तक पहुँचने से पहले उसका निरीक्षण कर सकता है, लेकिन capture के दौरान deletion या किसी अन्य business action को निष्पादित करने से target handlers और डिफ़ॉल्ट व्यवहार से पहले निष्पादन होता है, जिससे क्रम और रद्दीकरण के अवसर बदल जाते हैं। Capture takeover का उपयोग केवल तभी करें जब उत्पाद को स्पष्ट रूप से इसकी आवश्यकता हो, साथ ही ordering, cancellation, और duplicate-action परीक्षण भी करें।
फॉलो-अप 2: एक form को केंद्रीकृत focus handling की आवश्यकता होती है। आप इसे कैसे delegate करते हैं?
focus bubble नहीं करता है, लेकिन एक ancestor capture: true के साथ इसका निरीक्षण कर सकता है; वैकल्पिक रूप से bubbling focusin event का उपयोग करें। यदि लक्ष्य एक field-help handler है, तो focusin मॉडल के बारे में सोचना अक्सर आसान होता है। Keyboard, pointer, और programmatic focus का परीक्षण करें, और एक composite widget के भीतर प्रत्येक गतिविधि को उस पूरे विजेट को छोड़ने के रूप में रिपोर्ट करने से बचें।
फॉलो-अप 3: Action button एक closed shadow root के अंदर है। Page-level list इसकी पहचान कैसे करती है?
Page को एक closed root के अंदर के बटन की पहचान नहीं करनी चाहिए। Component आंतरिक रूप से click को संभालता है और अपने host से एक semantic event प्रकाशित करता है, जिसमें उसके डेटा में केवल एक स्थिर कार्रवाई और कार्य पहचानकर्ता होता है और एक स्पष्ट अनुबंध होता है जो bubbling और shadow boundary को पार करने की अनुमति देता है। Page उस component event को delegate करता है। Encapsulation बरकरार रहता है, और पृष्ठ उन आंतरिक nodes पर निर्भर नहीं करता है जिन्हें composedPath() प्रकट नहीं करेगा।
फॉलो-अप 4: Handler confirmation की प्रतीक्षा (awaits) करता है। DOM परिवर्तनों के बाद आप गलत row पर कार्रवाई करने से कैसे बचते हैं?
Asynchronous flow शुरू करने से पहले अपरिवर्तनीय taskId और actionName को समकालिक रूप से पढ़ें और मान्य करें। पुष्टि के बाद, taskId द्वारा वर्तमान business state को देखें; एक पुराना row संदर्भ न रखें और यह न मान लें कि यह अभी भी जुड़ा हुआ है। यदि डुप्लिकेट मायने रखते हैं, तो task/action को pending के रूप में भी चिह्नित करें या अनुरोध के लिए एक idempotency key का उपयोग करें।
फॉलो-अप 5: आपको delegation कब छोड़ देना चाहिए?
Direct listeners का ऑडिट करना आसान होता है जब नोड्स कम और स्थिर होते हैं, नियंत्रणों में असंबंधित व्यवहार होता है, event bubble नहीं करता है, या component boundary को local handling की आवश्यकता होती है। एक उच्च-आवृत्ति वाले event के लिए, यदि एक root बार-बार कई अप्रासंगिक events के लिए जटिल selectors चलाता है, तो root को संकीर्ण करें या सीधे bind करें। प्रदर्शन के लिए संरचना बदलने से पहले वास्तविक प्रदर्शन पथ को मापें।