Vanilla JavaScript商品收藏功能异常问题求助
商品收藏功能问题修复:切换商品后收藏按钮异常及收藏数组错误
问题现象
当前商品的收藏按钮可正常工作,但切换到下一个商品后,需点击两次才能完成收藏,且该操作会导致前一个已收藏商品取消收藏并从likedProduct数组中移除。预期所有商品的收藏按钮都能正常工作,likedProduct数组正确维护收藏商品。
问题根源
- 闭包捕获变量错误:原代码中
addLikedProduct仅在页面加载时绑定一次点击事件,事件处理函数捕获的current是初始加载的商品对象,切换商品后点击收藏按钮,修改的仍是旧商品的isLiked状态,而非当前显示的商品。 - 未同步本地存储的收藏状态:页面初始化时未从localStorage读取已收藏数据,直接从原始products数组过滤
likedProduct,导致商品的isLiked状态与实际收藏记录不一致;切换商品时也未根据收藏数组更新当前商品的状态。 - 状态与视图不同步:切换商品后,收藏按钮的图标未根据当前商品的实际收藏状态更新,导致首次点击需要先修正显示状态,出现“点击两次才生效”的问题。
修复方案及代码修改
关键修改步骤
- 初始化时同步本地存储数据:页面加载时读取localStorage中的收藏记录,同步更新products数组的
isLiked状态,确保初始状态正确。 - 事件委托优化:移除单独的
addLikedProduct函数,直接在productInfoContainer上绑定一次点击事件,通过元素ID定位当前商品,避免闭包陷阱。 - 状态与视图强同步:每次渲染商品信息时,根据
likedProduct数组判断当前商品的收藏状态,确保按钮图标与实际状态一致。
修改后的完整代码
export const products = [ { id: "01", name: "Air Jordan 1 - in orange for men", image: "./images/pic-1.png", brand: "Nike brand", rating: 4.5, price: 150, new: true, isLiked: false, }, { id: "02", name: "Air Jordan 1 - in yellow", image: "./images/pic-2.png", brand: "Nike brand", rating: 4.2, price: 138, new: true, isLiked: false, }, { id: "03", name: " PUMA Carina – Dynamic And Youthful", image: "./images/pic-3.png", brand: "Puma brand", rating: 4.0, price: 175, new: true, isLiked: false, }, { id: "04", name: " Reebok Club C85 – Walking, everyday wear", image: "./images/pic-4.png", brand: "Reebok brand", rating: 3.9, price: 105, new: true, isLiked: false, }, { id: "05", name: "Reebok Men's Ex-o-fit - Hi Sneaker", image: "./images/pic-5.png", brand: "Reebok brand", rating: 4.0, price: 199, new: true, isLiked: false, }, { id: "06", name: "Air Jordan 1 – Walking, everyday wear", image: "./images/pic-6.png", brand: "Nike brand", rating: 3.6, price: 175, new: true, isLiked: false, }, ]; const productBar = document.querySelector(".product-bar"); const currentProductImage = document.querySelector(".currentProductImage"); const productInfoContainer = document.querySelector(".product-info"); let current; let likedProduct = []; // 初始化收藏数据,同步localStorage与products数组状态 function initLikedProducts() { const saved = localStorage.getItem("likedProduct"); if (saved) { likedProduct = JSON.parse(saved); // 同步products数组的isLiked状态 likedProduct.forEach(likedProd => { const target = products.find(prod => prod.id === likedProd.id); if (target) target.isLiked = true; }); } else { likedProduct = products.filter(el => el.isLiked === true); } } // 渲染商品栏 function renderProductBar() { productBar.innerHTML = ""; const element = products .map( (prod) => ` <img src="${prod.image}" alt="" id="product" data-id="${prod.id}" class="shadow-md object-cover w-24 rounded-md"> ` ) .join(""); productBar.insertAdjacentHTML("afterbegin", element); } // 渲染当前商品图片 function renderCurrentImage(current) { currentProductImage.innerHTML = ""; const image = ` <img src="${current.image}" alt="" data-id="${current.id}" class="rounded-md rounded-b-none rounded-r-none "> `; currentProductImage.insertAdjacentHTML("afterbegin", image); } // 渲染当前商品信息 function renderCurrentProduct(id = "01") { productInfoContainer.innerHTML = ""; current = products.find((el) => el.id === id); renderCurrentImage(current); // 根据likedProduct判断当前商品的收藏状态(确保与本地存储同步) const isProductLiked = likedProduct.some(prod => prod.id === current.id); current.isLiked = isProductLiked; const prodInfo = ` <div class="flex items-center justify-between "> <span class="block px-2 py-1 text-xs text-orange-400 font-bold border-2 border-orange-400 rounded-full ">${ current.new ? "NEW" : "Out of Stock" }</span> <div class="relative w-8 h-8 fav" id="${current.id}"> <img src="./images/${isProductLiked ? "" : "non"}like.svg" class="duration-150 w-8 h-8 favSVG" alt=""></img> </div> </div> <p class="mt-2 text-lg brand md:text-base md1:text-lg text-stone-500">${ current.brand }</p> <h1 class="text-3xl font-semibold tracking-tight name md:text-2xl md1:text-3xl text-stone-900 ">${ current.name }</h1> <div class="relative flex mt-2"> <span class="text-base rating md:text-sm md1:text-base text-stone-500">${ current.rating } </span> <div class="flex -space-x-1"> <img src="./images/star.svg" alt="" class="w-5 h-5 md:w-4 md:h-4 md1:w-5 md1:h-5"/> <img src="./images/star.svg" alt="" class="w-5 h-5 md:w-4 md:h-4 md1:w-5 md1:h-5"/> <img src="./images/star.svg" alt="" class="w-5 h-5 md:w-4 md:h-4 md1:w-5 md1:h-5"/> <img src="./images/star.svg" alt="" class="w-5 h-5 md:w-4 md:h-4 md1:w-5 md1:h-5"/> <img src="./images/star.svg" alt="" class="w-5 h-5 md:w-4 md:h-4 md1:w-5 md1:h-5"/> </div> <a href="#" class="absolute text-sm underline duration-150 translate-y-6 md:text-xs md1:text-sm text-stone-400 hover:text-stone-500">Rate now</a> </div> <div class="mt-10 mb-4 price lg:mb-4 lg:mt-10"> <span class="text-xl font-semibold md:text-lg md1:text-xl text-stone-900">$</span> <span class="text-6xl font-bold md:text-4xl text-stone-900 md1:text-6xl pricing">${ current.price }</span> </div> `; productInfoContainer.insertAdjacentHTML("afterbegin", prodInfo); } // 切换商品收藏状态并更新localStorage function toggleLikedProduct(productId) { const product = products.find(prod => prod.id === productId); if (!product) return; const index = likedProduct.findIndex(el => el.id === productId); if (index === -1) { // 添加收藏 product.isLiked = true; likedProduct.push({...product}); // 存副本避免引用问题 } else { // 取消收藏 product.isLiked = false; likedProduct.splice(index, 1); } localStorage.setItem("likedProduct", JSON.stringify(likedProduct)); } // 更新收藏按钮图标 function toggleLoveBtn(productId) { const loveBtn = productInfoContainer.querySelector(`.fav#${productId}`); if (!loveBtn) return; const loveSvg = loveBtn.querySelector("img"); const isLiked = likedProduct.some(prod => prod.id === productId); loveSvg.src = `./images/${isLiked ? "" : "non"}like.svg`; } // 绑定收藏按钮点击事件(事件委托,仅绑定一次) productInfoContainer.addEventListener("click", (e) => { const clicked = e.target.closest(".fav"); if (!clicked) return; const productId = clicked.id; toggleLikedProduct(productId); toggleLoveBtn(productId); }); // 切换显示商品 function showCurrentProduct(e) { const clickedEl = e.target.closest("#product"); if (!clickedEl) return; const currentId = clickedEl.dataset.id; localStorage.setItem("currentId", JSON.stringify(currentId)); renderCurrentProduct(currentId); } productBar.addEventListener("click", showCurrentProduct); // 页面加载初始化 function onLoad() { initLikedProducts(); renderProductBar(); const savedId = localStorage.getItem("currentId"); const initialId = savedId ? JSON.parse(savedId) : "01"; renderCurrentProduct(initialId); } onLoad();
修改说明
initLikedProducts函数:负责从localStorage读取收藏数据,同步更新products数组的isLiked状态,确保初始状态正确。- 事件委托优化:直接在容器上绑定一次点击事件,通过元素ID定位当前商品,彻底解决闭包导致的变量绑定错误。
- 状态强同步:渲染商品时通过
likedProduct数组判断收藏状态,确保按钮图标与实际收藏记录一致。 - 引用问题修复:添加收藏时存储商品对象的副本,避免后续修改原始数组影响
likedProduct的准确性。
内容的提问来源于stack exchange,提问作者tertcoder
相关产品推荐
相关产品推荐

