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

JavaScript新手疑问:DOM元素访问不存在的completed属性原理

为什么DOM元素能访问completed属性?

核心原因很简单:在JavaScript中,所有DOM元素(比如你代码里的<li>)本质都是JavaScript对象。你可以像给普通JS对象添加自定义属性一样,直接给DOM元素新增属性,这就是clickedEl.completed能被访问的原因。

具体发生的过程:

  1. 第一次点击<li>时,clickedEl.completed这个属性还不存在,所以条件clickedEl.completed === true不成立,进入else分支:
    • 给该<li>添加删除线样式
    • 手动给这个DOM对象新增一个completed属性,并赋值为true
  2. 第二次点击同一个<li>时,clickedEl.completed已经存在且值为true,所以进入if分支:
    • 移除删除线样式
    • 这里你代码漏了一步:应该把clickedEl.completed设回false,否则下次点击逻辑会出错

你的代码存在的关键问题

现在你的todosArray(存在localStorage里)和DOM元素的completed属性是完全脱节的:

  • 点击<li>标记完成时,只修改了DOM元素的属性,没有更新todosArray里对应todoObj的completed值,刷新页面后,之前标记的完成状态会丢失
  • 从localStorage读取todo渲染到页面时,没有把todoObj.completed的状态同步到DOM元素(比如如果原数据是已完成,页面上的<li>应该直接显示删除线,并且给DOM元素加上completed属性)

修正方案(关联DOM和数据)

给每个<li>添加一个data-id属性,关联todosArray里对应对象的索引,这样点击时可以找到对应的数据,同步更新DOM和数组:

// 新增:给todoObj添加id(用数组索引当id即可)
function addTodo(item) {
    const todoObj = {
        id: todosArray.length, // 用数组长度作为唯一id
        todo: item,
        completed: false
    };
    todosArray.push(todoObj);
    localStorage.setItem('todosArray', JSON.stringify(todosArray));
    formInput.value = '';
}

// 提交表单时,给li加上data-id
form.addEventListener('submit', function (e) {
    e.preventDefault();
    if (formInput.value === '') {
        alert('Please add a task.');
    } else {
        const newLi = document.createElement('li');
        newLi.innerHTML = formInput.value;
        // 给li添加data-id,关联刚添加的todoObj的id
        newLi.dataset.id = todosArray.length;
        ul.append(newLi);
        const removeBtn = document.createElement('button');
        newLi.append(removeBtn);
        removeBtn.innerHTML = 'Remove';
        addTodo(formInput.value);
    }
});

// 点击li时,同步更新DOM和数据
ul.addEventListener('click', function (e) {
    if (e.target.textContent === 'Remove') {
        const li = e.target.parentElement;
        // 删除对应的数组元素
        const todoId = parseInt(li.dataset.id);
        todosArray.splice(todoId, 1);
        // 重新保存到localStorage
        localStorage.setItem('todosArray', JSON.stringify(todosArray));
        li.remove();
    } else if (e.target.tagName === 'LI') {
        const clickedEl = e.target;
        const todoId = parseInt(clickedEl.dataset.id);
        const todoObj = todosArray[todoId];
        
        if (todoObj.completed) {
            clickedEl.style.textDecoration = 'none';
            todoObj.completed = false;
            clickedEl.completed = false; // 同步DOM属性
        } else {
            clickedEl.style.textDecoration = 'line-through';
            todoObj.completed = true;
            clickedEl.completed = true; // 同步DOM属性
        }
        // 更新localStorage
        localStorage.setItem('todosArray', JSON.stringify(todosArray));
    }
});

// 从localStorage读取时,同步DOM状态
if (localStorage.getItem('todosArray')) {
    todosArray = JSON.parse(localStorage.getItem('todosArray'));
    for (let i = 0; i < todosArray.length; i++) {
        const newLi = document.createElement('li');
        newLi.textContent = todosArray[i].todo;
        newLi.dataset.id = i;
        // 如果原数据是已完成,同步样式和DOM属性
        if (todosArray[i].completed) {
            newLi.style.textDecoration = 'line-through';
            newLi.completed = true;
        }
        ul.append(newLi);
        const removeBtn = document.createElement('button');
        newLi.append(removeBtn);
        removeBtn.textContent = 'Remove';
    }
}

这样修改后,DOM元素的状态和todosArray的数据就完全同步了,刷新页面也不会丢失完成状态。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 01:18:24