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

原生JavaScript支出追踪器删除非序列项时总金额计算异常问题求助

问题根源分析

你的问题核心出在**totalAmount变量的作用域**和删除操作的DOM逻辑上:

  1. 每次点击添加按钮时,你都在回调函数里声明了局部的totalAmount变量——每个删除按钮的回调都会捕获这个变量,但不同支出项对应的totalAmount是各自添加时的旧值,不是页面上实时更新的总金额。比如先加项1,totalAmount是项1的金额;再加项2,新的totalAmount是项1+项2;这时候删除项1的按钮用的是第一个totalAmount(项1的金额),减去后会变成0,但实际总金额应该是项2的金额,自然会计算错误。

  2. 另外,你在删除时先把expenseAmount元素从DOM里移除,再去读取它的innerHTML——虽然有时候还能读到值,但这不是可靠的做法,元素被移除后它的属性在某些场景下可能无法正常访问。

修复方案

我们做两个关键调整就能解决问题:

  • 每次计算总金额时,直接从total元素读取当前最新值,彻底避免依赖局部变量的闭包陷阱。
  • 在创建删除按钮时,先把当前支出项的金额保存为独立变量,供删除回调使用,不再依赖DOM元素。

下面是修改后的完整代码:

<!DOCTYPE html> 
<html lang="en"> 
<head> 
    <meta charset="UTF-8"> 
    <meta http-equiv="X-UA-Compatible" content="IE=edge"> 
    <meta name="viewport" content="width=device-width, initial-scale=1.0"> 
    <link rel="stylesheet" href="ExpenseTracker.css"> 
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css"> 
    <script src="https://cdn.jsdelivr.net/npm/flatpickr"></script> 
    <title>Expense Tracker</title> 
</head> 
<body> 
    <div class="container"> 
        <label name="expense">Expense: </label> 
        <input id="inputField" name="expense" type="text"> 
        <label name="date">Date: </label> 
        <input id="start" type="text" name="date"> 
        <label name="amount">Amount: </label> 
        <input id="money" name="amount" type="number" min="0" step="0.1"> 
        <button id="add" >Add</button> 
        <table> 
            <thead> 
                <tr style="border: 1px solid black;"> 
                    <th>Description</th> 
                    <th>Date</th> 
                    <th>Amount</th> 
                </tr> 
            </thead> 
            <tbody id="listContainer" style="border: 1px solid black;"> 
            </tbody> 
            <tr> 
                <td id="total">0</td> 
            </tr> 
        </table> 
        <button onclick="clearHistory()">clear localStorage</button> 
    </div> 
    <script> 
        class ExpenseObject{ 
            constructor(e, d, a){ 
                this.expenseDescription = e; 
                this.dateObject = d; 
                this.amount = a; 
            } 
        } 

        function clearHistory(){ 
            localStorage.clear(); 
            // 清空列表同时重置总金额
            document.getElementById("listContainer").innerHTML = "";
            document.getElementById("total").innerText = "0";
        } 

        const createDate = flatpickr("#start",{ 
            dateFormat:"d-m-Y ", 
        }); 

        let addButton = document.getElementById("add"); 
        let listContainer=document.getElementById("listContainer"); 
        let inputField= document.getElementById("inputField"); 
        let dateInput = document.getElementById("start"); 
        let amountField = document.getElementById("money"); 
        let total = document.getElementById("total"); 

        addButton.addEventListener('click', function(){ 
            if(!inputField.value || !dateInput.value || !amountField.value){ 
                alert("please do not leave blank in any field"); 
                return; 
            } 

            // 提前保存当前支出的数值,避免后续依赖DOM
            const currentAmount = parseFloat(amountField.value);

            var newRow = document.createElement('tr'); 
            var expense = document.createElement('td'); 
            var expenseDate = document.createElement('td'); 
            var expenseAmount = document.createElement('td'); 
            var deleteButton = document.createElement('button'); 

            deleteButton.innerHTML="X"; 
            let expenseStuff = new ExpenseObject (inputField.value,dateInput.value,amountField.value ) 

            expense.innerHTML = expenseStuff.expenseDescription; 
            expenseDate.innerHTML = expenseStuff.dateObject; 
            expenseAmount.innerText = expenseStuff.amount; 

            listContainer.appendChild(newRow); 
            newRow.appendChild(expense); 
            newRow.appendChild(expenseDate); 
            newRow.appendChild(expenseAmount); 
            newRow.appendChild(deleteButton); 

            // 清空输入框
            inputField.value = ""; 
            amountField.value=""; 

            // 更新总金额:读取当前值累加,保留两位小数避免浮点精度问题
            let currentTotal = parseFloat(total.innerText) || 0;
            currentTotal += currentAmount;
            total.innerHTML = currentTotal.toFixed(2);

            deleteButton.addEventListener('click', function(){ 
                // 直接使用提前保存的金额,计算新的总金额
                let updatedTotal = parseFloat(total.innerText) || 0;
                updatedTotal -= currentAmount;
                // 确保总金额不会出现负数
                updatedTotal = Math.max(updatedTotal, 0);
                total.innerHTML = updatedTotal.toFixed(2);

                // 直接删除整行,代码更简洁
                newRow.remove();
            }) 
        }) 
    </script> 
</body> 
</html>
额外优化说明
  • 初始化total的文本为0,避免第一次添加时读取到"total"导致的NaN问题,逻辑更直观。
  • 使用toFixed(2)格式化总金额,解决JS浮点运算的精度误差(比如0.1+0.2=0.30000000000000004这类情况)。
  • 删除时直接移除整个行元素,不需要逐个删除子节点,代码更简洁高效。
  • 清空localStorage时同时清空支出列表和总金额,功能逻辑更完整。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 07:24:06