In the browser DOM, what can element.addEventListener('click', fn) do that assigning element.onclick = fn cannot?
answer
- one slot versus a list
- the second assignment wins
- options only exist on the API form
- null clears the property handler
- capture, once, passive, signal
basics
~10 saddEventListener registers any number of independent listeners on one element and accepts an options argument — capture, once, passive, signal. element.onclick is a single property slot, so each assignment silently replaces the previous handler.
solid answer
~40 s`element.onclick` is one property. Assigning to it a second time overwrites the first handler, and setting it to `null` is how you clear it. `addEventListener` maintains a list instead, so several independent parts of an app can each register a click listener on the same element without knowing about each other, and each is removed individually with `removeEventListener` or by aborting an `AbortSignal`. Only `addEventListener` takes the third argument, so `capture`, `once`, `passive` and `signal` are simply unavailable in the property form. The two coexist: if you set `onclick` and also add a listener, both run when the element is clicked. In practice I use `onclick` only in a throwaway script; anything inside a component uses `addEventListener`, because it composes and it tears down cleanly.
code
javascript · 9 linesconst btn = document.createElement('button');
btn.onclick = () => console.log('first');
btn.onclick = () => console.log('second'); // replaces 'first'
btn.addEventListener('click', () => console.log('listener A'));
btn.addEventListener('click', () => console.log('listener B'));
btn.click(); // logs: second, listener A, listener Bgo deeper
Be ready to say plainly that onclick is one property that gets overwritten, while addEventListener can attach many handlers, and that you clear the first with null and the second with removeEventListener.
Explain the mechanics: the property handler is registered into the same listener list when first assigned, so both forms run in registration order, and only the API form accepts the capture/once/passive/signal options.
Show why shared or third-party code must never use the property form, and connect the choice to teardown — listeners you added are the ones you can remove individually or abort as a group when a view unmounts.
Own the codebase convention: inline on* attributes fight a strict Content-Security-Policy and hide behaviour in markup, so argue for a single registration idiom and a lifecycle that guarantees every listener has an owner responsible for removing it.
## Two ways to register the same thing The DOM offers two registration mechanisms for the same underlying dispatch machinery. The older one is the *event handler* — a property on the element named after the event with an `on` prefix: `onclick`, `oninput`, `onkeydown`. You assign a function to it. The newer one is `EventTarget.addEventListener(type, listener, options)`, available on every object that can receive events: elements, `document`, `window`, and other `EventTarget`s. Both end up calling your function with the event object, and inside a plain (non-arrow) function `this` is the element the handler is attached to in both cases. The differences are about *how many*, *with what settings*, and *how you take it back off*. ## The property is a single slot `onclick` is an ordinary IDL property. It holds exactly one function or `null`: ```js btn.onclick = () => console.log('save'); btn.onclick = () => console.log('analytics'); // the save handler is gone ``` Nothing warns you. The second assignment overwrote the first, and any code that later assigns `btn.onclick` wipes yours out too. That is why the property form is dangerous in shared code: two modules that both want to react to the same click cannot both use it. Clearing is symmetric and easy, though — `btn.onclick = null` unregisters the handler. ## addEventListener keeps a list `addEventListener` appends to an internal list of listeners on that target: ```js btn.addEventListener('click', save); btn.addEventListener('click', track); ``` Both run on a click, in the order they were added. Neither module needs to know the other exists, which is why every component library, framework internal, and browser extension uses this form. Removal is per-listener: `btn.removeEventListener('click', track)` leaves `save` attached. ## Only the API form takes options The third argument is where the platform keeps the interesting switches: ```js btn.addEventListener('click', onClick, { capture: false, // listen on the way down instead of the way up once: true, // remove me automatically after the first call passive: true, // I promise not to cancel the default action signal // an AbortSignal that unregisters me when aborted }); ``` The legacy positional form takes a boolean in that slot, which is the capture flag: `addEventListener('click', fn, true)`. There is no equivalent for `onclick` at all — a property handler is always a non-capturing, repeat-forever, non-passive listener. If you need any of those behaviours, the property form is out. ## They coexist, and ordering follows registration Setting `onclick` does not disable listeners, and adding listeners does not disable `onclick`. The property handler joins the same listener list at the moment the property is *first* set to a function, so ordering is simply the order of registration: ```js btn.onclick = () => console.log('A'); btn.addEventListener('click', () => console.log('B')); btn.click(); // A then B ``` If you then reassign `btn.onclick`, the replacement keeps the original slot's position rather than moving to the end. ## Inline on* attributes are a third thing Writing `<button onclick="save()">` in HTML sets the same property, but from a string of markup. That adds two problems beyond the single-slot limitation: the code is a global-scope string rather than a module-scoped function, and a strict Content-Security-Policy blocks it unless the policy explicitly allows inline script. Modern codebases keep behaviour in scripts and attach it with `addEventListener`. One genuine quirk of the handler form: because it has a return value, returning `false` from an `onclick` handler cancels the default action, which is where the old jQuery-era habit came from. An `addEventListener` callback's return value is ignored entirely — you call `event.preventDefault()`. ## What to actually use Use `addEventListener` by default. It is the only form that composes with other code, the only one that can be capturing, one-shot, or passive, and the only one that participates in `AbortSignal`-based teardown. Reach for `onclick` only when you are writing a tiny standalone script and you positively want "there is exactly one handler and assigning replaces it" — for example, wiring a single button in a demo page. And remember the removal asymmetry: a property handler is cleared with `= null`, never with `removeEventListener`.
- If an element has both an onclick property handler and a listener added with addEventListener for 'click', do both run, and in what order?Both run. The property handler is registered into the same listener list the moment the property is first set to a function, so ordering is just registration order — set `onclick` first and it runs first. Reassigning the property later replaces the function in place without moving its position.
- How do you unregister a handler that was assigned through the onclick property?Assign `null` to it: `btn.onclick = null`. `removeEventListener('click', fn)` does not touch it, because the property handler is not the function object you would pass — the browser wraps it, and matching is by reference.
- Why do teams ban inline onclick attributes in HTML?Two reasons. The attribute value is evaluated as global-scope script, so it cannot reference module-scoped functions and is awkward to test; and a strict Content-Security-Policy refuses to execute it without an explicit inline-script allowance. Attaching behaviour from a script file avoids both.
saying these in an interview costs you the question
- Thinks onclick can hold several handlers at once
- Believes addEventListener replaces any existing handler
- Tries to clear an onclick property with removeEventListener
- Claims you can pass capture or once to onclick
- Says setting onclick disables previously added listeners