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

Odin Project图书馆应用切换阅读状态时出现Cannot read properties of undefined错误

问题描述

基于The Odin Project开发图书馆应用时,点击「切换阅读状态」按钮尝试将书籍状态从「未读」改为「已读」时,出现Cannot read properties of undefined错误,错误发生在读取myLibrary[parseInt(retrieveBookToToggle)].status的代码行。

错误代码
console.log(
  "What is the toggle initial value?...",
  myLibrary[parseInt(retrieveBookToToggle)].status
);

let myLibrary = [];
const btnAdd = document.querySelector("btn-add");

//Object Constructor
class Book {
  constructor(title, author, pages, status) {
    this.title = title;
    this.author = author;
    this.pages = pages;
    this.status = status;
  }

  //Add a new book to array
  function addBookToLibrary(title, author, pages, status) {
    let book = new Book(title, author, pages, status);
    myLibrary.push(book);
    displayBooksOnPage()
  }


  //Display array to card
  function displayBooksOnPage() {
    const books = document.querySelector(".books-container");

    //remove displayed cards before loop array
    const removeDivs = document.querySelectorAll(".card");
    console.log("Show me the node count of the current card divs........", removeDivs);
    for (let i = 0; i < removeDivs.length; i++) {
      removeDivs[i].remove();
    }

    //Loop Library array and display to the cards
    let index = 0;
    myLibrary.forEach(myLibrarys => {
      const card = document.createElement("div");
      card.classList.add("card");
      books.appendChild(card);

      //remove book button
      const removeBookButton = document.createElement("button");
      removeBookButton.classList.add("remove-book-button");
      removeBookButton.textContent = "Remove Book";
      console.log("show me my currentnarray objects inside of foreach........", myLibrary);

      //link data attribute of the remove button to the array and card
      removeBookButton.dataset.linkedArray = index;
      index++;
      console.log("show me dataset link back to the array...", removeBookButton.dataset.linkedArray);
      card.appendChild(removeBookButton);

      //start event listener/remove array item from array and card from parent div via data link
      removeBookButton.addEventListener("click", removeBookFromLibrary);

      function removeBookFromLibrary() {
        let retrieveBookToRemove = removeBookButton.dataset.linkedArray;
        console.log("attempting to remove array item via data attribute...", parseInt(retrieveBookToRemove));
        myLibrary.splice(parseInt(retrieveBookToRemove), 1);
        card.remove();
        displayBooksOnPage;
      }

      const readStatusButton = document.createElement("button");
      readStatusButton.classList.add("read-status-button");
      readStatusButton.textContent = "Change Status";

      readStatusButton.dataset.linkedArray = index;
      console.log("show dataset link back to the array for read status button", readStatusButton.dataset.linkedArray);
      card.appendChild(readStatusButton);

      readStatusButton.addEventListener("click", toggleReadStatus);

      function toggleReadStatus() {
        let retrieveBookToToggle = readStatusButton.dataset.linkedArray;
        Book.prototype = Object.create(Book.prototype);
        const toggleBook = new Book();
        console.log("what's the toggle initial value? ", myLibrary[parseInt(retrieveBookToToggle)].status);
        if (myLibrary[parseInt(retrieveBookToToggle)].status == "Read") {
          toggleBook.status = "Not Read";
          myLibrary[parseInt(retrieveBookToToggle)].status = toggleBook.status;
        } else if (myLibrary[parseInt(retrieveBookToToggle)].status == "Not Read") {
          toggleBook.status = "Read";
          myLibrary[parseInt(retrieveBookToToggle)].status = toggleBook.status;
        }
        displayBooksOnPage();
      }



      for (let key in myLibrarys) {
        const para = document.createElement("p");
        para.textContent = (`${myLibrarys[key]}`);
        card.appendChild(para);
      }
    })
  }

  const addBookButton = document.querySelector(".btn-add");
  addBookButton.addEventListener("click", displayTheForm);

  function displayTheForm() {
    document.getElementById("books-form").style.display = "";
  }

  const submitButton = document.querySelector(".btn-add");
  submitButton.addEventListener("click", intakeFormData);

  function intakeFormData() {
    let Title = document.getElementById("title").value;
    let Author = document.getElementById("author").value;
    let Pages = document.getElementById("pages").value;
    let Status = document.getElementById("status").value;

    if ((Title == "") || (Author == "") || (Pages == "") || (Status == "")) {
      return;
    }

    addBookToLibrary(Title, Author, Pages, Status);

    document.getElementById("add-book").reset();
  }

  const clearButton = document.getElementById("btn-reset");
  clearButton.addEventListener("click", clearForm());

  function clearForm() {
    document.getElementById("add-book").reset();
  }
修复方案

1. 修正状态按钮的索引值(核心错误)

错误原因:给「切换状态」按钮的dataset.linkedArray赋值时,用的是index++之后的数值,导致索引比实际数组元素的索引大1,访问时超出数组范围拿到undefined。

修复代码:

// 移除按钮的索引赋值保持不变
removeBookButton.dataset.linkedArray = index;
// 状态按钮同样使用当前的index,不要用自增后的数值
readStatusButton.dataset.linkedArray = index;
// 统一在两个按钮赋值后再执行index自增
index++;

调整后的关键代码片段:

// 给移除按钮赋值索引
removeBookButton.dataset.linkedArray = index;
// 给状态按钮赋值相同的当前索引
readStatusButton.dataset.linkedArray = index;
// 最后执行索引自增
index++;

2. 修正类内部的函数声明错误

class内部不能用function关键字声明方法,需要把addBookToLibrary、displayBooksOnPage等函数移到类外部,因为这些是全局操作逻辑,不属于Book实例的方法。

示例:

let myLibrary = [];
const btnAdd = document.querySelector("btn-add");

//Object Constructor
class Book {
  constructor(title, author, pages, status) {
    this.title = title;
    this.author = author;
    this.pages = pages;
    this.status = status;
  }
}

// 将addBookToLibrary移到类外部
function addBookToLibrary(title, author, pages, status) {
  let book = new Book(title, author, pages, status);
  myLibrary.push(book);
  displayBooksOnPage()
}

// displayBooksOnPage同样移到类外部
function displayBooksOnPage() {
  // ...原函数内容
}

3. 移除toggleReadStatus中多余的Book实例创建

切换状态时不需要重新创建Book对象,直接修改数组中对应元素的status即可:

function toggleReadStatus() {
  let retrieveBookToToggle = readStatusButton.dataset.linkedArray;
  const book = myLibrary[parseInt(retrieveBookToToggle)];
  if (book.status === "Read") {
    book.status = "Not Read";
  } else {
    book.status = "Read";
  }
  displayBooksOnPage();
}

4. 修正事件监听的函数引用错误

  • removeBookFromLibrary中调用displayBooksOnPage时缺少括号,应该是displayBooksOnPage();
  • clearButton的事件监听错误地执行了clearForm(),应该传递函数引用clearForm:
clearButton.addEventListener("click", clearForm);
修复后的完整代码
let myLibrary = [];
const btnAdd = document.querySelector("btn-add");

//Object Constructor
class Book {
  constructor(title, author, pages, status) {
    this.title = title;
    this.author = author;
    this.pages = pages;
    this.status = status;
  }
}

//Add a new book to array
function addBookToLibrary(title, author, pages, status) {
  let book = new Book(title, author, pages, status);
  myLibrary.push(book);
  displayBooksOnPage()
}

//Display array to card
function displayBooksOnPage() {
  const books = document.querySelector(".books-container");

  //remove displayed cards before loop array
  const removeDivs = document.querySelectorAll(".card");
  console.log("Show me the node count of the current card divs........", removeDivs);
  for (let i = 0; i < removeDivs.length; i++) {
    removeDivs[i].remove();
  }

  //Loop Library array and display to the cards
  let index = 0;
  myLibrary.forEach(myLibrarys => {
    const card = document.createElement("div");
    card.classList.add("card");
    books.appendChild(card);

    //remove book button
    const removeBookButton = document.createElement("button");
    removeBookButton.classList.add("remove-book-button");
    removeBookButton.textContent = "Remove Book";
    console.log("show me my current array objects inside of foreach........", myLibrary);

    //read status button
    const readStatusButton = document.createElement("button");
    readStatusButton.classList.add("read-status-button");
    readStatusButton.textContent = "Change Status";

    //link data attribute for both buttons
    removeBookButton.dataset.linkedArray = index;
    readStatusButton.dataset.linkedArray = index;
    index++;
    
    console.log("show me dataset link back to the array...", removeBookButton.dataset.linkedArray);
    console.log("show dataset link back to the array for read status button", readStatusButton.dataset.linkedArray);
    
    card.appendChild(removeBookButton);
    card.appendChild(readStatusButton);

    //remove book event listener
    removeBookButton.addEventListener("click", removeBookFromLibrary);

    function removeBookFromLibrary() {
      let retrieveBookToRemove = removeBookButton.dataset.linkedArray;
      console.log("attempting to remove array item via data attribute...", parseInt(retrieveBookToRemove));
      myLibrary.splice(parseInt(retrieveBookToRemove), 1);
      card.remove();
      displayBooksOnPage();
    }

    //toggle status event listener
    readStatusButton.addEventListener("click", toggleReadStatus);

    function toggleReadStatus() {
      let retrieveBookToToggle = readStatusButton.dataset.linkedArray;
      const book = myLibrary[parseInt(retrieveBookToToggle)];
      console.log("what's the toggle initial value? ", book.status);
      book.status = book.status === "Read" ? "Not Read" : "Read";
      displayBooksOnPage();
    }

    //render book info
    for (let key in myLibrarys) {
      const para = document.createElement("p");
      para.textContent = (`${myLibrarys[key]}`);
      card.appendChild(para);
    }
  })
}

const addBookButton = document.querySelector(".btn-add");
addBookButton.addEventListener("click", displayTheForm);

function displayTheForm() {
  document.getElementById("books-form").style.display = "";
}

const submitButton = document.querySelector(".btn-add");
submitButton.addEventListener("click", intakeFormData);

function intakeFormData() {
  let Title = document.getElementById("title").value;
  let Author = document.getElementById("author").value;
  let Pages = document.getElementById("pages").value;
  let Status = document.getElementById("status").value;

  if ((Title === "") || (Author === "") || (Pages === "") || (Status === "")) {
    return;
  }

  addBookToLibrary(Title, Author, Pages, Status);

  document.getElementById("add-book").reset();
}

const clearButton = document.getElementById("btn-reset");
clearButton.addEventListener("click", clearForm);

function clearForm() {
  document.getElementById("add-book").reset();
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 11:15:39