创建10个标签,点击时弹出对应的序号

理解 JavaScript 闭包和作用域的经典面试题,掌握循环中事件绑定的正确方式

问题

这是一道经典的 JavaScript 闭包面试题。需要创建 10 个标签(如按钮或 div),当点击每个标签时,弹出对应的序号(0-9)。

这道题的考点是:

  • JavaScript 的作用域和闭包机制
  • 循环中绑定事件的常见陷阱
  • 如何正确保存循环变量的值

解答

方法一:使用闭包(IIFE)

// 创建容器
const container = document.getElementById('container');

// 使用 var 声明 + 闭包解决
for (var i = 0; i < 10; i++) {
  const btn = document.createElement('button');
  btn.innerText = `按钮${i}`;
  
  // 使用立即执行函数创建闭包,保存当前的 i 值
  (function(index) {
    btn.onclick = function() {
      alert(`当前点击的是第 ${index} 个按钮`);
    };
  })(i);
  
  container.appendChild(btn);
}

方法二:使用 let 块级作用域

const container = document.getElementById('container');

// 使用 let 声明,每次循环创建新的块级作用域
for (let i = 0; i < 10; i++) {
  const btn = document.createElement('button');
  btn.innerText = `按钮${i}`;
  
  btn.onclick = function() {
    alert(`当前点击的是第 ${i} 个按钮`);
  };
  
  container.appendChild(btn);
}

方法三:使用自定义属性

const container = document.getElementById('container');

for (var i = 0; i < 10; i++) {
  const btn = document.createElement('button');
  btn.innerText = `按钮${i}`;
  
  // 将索引保存在元素的自定义属性中
  btn.setAttribute('data-index', i);
  
  btn.onclick = function() {
    const index = this.getAttribute('data-index');
    alert(`当前点击的是第 ${index} 个按钮`);
  };
  
  container.appendChild(btn);
}

方法四:使用事件委托

const container = document.getElementById('container');

// 创建所有按钮
for (let i = 0; i < 10; i++) {
  const btn = document.createElement('button');
  btn.innerText = `按钮${i}`;
  btn.setAttribute('data-index', i);
  container.appendChild(btn);
}

// 在父元素上绑定事件(事件委托)
container.onclick = function(e) {
  if (e.target.tagName === 'BUTTON') {
    const index = e.target.getAttribute('data-index');
    alert(`当前点击的是第 ${index} 个按钮`);
  }
};

使用示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>点击标签弹出序号</title>
  <style>
    #container {
      padding: 20px;
    }
    button {
      margin: 5px;
      padding: 10px 20px;
      cursor: pointer;
      font-size: 14px;
    }
  </style>
</head>
<body>
  <div id="container"></div>
  
  <script>
    const container = document.getElementById('container');
    
    // 推荐方案:使用 let
    for (let i = 0; i < 10; i++) {
      const btn = document.createElement('button');
      btn.innerText = `按钮${i}`;
      
      btn.onclick = function() {
        alert(`当前点击的是第 ${i} 个按钮`);
      };
      
      container.appendChild(btn);
    }
  </script>
</body>
</html>

关键点

  • 闭包原理:使用 IIFE(立即执行函数)创建独立的作用域,每次循环都会保存当前的 i 值,避免所有事件处理函数共享同一个变量

  • var vs letvar 是函数作用域,循环结束后 i 的值为 10;let 是块级作用域,每次循环都会创建新的变量绑定

  • 错误示范:直接使用 var 而不使用闭包会导致所有按钮点击时都显示 10

    for (var i = 0; i < 10; i++) {
      btn.onclick = function() {
        alert(i); // 所有按钮都会弹出 10
      };
    }
  • 最佳实践:在现代开发中,推荐使用 letconst,代码更简洁易懂

  • 事件委托优势:当需要创建大量元素时,使用事件委托可以减少内存占用,只需绑定一个事件监听器

  • 性能考虑:事件委托方式在创建大量元素时性能更好,因为只有一个事件监听器