You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

循环中<li>的addEventListener不触发及相关技术疑问

问题求助:点击伪元素为<li>添加类的解决方案

Hey there! I see you've been stuck on this for two days—let's fix this together. First, let's go over why your original code wasn't working, then share a polished solution, and finally answer all your specific questions clearly.

First: Why Your Original Code Failed

Let's break down the key issues:

  1. HTML inline onclick conflict: You added onclick="showMe()" to your <li>, which calls the function in the global scope (where this points to window, not the <li>) and overrides part of your addEventListener logic.
  2. Loop variable hoisting bug: In your second try, pullTextOnClick uses liitems[i], but by the time the click event fires, the loop has finished and i equals liitems.length—so liitems[i] is undefined.
  3. Incorrect bind usage: Your first try's bind(this, lit) sets this to window (since you're inside window.onload) instead of the target <li>.
  4. Pseudo-element note: Remember, ::after isn't a real DOM element, so you can't bind events directly to it—but clicks on it bubble up to its parent <li>, so binding the <li> is the right approach.

Fixed Working Code

Here's the corrected version that works as expected:

HTML

<div class="container">
  <section class="timeline" id="timelineexpand">
    <ul>
      <li class="liitems" id="li1">
        <div>
          <time>1992</time> Sth happend
        </div>
      </li>
      <li class="liitems" id="li2">
        <div>
          <time>1997</time> Sth else
        </div>
      </li>
    </ul>
  </section>
</div>

CSS (added a test style for in-view)

.timeline ul li {
  list-style-type:none;
  position:relative;
  width:6px;
  margin:0 auto;
  padding-top:50px;
  background:#fff;
}
.timeline ul li::after{
  content:'';
  position:absolute;
  left:50%;
  bottom:0;
  transform: translateX(-50%);
  width:50px;
  height:50px;
  border-radius:50%;
  background: inherit;
  cursor:pointer;
}
/* Visual feedback for the active class */
.timeline ul li.in-view {
  background: #ff4444;
}

JavaScript

/* jslint browser: true */
/*global window */
window.onload = function() {
  // Use querySelectorAll for cleaner selection
  const liItems = document.querySelectorAll(".liitems");
  
  // Event handler: `this` points to the clicked <li> by default
  function handleLiClick() {
    this.classList.add("in-view");
    console.log(this.className);
  }
  
  // Bind the handler to each <li>
  liItems.forEach(item => {
    item.addEventListener("click", handleLiClick);
  });
};

Answers to Your Specific Questions

1. What does /*global window */ do? Why do I get "window.onload is undefined" without it?

This is a JSLint directive comment. JSLint is a strict code-linting tool that checks for undeclared variables. Without this comment, JSLint doesn't recognize window as a built-in global variable, so it throws a warning saying "window.onload is undefined". If you don't use JSLint, you can safely remove this comment—its only purpose is to satisfy the linter.

2. How do I bind the target <li> element to the event handler?

You have three reliable options:

  • Use this directly: When you bind a function with addEventListener, this automatically refers to the DOM element that triggered the event (your <li>). This is the simplest approach, as shown in the fixed code.
  • Use event.currentTarget: If you need to be explicit, access the bound element via the event object:
    function handleLiClick(event) {
      const targetLi = event.currentTarget; // Always the element you bound the event to
      targetLi.classList.add("in-view");
    }
    
  • Avoid inline onclick attributes: Inline attributes like onclick="showMe()" force this to point to window (global scope), so they're best avoided in favor of addEventListener.

3. How does bind() work? Why didn't my code use it correctly?

Function.prototype.bind() creates a new copy of your function with two key changes:

  1. The this value inside the new function is permanently set to the first argument you pass to bind().
  2. Any additional arguments you pass to bind() are preset as parameters for the new function.

Your code failed because:

  • You passed this (which is window inside window.onload) as the first argument to bind(), so showMe's this became window instead of the <li>.
  • You also had an inline onclick calling the original showMe function, so your bound version was never actually used.

If you wanted to use bind intentionally (though it's unnecessary here), you could do this:

const lit = document.getElementById("li1");
function showMe(element) {
  element.classList.add("in-view");
}
// Bind the <li> as the first parameter to showMe
lit.addEventListener("click", showMe.bind(null, lit));

4. Why does addClass() throw an error, but classList.add() works?

addClass() is a jQuery method, not native JavaScript. If you haven't included the jQuery library in your page, calling addClass() will throw a "function not defined" error.

classList.add() is a native DOM API supported by all modern browsers—no external libraries required. It's the standard way to add classes to elements in vanilla JS.

If you really want to use addClass(), you'd need to first include jQuery in your HTML:

<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>

Then use jQuery's syntax:

$(".liitems").click(function() {
  $(this).addClass("in-view");
});

But native classList is more lightweight and doesn't require extra dependencies, so it's the better choice here.


内容的提问来源于stack exchange,提问作者Zheka

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:26:52