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

塔罗牌Web应用点击触发Uncaught TypeError: Property 'handleEvent' is not callable错误求助

解决"Uncaught TypeError: Property 'handleEvent' is not callable"错误及塔罗牌抽卡功能修复

问题根源

  1. 事件监听绑定错误:cardb.addEventListener("click", changeCard("a"))这种写法会立即执行changeCard("a"),而非将函数作为事件处理函数传递。由于changeCard没有返回值,实际传给监听的是undefined,浏览器尝试调用undefined的handleEvent方法,触发报错。
  2. 函数提前终止:jackandjill函数开头的return语句直接终止了函数执行,后续的changeCard定义、事件绑定代码完全未运行。
  3. eval滥用:用eval获取DOM元素是不良实践,容易引发错误和安全问题。
  4. 重复请求JSON:每次抽卡都重新请求本地JSON,效率低下。
  5. 无效的setTimeout:setTimeout(500)不符合语法要求,正确用法需要传入函数作为第一个参数。

修复方案

1. 正确绑定事件监听

将changeCard改为返回函数的形式,确保传递给addEventListener的是可调用的函数:

function changeCard(inputLetter) {
  const cards = {a: carda, b: cardb};
  const card = cards[inputLetter];
  
  return async function() {
    // 抽卡逻辑...
  }
}
// 绑定事件时,传递changeCard返回的函数
cardb.addEventListener("click", changeCard("a"));

2. 移除无效的return语句

删掉jackandjill函数开头的return,让后续代码正常执行。

3. 替换eval,使用对象映射

用对象存储DOM元素的映射,替代eval:

const cards = {a: carda, b: cardb};
const card = cards[inputLetter];

4. 预加载并缓存JSON数据

页面初始化时一次性加载塔罗牌数据,避免重复请求:

let tarotCards = null;
async function loadTarotData() {
  const response = await fetch('tarotdeck.json');
  const data = await response.json();
  tarotCards = data.cards;
}

5. 修正setTimeout或移除无用延迟

如果不需要延迟执行,直接删掉无效的setTimeout;如果需要延迟,改为正确写法:

setTimeout(() => {
  // 要延迟执行的代码
}, 500);

完整修正代码

JavaScript部分

let carda = document.querySelector("div.carda");
let cardb = document.querySelector("div.cardb");
let cardName = document.querySelector("h3.name");
let moreInfo = document.querySelector("p.about");

// 预加载塔罗牌数据,只加载一次
let tarotCards = null;
async function loadTarotData() {
  const response = await fetch('tarotdeck.json');
  const data = await response.json();
  tarotCards = data.cards;
}

async function drawCard(index) {
  if (!tarotCards) await loadTarotData();
  return Object.values(tarotCards[index]);
}

function getRandomNumber(min, max) {
  let step1 = max - min + 1;
  let step2 = Math.random() * step1;
  let result = Math.floor(step2) + min;
  return result;
}

// 重构changeCard,返回一个事件处理函数
function changeCard(inputLetter) {
  // 用对象替代eval
  const cards = {a: carda, b: cardb};
  const card = cards[inputLetter];

  return async function() {
    const ranIndex = getRandomNumber(0, 78);
    const cardDrawn = await drawCard(ranIndex);
    
    cardName.innerHTML = cardDrawn[0];
    moreInfo.innerHTML = cardDrawn[1];
    
    card.classList.remove("default-styling");
    cardName.classList.remove("hide");
    moreInfo.classList.remove("hide");
  }
}

// 页面加载完成后绑定事件
window.addEventListener('DOMContentLoaded', async () => {
  // 先加载数据
  await loadTarotData();
  // 绑定事件,这里传递的是changeCard返回的函数
  cardb.addEventListener("click", changeCard("a"));
  carda.addEventListener("click", changeCard("b"));
});

HTML部分(修正id拼写错误)

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Daily Tarot</title>
  <link rel="stylesheet" href="style.css" />
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous" />
  <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
  <link rel="manifest" href="/site.webmanifest">
</head>

<body>
  <div class="app_container">
    <h1>Tarot</h1>
    <div id="carda">
      <div class="carda default-styling">
        </br>
      </div>
      <div id="cardb">
        <div class="cardb default-styling">
          </br>
        </div>
        <h3 class="name hide">Name of card</h3>
        <p class="about hide">About card</p>
        <h3 class="arcana hide">arcana card</h3>
        <p class="suit hide">suit card</p>
        <p class="fortune hide">Fortune telling</p>
        <p class="keywords hide"> keywords </p>
        <p class="meanings hide"> meanings </p>
        <p class="Archetype hide">Archetype </p>
        <p class="Hebrew hide">Hebrew Alphabet </p>
        <p class="Numerology hide">Numerology </p>
        <p class="Elemental hide">Elemental </p>
        <p class="Mythic hide">Mythical/Spiritual </p>
        <p class="Questions hide"> Questions to Ask </p>
      </div>
    </div>
  </div>
  <small>The images and information about each card are from
      <a href="https://www.tarot.com/tarot/cards">Tarot.com</a><br /><a
        href="https://github.com/SamJamWillingham/Tarot-Card-Drawer"
        >Open course code</a>
        </small>
  <script src="script.js"></script>
</body>

</html>

CSS部分

div.app_container {
  max-width: 600px;
  border-radius: 8px;
  margin: 15px auto;
  background-image: linear-gradient(-225deg, #e3fdf5 0%, #ffe6fa 100%);
  box-shadow: 8px 8px 10px rgba(0, 0, 0, 0.281);
  padding: 25px;
}
div.default-styling {
  display: block;
  margin: 0 auto;
  color: #000000;
  background-color: aqua; /*Delete me!*/
  text-align: center;
  border: 1px solid #000000;
  border-radius: 10px;
  background-color: rgba(255, 255, 255, 0.1);
  max-width: 220px;
  padding-top: 125px;
  padding-bottom: 125px;
}

img.cardImg{
  display: block;
  margin: 0 auto;
  max-width: 189px;
  height: 320px;
}
div.newCard, .carda, .cardb{
  display: block;
  margin: 0 auto;
  max-width: 210px;
  border: none;
  background-color: red; /*Delete me!*/
  border-radius: 15px;
}
#cardb{
  float: right;
  width: 300px;
  height: 300px;
}
.cardb{
  float: left;
  height: 320px;
  width: 300px;
}
h1 {
  font-family: "cursive";
  text-align: center;
}
h3,
p {
  font-family: "sans-serif";
  text-align: left;
}
small {
  display: block;
  margin: 0 auto;
  text-align: center;
}
.hide {
  display: none;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 03:09:27