Reading & Updating DOM - JavaScript

  1. What are the values of before and after after the script runs?
    Answer: before is ["AB", "A"], and after is ["ABA", 0]. When working with DOM properties, the key distinction to understand is that textContent and innerText behave differently — especially when hidden elements are involved. textContent returns the raw text content of all descendant nodes, regardless of visibility. So for <div id="m"><span>A</span><span hidden>B</span></div>, m.textContent gives "AB" — it sees both spans. innerText, on the other hand, is visibility-aware: it only returns text that is rendered on screen, so m.innerText gives "A", ignoring the hidden span. That's why before = ["AB", "A"], confirming answer C. Now for after: the line m.textContent += m.innerText is equivalent to m.textContent = m.textContent + m.innerText, which evaluates to "AB" + "A" = "ABA". Crucially, **assigning to textContent destroys all child elements** — the two <span> nodes are removed and replaced with a single text node. That's why m.childElementCount becomes 0, not 2. A is wrong because it assumes innerText also reads hidden elements — it doesn't. B is wrong in the opposite direction, assuming textContent skips hidden elements like innerText does. D gets the text values right but incorrectly assumes the child spans survive the textContent assignment — they don't. A good rule of thumb: whenever you set textContent, think "nuclear option" — it wipes out all child nodes. And remember the textContent vs. innerText split: raw vs. rendered.
  2. What is stored in result?
    Answer: ["b", 1] This question tests your understanding of live HTMLCollections — one of JavaScript's trickiest DOM concepts. When you call document.getElementsByClassName(), you don't get a static snapshot of matching elements. You get a live HTMLCollection, meaning it automatically updates whenever the DOM changes. This is fundamentally different from document.querySelectorAll(), which returns a static NodeList. Here's what makes the loop behave unexpectedly. On iteration i = 0, items[0] is the <li> for "a" — its "pending" class is removed, so it immediately drops out of the live collection. Now items[0] points to "b", and items[1] points to "c". When i = 1, the loop processes items[1], which is now "c" — removing its class. "b" slides to items[0] and stays there because the loop ends at i = 2 (length is now 1). The "b" element never gets processed. So after the loop, one element remains in items — the <li> for "b" — giving items.length === 1 and item.textContent === "b". The correct answer is **B: ["b", 1]**. A (["", 0]) assumes all three elements were removed, which would be true with a static collection or a while loop approach. C (["c", 1]) incorrectly identifies which element survives — "c" was actually processed on the second iteration. D (["bc", 2]) assumes two elements remain, misunderstanding the live-collection skipping pattern entirely. Study tip: Whenever you see getElementsByClassName or getElementsByTagName in a loop question, immediately ask yourself: "Is this a live collection?" If yes, watch for elements being skipped as the collection shrinks beneath the iterator.
  3. What is stored in result?
    Answer: ["<strong>new</strong>", "&lt;strong&gt;new&lt;/strong&gt;", 0] When working with DOM manipulation, the key distinction to understand is the difference between textContent and innerHTML — they look similar but behave very differently when you assign HTML markup as a string. Setting status.textContent = "<strong>new</strong>" treats the entire string as plain text, not as HTML. The browser stores it literally — angle brackets and all — as a text node, never parsing the tags. This means no <strong> element is created in the DOM; it's just a raw string of characters displayed on screen. So status.textContent returns that literal string "<strong>new</strong>", and status.innerHTML — which serializes the DOM to HTML — will HTML-encode the angle brackets, returning "&lt;strong&gt;new&lt;/strong&gt;". Since no child elements were created, status.childElementCount is 0. That gives you answer C. A is wrong because innerHTML wouldn't return the raw angle brackets — it encodes them as HTML entities since those characters are stored as text, not markup. B is doubly wrong: it swaps the textContent and innerHTML behaviors and still incorrectly shows a child element count of 1. D is wrong because textContent returns the raw stored string (with real angle brackets), not HTML-encoded entities — only innerHTML does the encoding. The key strategy: remember that textContent is a plain text setter and getter — it never parses HTML. If you want to inject actual HTML elements, you must use innerHTML. When innerHTML reads back text nodes containing special characters, it encodes them as entities to keep the output valid HTML.
  4. What is stored in result?
    Answer: ["guide", "https://example.com/app/guide"] When working with DOM element properties and methods, it's crucial to understand that getAttribute() and direct property access (like .href) often return different things — one gives you the raw HTML attribute value, and the other gives you the fully resolved, browser-interpreted value. After link.setAttribute("href", "guide") runs, the anchor's href attribute is literally set to the string "guide". When you call link.getAttribute("href"), you get back exactly what's stored in the HTML attribute — the raw string "guide". No resolution, no processing. Meanwhile, link.href is a reflected IDL attribute, meaning the browser resolves it into a full absolute URL based on the page's current location (https://example.com/app/page.html). Since "guide" is a relative path, it resolves relative to the current directory /app/, giving you https://example.com/app/guide. So result is ["guide", "https://example.com/app/guide"], confirming A is correct. B is wrong because it assumes getAttribute() also returns the resolved URL — it doesn't; it always returns the raw attribute string. C is wrong about the resolved URL: since the page is in /app/, a relative path resolves to /app/guide, not /guide (which would only happen if the page were at the root). D is a trap that ignores the setAttribute call entirely — by the time the script reads the values, the href has already been updated from "../old" to "guide". Remember this pattern: **getAttribute() = raw HTML string, .property = browser-resolved value**. This distinction appears frequently in DOM questions and trips up many students.
  5. What is stored in result?
    Answer: [2, "AB", "BC"] When working with DOM queries in JavaScript, the critical concept to understand is the difference between static and live collections — and querySelectorAll returns a static NodeList. Here's what happens step by step: nodes is captured as a static snapshot of the two .note paragraphs (A and B) at the moment querySelectorAll is called. After that, two mutations occur — removing the "note" class from paragraph A, and inserting a new paragraph C. Because nodes is static, neither change affects it. It still holds its original two elements, so nodes.length remains 2. When you iterate over nodes to build the text, you're reading the actual DOM nodes — paragraph A still exists and still contains "A", and paragraph B still contains "B". So Array.from(nodes, ...) produces "AB". The final section.querySelectorAll(".note") is a fresh query run after all mutations. Paragraph A lost its class, so it's excluded. Paragraph C was just added with the class, so it's included. Only B and C qualify, yielding "BC". That makes result equal to [2, "AB", "BC"], confirming D. A is wrong because it assumes the first element is still a .note and the second fresh query would also return "AB" — ignoring the class removal and new insertion. B wrongly assumes nodes itself updates to reflect mutations (that would be a live HTMLCollection, not a static NodeList). C incorrectly assumes nodes.length grew to 3 because of the inserted element. The key study tip: remember that querySelectorAll is static — it freezes a snapshot. If you need a live collection, use getElementsByClassName instead.
  6. What is stored in result?
    Answer: [true, false, false, null] When working with HTML attributes in JavaScript, it's crucial to distinguish between the presence of an attribute and its value — these are two separate concepts that trip up many developers. Here's the key insight: setAttribute("disabled", "false") does not disable the button conditionally based on the string "false." It simply sets the disabled attribute to exist on the element with the value "false". The disabled attribute is a boolean attribute in HTML — its presence alone disables the element, regardless of what string value you assign. So after setAttribute("disabled", "false"), the button is disabled and the attribute exists. Walking through the code: first = button.hasAttribute("disabled") returns true because the attribute is present. Then button.toggleAttribute("disabled") removes the attribute (since it existed) and returns false — the new state of the attribute's presence. Now that disabled is removed, button.disabled returns false, and button.getAttribute("disabled") returns null because the attribute no longer exists. That gives you [true, false, false, null], confirming D. Choice A is wrong because hasAttribute returns true (not false) after setAttribute adds the attribute. Choice B incorrectly assumes toggleAttribute adds the attribute (returning true) rather than removing it. Choice C gets first and toggled right but then mistakenly assumes button.disabled is still true after toggling removes the attribute. Remember this rule: for boolean HTML attributes, presence = true, absence = false — the string value assigned is irrelevant.
  7. What is stored in result?
    Answer: [false, true, true, "panel active"] When you see classList.toggle(), always check whether a second argument (a force parameter) is present — it completely changes the method's behavior. toggle(className) with no second argument adds the class if absent, removes it if present, and returns the resulting boolean state. toggle(className, true) forces the class on (always adds it, never removes), returning true. toggle(className, false) forces the class off (always removes it, never adds), returning false. Let's trace through each line. The box starts with class="panel active". - box.classList.toggle("active", false) — forces "active" off, removing it. Returns false. So x = false. Box now has "panel". - box.classList.toggle("active") — no force argument. "active" is currently absent, so it gets added. Returns true. So y = true. Box now has "panel active". - box.classList.toggle("panel", true) — forces "panel" on. It's already present, so nothing changes. Returns true. So z = true. Box stays "panel active". That gives result = [false, true, true, "panel active"], which is C. Choice A is wrong because it sets y = false, as if the second toggle removed "active" — but "active" was absent at that point, so it gets added, not removed. Choice B incorrectly sets x = true, ignoring that the false force parameter removes the class. Choice D gets z wrong, suggesting the third toggle removed "panel" when true forces it to stay. Study tip: Memorize toggle(name, true) = "force add, return true" and toggle(name, false) = "force remove, return false" — the force parameter overrides the normal toggling logic entirely.
  8. What is stored in result?
    Answer: ["B", "B", "C", "C"] When working with HTML input elements in JavaScript, you need to understand that there are two separate systems tracking an input's value: the DOM property (input.value) and the HTML attribute (input.getAttribute("value") / input.defaultValue). These start synchronized but diverge once the user or JavaScript modifies the property directly. Here's the walkthrough: The HTML initializes the attribute value to "A". Then input.value = "B" sets the DOM property, not the attribute — so the property is now "B" while the attribute remains "A". before captures "B". Next, input.setAttribute("value", "C") updates the attribute to "C", but critically, this does not overwrite the DOM property once it has been manually set. So input.value stays "B". The result array is therefore ["B", "B", "C", "C"], confirming answer B. A is wrong because it assumes setAttribute updates input.value — it does not, once the property has been independently set. C is wrong because it assumes input.defaultValue reflects the original HTML attribute "A" — but defaultValue is live and mirrors the current value attribute, which is now "C". D is wrong because it mixes both misconceptions, imagining that input.value updates to "C" but defaultValue stays frozen at "B". A handy rule: think of input.value as the "live user state" and input.getAttribute("value") / defaultValue as the "form reset value." Once you set the property directly, setAttribute can no longer reach it.
  9. What is stored in result?
    Answer: [true, "beta delta", false] Whenever you see a question involving classList, remember that it is a live reference — it always reflects the current state of the element's class attribute, not a snapshot from when you first grabbed it. Here's the step-by-step breakdown. Initially, the element has classes "alpha beta", so tokens.contains("alpha") returns true, making before = true. Then setAttribute("class", "beta gamma") directly rewrites the class attribute, so the element now has "beta gamma" — and because tokens is live, it immediately reflects this change. Next, tokens.toggle("gamma") removes "gamma" since it's currently present, leaving "beta". Then tokens.add("delta") appends "delta", giving a final class list of "beta delta". At the end, tokens.contains("alpha") is false because "alpha" was wiped out by setAttribute. So result is [true, "beta delta", false] — confirming B. A is wrong because "alpha" was destroyed by setAttribute and never re-added; the class list never returns to the original state. C is a tempting trap — it looks right if you forget that toggle removes "gamma" (since it was already present), not adds it. D is wrong only in the first element: before was captured before setAttribute ran, when "alpha" was still there, so it's true, not false. The key study tip: classList is a live DOMTokenList, and toggle removes the class if present, adds it if absent — never assume it always adds.
  10. What is stored in result?
    Answer: ["12", "false", false, "false"] When working with the HTML dataset API, the most important concept to internalize is that **everything stored in dataset is a string** — no matter what type of value you assign. The browser serializes values to strings automatically. Here's what happens step by step. card.dataset.userId = 12 converts the number 12 to the string "12" and updates data-user-id. card.dataset.ready = false converts the boolean false to the string "false" and creates data-ready="false". delete card.dataset.mode removes the data-mode attribute entirely. Now let's evaluate each item in result. getAttribute("data-user-id") returns "12" — a string, since attributes are always strings. getAttribute("data-ready") returns "false" — again a string, not a boolean. hasAttribute("data-mode") returns false because the attribute was deleted. Finally, card.dataset.ready returns "false" — still a string, since dataset reads back whatever string was stored. This gives you ["12", "false", false, "false"], which is A. B is wrong because it uses primitive types (12, false) instead of strings — getAttribute and dataset never return numbers or booleans. C is wrong because getAttribute("data-ready") returns "false", not null — null would only appear if the attribute didn't exist at all. D is wrong because it shows "7" for data-user-id, ignoring the assignment that updated it to 12. The key rule to memorize: dataset always stores and returns strings. If you assign false, you get back "false".

Start this quiz