ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

原生JavaScript实现TodoMVC:夯实前端基础的关键实践

原生JavaScript实现TodoMVC:夯实前端基础的关键实践 1. 为什么选择原生JavaScript实现TodoMVC在框架盛行的今天坚持用原生JavaScript实现TodoMVC看似复古实则是一次绝佳的基础能力训练。我见过太多开发者能熟练使用Vue/React的API却对事件委托、DOM操作优化这些底层原理一知半解。通过这个项目你将掌握纯手工管理DOM节点生命周期无虚拟DOM情况下的高效渲染策略事件系统的精确控制能力模块化组织代码的原始方法提示完成原生版本后再对比Vue实现你会对现代框架解决的问题有更深刻的理解。我在团队面试中常让候选人做这个练习能清晰看出其基础功底。2. 项目结构与核心模块设计2.1 基础HTML架构采用经典TodoMVC的HTML结构但去除所有框架痕迹。关键点在于section classtodoapp header classheader h1todos/h1 input classnew-todo autofocus /header section classmain ul classtodo-list/ul /section footer classfooter/footer /section特别注意避免使用id选择器保持CSS作用域纯净为动态元素预留空的容器如todo-list使用语义化class命名不要用js-前缀2.2 状态管理方案虽然可以用Redux模式但为保持原生纯粹性我推荐更轻量的发布订阅模式const store (() { let todos [] const subscribers [] return { getTodos: () [...todos], addTodo: (text) { todos.push({ id: Date.now(), text, completed: false }) subscribers.forEach(fn fn()) }, subscribe: (fn) { subscribers.push(fn) return () subscribers.filter(sub sub ! fn) } } })()这种模式的优势在于避免直接暴露状态对象提供可控的修改接口实现响应式更新的基础机制3. 核心功能实现详解3.1 新增待办事项在header的input上监听keyup事件document.querySelector(.new-todo).addEventListener(keyup, (e) { if (e.key Enter e.target.value.trim()) { store.addTodo(e.target.value.trim()) e.target.value } })常见陷阱未处理连续空格输入需trim()忘记清空输入框没考虑键盘兼容性应同时监听keyCode3.2 列表渲染优化采用文档片段(documentFragment)批量操作DOMconst renderTodos () { const fragment document.createDocumentFragment() store.getTodos().forEach(todo { const li document.createElement(li) li.innerHTML div classview input classtoggle typecheckbox label${escapeHtml(todo.text)}/label button classdestroy/button /div fragment.appendChild(li) }) const list document.querySelector(.todo-list) list.innerHTML list.appendChild(fragment) }重要必须实现escapeHtml防止XSS攻击这是原生开发容易忽略的安全隐患。3.3 状态切换与删除使用事件委托提高性能document.querySelector(.todo-list).addEventListener(click, (e) { const target e.target const li target.closest(li) const id parseInt(li.dataset.id) if (target.matches(.toggle)) { store.toggleTodo(id) } else if (target.matches(.destroy)) { store.removeTodo(id) } })委托要点只在父元素绑定一个监听器用matches()精确判断事件源通过closest()找到业务元素使用data-*属性传递业务ID4. Vue对比版本实现差异4.1 模板语法对比Vue版本可以简化为template li v-fortodo in filteredTodos :keytodo.id div classview input classtoggle typecheckbox v-modeltodo.completed label{{ todo.text }}/label button classdestroy clickremoveTodo(todo.id)/button /div /li /template框架优势立即显现自动XSS防护双向绑定简化状态同步声明式渲染无需手动DOM操作4.2 状态管理对比Vuex版本的核心逻辑const store new Vuex.Store({ state: { todos: [] }, mutations: { addTodo(state, text) { state.todos.push({ id: Date.now(), text, completed: false }) } } })与原生实现的本质区别响应式系统自动处理依赖追踪不可变状态管理完善的调试工具链4.3 性能优化差异原生需要手动实现的优化点在Vue中通过虚拟DOM的diff算法异步更新队列组件级更新策略但原生版本在简单场景可能有更高性能因为无虚拟DOM开销精确控制DOM操作范围更少的内存占用5. 项目进阶与调试技巧5.1 本地存储持久化实现localStorage同步const STORAGE_KEY todos-vanillajs const todoStorage { fetch() { const todos JSON.parse(localStorage.getItem(STORAGE_KEY) || []) todos.forEach((todo, index) { todo.id index }) return todos }, save(todos) { localStorage.setItem(STORAGE_KEY, JSON.stringify(todos)) } }注意事项需要处理JSON序列化异常考虑存储空间限制约5MB敏感数据需加密存储5.2 性能监控方案添加性能标记function update() { performance.mark(renderStart) renderTodos() performance.mark(renderEnd) performance.measure(render, renderStart, renderEnd) }分析指标建议DOMContentLoaded时间平均渲染耗时内存占用变化5.3 测试策略设计基础测试方案describe(TodoList, () { beforeEach(() { document.body.innerHTML __html__[index.html] init() }) it(should add new todo, () { const input document.querySelector(.new-todo) input.value Test todo input.dispatchEvent(new KeyboardEvent(keyup, { key: Enter })) expect(document.querySelectorAll(.todo-list li).length).toBe(1) }) })测试重点用户交互模拟DOM状态断言边缘情况覆盖6. 从项目中学到的经验经过完整实现后我总结出几点关键认知框架的价值现代框架解决的不仅是语法糖问题更是提供了可维护的工程化方案。当我在原生版本中手动实现shouldComponentUpdate等效逻辑时深刻体会到React的优化价值。基础的重量能徒手实现完整TodoMVC的开发者在排查框架问题时往往更快定位到根本原因。就像懂汇编的程序员对内存管理有更深理解。技术选型平衡对于简单页面原生实现可能更轻量但复杂应用必须用框架。我曾维护过一个2万行代码的原生项目最终重构为Vue节省了40%代码量。建议每个前端开发者都至少完成一次这样的练习你会真正理解虚拟DOM的价值掌握浏览器原生API的细节在框架使用中做出更明智的决策建立完整的前端知识体系
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表