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

Vanilla JavaScript商品收藏功能异常问题求助

商品收藏功能问题修复:切换商品后收藏按钮异常及收藏数组错误

问题现象

当前商品的收藏按钮可正常工作,但切换到下一个商品后,需点击两次才能完成收藏,且该操作会导致前一个已收藏商品取消收藏并从likedProduct数组中移除。预期所有商品的收藏按钮都能正常工作,likedProduct数组正确维护收藏商品。

问题根源

  1. 闭包捕获变量错误:原代码中addLikedProduct仅在页面加载时绑定一次点击事件,事件处理函数捕获的current是初始加载的商品对象,切换商品后点击收藏按钮,修改的仍是旧商品的isLiked状态,而非当前显示的商品。
  2. 未同步本地存储的收藏状态:页面初始化时未从localStorage读取已收藏数据,直接从原始products数组过滤likedProduct,导致商品的isLiked状态与实际收藏记录不一致;切换商品时也未根据收藏数组更新当前商品的状态。
  3. 状态与视图不同步:切换商品后,收藏按钮的图标未根据当前商品的实际收藏状态更新,导致首次点击需要先修正显示状态,出现“点击两次才生效”的问题。

修复方案及代码修改

关键修改步骤

  1. 初始化时同步本地存储数据:页面加载时读取localStorage中的收藏记录,同步更新products数组的isLiked状态,确保初始状态正确。
  2. 事件委托优化:移除单独的addLikedProduct函数,直接在productInfoContainer上绑定一次点击事件,通过元素ID定位当前商品,避免闭包陷阱。
  3. 状态与视图强同步:每次渲染商品信息时,根据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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 07:20:55