Vanilla JS图书馆项目:移除书籍与阅读状态切换功能故障
图书馆项目移除书籍与切换阅读状态功能故障排查
我使用Vanilla JS与HTML开发一个图书馆项目,整体功能已基本完成,但移除书籍(removeBook)和切换阅读状态(toggleRead)两个函数无法正常工作:
toggleRead函数执行book.isRead = !book.isRead时触发TypeErrorremoveBook函数完全无法实现移除功能
有问题的函数代码片段
const removeBook = (e) => { const title = e.currentTarget.innerHTML.replaceAll( '"', '', 'Title: ') library.removeBook(title) updateLibContainer() console.log('remove'); }
const toggleRead = (e) => { const title = e.currentTarget.innerHTML.replaceAll( '"', '', 'Title: ') const book = library.getTitle(title) book.isRead = !book.isRead console.log('read'); updateLibContainer() }
项目完整代码
JavaScript代码
// Selectors for pop up and closing pop up when x is pressed const popUp = document.querySelector('.pop-up'); const closePopUp = document.getElementsByTagName('span')[0]; closePopUp.addEventListener('click', () => popUp.style.display = 'none') const newBookBtn = document.querySelector('.newbtn'); newBookBtn.addEventListener('click', () => popUp.style.display = 'block') // Constructor for books class Book { constructor( title = 'Unkown', author = 'Unkown', pages = 0, isRead = false ) { this.title = title this.author = author this.pages = pages this.isRead = isRead } } // Constructor for Library class Library { constructor() { this.books = [] } addBook(newBook) { this.books.push(newBook) } removeBook(title) { this.books = this.books.filter((book) => book.title !== title) } getTitle(title) { return this.books.find(book => book.title === title) } isInLibrary(title) { return this.books.some(book => book.title === title) } } const library = new Library() // Additional UI declarations const LibContainer = document.getElementById('lib-container') const addBookForm = document.getElementById('add-form') // Handling and creating book from form input data const bookFromInput = () => { const title = document.getElementById('title').value const author = document.getElementById('author').value const pages = document.getElementById('pages').value const isRead = document.getElementById('is-read').checked return new Book(title, author, pages, isRead) } // Function when form is submitted to create new book from input, check if it exists // and close form const addNewBook = (e) => { e.preventDefault() const newBook = bookFromInput() if (library.isInLibrary(newBook.title)) { alert('Book already exists') } else { library.addBook(newBook) exitForm() updateLibContainer() } } const exitForm = () => { addBookForm.reset() popUp.style.display = 'none' } const resetLibContainer = () => { LibContainer.innerHTML = '' console.log('reset Container'); } const updateLibContainer = () => { resetLibContainer() for (let book of library.books) { createBookCard(book) } console.log('updateContainer'); } // Creating book card const createBookCard = (book) => { const bookCard = document.createElement('div') const title = document.createElement('p') const author = document.createElement('p') const pages = document.createElement('p') const changeReadStatus = document.createElement('button') const removeBookBttn = document.createElement('button') bookCard.classList.add('book-card') changeReadStatus.classList.add('change-read-status') changeReadStatus.onclick = toggleRead removeBookBttn.classList.add('remove-book') removeBookBttn.onclick = removeBook title.textContent = `Title: ${book.title}` author.textContent = `Author: ${book.author}` pages.textContent = `Pages: ${book.pages}` removeBookBttn.textContent = 'Remove' if (book.isRead) { changeReadStatus.textContent = 'Read' changeReadStatus.style.backgroundColor = '#68f364' } else { changeReadStatus.textContent = 'Not read' changeReadStatus.style.backgroundColor = '#d16767' } bookCard.appendChild(title) bookCard.appendChild(author) bookCard.appendChild(pages) bookCard.appendChild(changeReadStatus) bookCard.appendChild(removeBookBttn) LibContainer.appendChild(bookCard) } const removeBook = (e) => { const title = e.currentTarget.innerHTML.replaceAll( '"', '', 'Title: ') library.removeBook(title) updateLibContainer() console.log('remove'); } const toggleRead = (e) => { const title = e.currentTarget.innerHTML.replaceAll( '"', '', 'Title: ') const book = library.getTitle(title) book.isRead = !book.isRead console.log('read'); updateLibContainer() } addBookForm.onsubmit = addNewBook
HTML代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Library</title> <link rel="stylesheet" href="style.css"> </head> <body> <div class="header"> <h1>The Library</h1> </div> <div class="newbtn-container"> <button class="newbtn">+ New Book</button> </div> <div class="pop-up"> <form class="form" action id="add-form"> <span>x</span> <legend>New book</legend> <input type="text" id="title" class="form" placeholder="Title"> <input type="text" id="author" class="form" placeholder="Author"> <input type="text" id="pages" class="form" placeholder="Pages"> <div class="checkbox"> <label>Read: </label> <input type="checkbox" class="form" id="is-read"> </div> <button class="form" id="submit">Add</button> </form> </div> <div id="lib-container"> <div class="book-card"> <p>Title: 22</p> <p>Author: 22</p> <p>Pages: 22</p> <button class="change-read-status" style="background-color: rgb(209, 103, 103);">Not read</button> <button class="remove-book">Remove</button> </div> </div> </body> <script src="script.js"></script> </html>
问题根源
e.currentTarget指向的是触发事件的按钮(移除按钮的innerHTML是"Remove",状态切换按钮的innerHTML是"Read"或"Not read"),而非书籍标题元素。你试图通过replaceAll从按钮的文本中提取标题,这完全无法得到正确的书籍标题,导致:
library.getTitle(title)返回undefined,执行book.isRead = !book.isRead时触发TypeErrorlibrary.removeBook(title)找不到对应书籍,所以无法移除
修复方案
方案一:使用自定义属性存储书籍标题
修改createBookCard函数,给两个按钮添加data-title属性存储书籍标题:
const createBookCard = (book) => { // ... 其他代码不变 changeReadStatus.classList.add('change-read-status') changeReadStatus.onclick = toggleRead // 添加自定义属性 changeReadStatus.setAttribute('data-title', book.title) removeBookBttn.classList.add('remove-book') removeBookBttn.onclick = removeBook // 添加自定义属性 removeBookBttn.setAttribute('data-title', book.title) // ... 其他代码不变 }
然后修改removeBook和toggleRead函数,从自定义属性中获取标题:
const removeBook = (e) => { const title = e.currentTarget.dataset.title; library.removeBook(title); updateLibContainer(); console.log('remove'); } const toggleRead = (e) => { const title = e.currentTarget.dataset.title; const book = library.getTitle(title); // 增加判断避免找不到书籍时报错 if (book) { book.isRead = !book.isRead; updateLibContainer(); console.log('read'); } }
方案二:通过DOM遍历获取标题
无需修改卡片创建逻辑,直接从按钮所在的卡片中找到标题元素:
const removeBook = (e) => { // 找到按钮所在的书籍卡片 const bookCard = e.currentTarget.closest('.book-card'); // 获取标题元素的文本内容 const titleText = bookCard.querySelector('p').textContent; // 提取纯标题文本 const title = titleText.replace('Title: ', ''); library.removeBook(title); updateLibContainer(); console.log('remove'); } const toggleRead = (e) => { const bookCard = e.currentTarget.closest('.book-card'); const titleText = bookCard.querySelector('p').textContent; const title = titleText.replace('Title: ', ''); const book = library.getTitle(title); if (book) { book.isRead = !book.isRead; updateLibContainer(); console.log('read'); } }
两种方案都能解决问题,方案一更简洁高效,方案二无需修改卡片创建逻辑,可根据需求选择。
内容的提问来源于stack exchange,提问作者Sinned
相关产品推荐
相关产品推荐

