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

点击按钮向数组添加影视数据异常:仅获最后一项数据求助

问题原因
  1. 异步循环导致DOM渲染与事件绑定不同步:原代码用forEach(async...)遍历电影列表,异步请求电影详情时循环会继续执行,导致后续querySelectorAll('.watchlist')执行时,大部分电影卡片还未添加到DOM,无法获取全部按钮元素。
  2. 闭包变量污染:事件回调里的data变量是循环中的同一变量,异步循环结束后会指向最后一次循环的电影数据,所以点击任何按钮都会获取最后一个电影的信息。
  3. innerHTML拼接无法关联数据:用字符串拼接HTML的方式,无法直接将按钮与对应的电影数据绑定,只能事后通过DOM查询,容易出现数据不匹配的问题。
解决方案

改用Promise.all统一处理所有异步请求,确保电影数据全部获取后再渲染DOM,同时为每个按钮单独绑定事件,直接关联当前电影的数据:

//api key
const apikey = ''

//grab DOM elements
const searchInput = document.getElementById('input-el')
const searchBtn = document.getElementById('button-el')
const movieList = document.getElementById('movies')

//array of objects that makes up watchlist
let watchlist = []
// 页面加载时从本地存储恢复数据
if(localStorage.getItem('watchlist')) {
    watchlist = JSON.parse(localStorage.getItem('watchlist'))
}

//determine whether to display add or remove button 
let addRemoveWatchlist = ''

//fetch api data if search input is truthy
const fetchMovies = async(searchTerm) => {
    const URL = `http://www.omdbapi.com/?apikey=${apikey}&s=${searchTerm}`
    const res = await fetch(`${URL}`)
    const movies = await res.json()
    if(movies.Response == 'True') {
        displayMovieList(movies.Search)
    } else {
        movieList.innerHTML = `
            <section class='movie-card'>
                We can't seem to find anything . . . try being more specific
            </section>
            `
    }
}

const fetchMoreInfo = async(movieId) => {
    const URL = `http://www.omdbapi.com/?apikey=${apikey}&i=${movieId}`
    const res = await fetch(`${URL}`)
    const moreInfo = await res.json()
    return moreInfo 
}

//loop through the api data array and display in html
const displayMovieList = async(movies) => {
    movieList.innerHTML = ''
    // 先批量获取所有电影的详细数据,确保异步操作全部完成
    const movieDetails = await Promise.all(
        movies.map(movie => fetchMoreInfo(movie.imdbID))
    )
    
    movieDetails.forEach(data => { 
        // 创建卡片元素
        const movieCard = document.createElement('section')
        movieCard.className = 'movie-card'
        
        // 填充卡片内容
        movieCard.innerHTML = `
            <img src='${data.Poster}' class='movie-img' />
            <div class='movie-info'>
                <section class='section1'>
                    <div class='movie-title'>${data.Title}</div>
                    <div class='movie-year'>${data.Year}</div>
                    <div class='imdb-rating'><i class="fa-solid fa-star"></i>${data.imdbRating}</div>
                </section>
                <section class='section2'>
                    <div class='movie-rating'><b>${data.Rated}</b></div>
                    <div class='movie-runtime'>${data.Runtime}</div>
                    <button class='watchlist'>Add to Watchlist</button>
                </section>
                <section class='section3'>
                    <div class='actors'><b>Actors:</b> ${data.Actors}</div>
                    <div class='director'><b>Director:</b> ${data.Director}</div>
                    <div class='plot'>${data.Plot}</div>
                </section>
            </div>
        `
        
        // 给当前卡片的按钮绑定点击事件,直接使用当前循环的data
        const watchBtn = movieCard.querySelector('.watchlist')
        watchBtn.addEventListener('click', () => {
            // 将电影的关键信息存入对象,添加到watchlist
            const movieToAdd = {
                id: data.imdbID,
                title: data.Title,
                year: data.Year,
                poster: data.Poster,
                rating: data.imdbRating
            }
            // 避免重复添加
            if(!watchlist.find(item => item.id === movieToAdd.id)) {
                watchlist.push(movieToAdd)
                // 同步到本地存储
                localStorage.setItem('watchlist', JSON.stringify(watchlist))
                console.log(watchlist)
            }
        })
        
        // 将卡片和分隔线添加到页面
        movieList.appendChild(movieCard)
        const hr = document.createElement('hr')
        movieList.appendChild(hr)
    })
}

//grab search bar input
const findMovies = ()  => {
    let searchTerm = searchInput.value
    fetchMovies(searchTerm)
}

searchBtn.addEventListener('click', findMovies)

searchInput.addEventListener('keypress', (e) => {
    if (e.key === 'Enter') {
        searchBtn.click()
    }
})
关键改动说明
  • 批量异步处理:用Promise.all+map替代异步forEach,确保所有电影详情都获取完成后再渲染DOM,避免事件绑定时机错误。
  • 元素级事件绑定:每个电影卡片创建后,直接获取内部的按钮并绑定事件,此时data是当前电影的具体数据,不会出现闭包污染问题。
  • 本地存储同步:添加了从本地存储恢复数据、同步存储的逻辑,确保页面刷新后watchlist数据不会丢失。
  • 避免重复添加:添加电影时检查是否已存在,防止重复存入相同电影。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 08:21:57