ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

JavaScript快速入门:掌握变量、函数、DOM与异步,2小时写出交互页面

JavaScript快速入门:掌握变量、函数、DOM与异步,2小时写出交互页面 很多前端新手都是这样“学废”的网上收藏了十几个JavaScript教程跟着敲了几页代码关掉视频面对一个空白的编辑器仍然不知道第一行该写什么。问题不全在你而在教程本身——大多数教程只告诉你语法是什么却没说清楚JavaScript在真实页面里到底扮演什么角色。你背了一堆知识点但代码一跑就报错报错信息也看不懂最后只能发出“JS怎么这么难”的感叹。这篇文章想给你一条最短路径2小时内把JavaScript最核心、最常用的部分串起来。我先给一个明确判断——JavaScript快速入门的重点不是背完所有语法而是掌握三件事变量与类型、函数与作用域、DOM操作与异步请求。这三件事足以支撑你写出一个能交互、能请求数据、能处理用户操作的页面。读完本文你能独立写一个带交互逻辑的待办清单能用fetch请求后端接口能大致读懂多数前端项目里JS部分的源码并且遇到报错时知道第一步去哪里排查。资料不追求全但每一段都保证你能直接用到项目里。1. 这篇文章真正要解决的问题先说个残酷的现实很多人学JavaScript学了几个月仍然只会复制粘贴别人代码换一个场景就不会改。为什么因为大多数学习路径是反的一上来就啃ES6规范、原型链、闭包、this指向这些进阶概念结果基础语法还没用熟反而被复杂概念劝退。JavaScript真正需要你理解的核心不是那些冷门技巧而是它在浏览器里怎么工作。你在页面上点击按钮、输入表单、滚动列表背后都是JavaScript在响应。它干的事情其实很朴素选中页面上的某个元素修改它的内容或样式监听用户的操作做出相应反应向服务器请求数据再把数据显示到页面上。这三类事情覆盖了前端开发百分之八十的日常工作。所以文章的受众非常明确第一类是刚接触前端、想快速建立JavaScript整体认知的新手第二类是学过基础但还不清楚“语法怎么落地成页面”的初学者第三类是准备面试、需要快速梳理基础脉络的同学。如果你已经能用Vue或React写完整项目本文内容偏基础可以选择性跳过。我建议你给自己的目标设一个边界2小时快速入门不等于2小时精通。入门意味着你能够用JavaScript完成基础的页面交互遇到报错不慌知道自己缺什么知识、该去哪里查。真正精通JavaScript需要你在大量实战中积累这是后面的事情。2. JavaScript基础概念先弄清楚这门语言在做什么2.1 JavaScript是什么它和HTML、CSS的分工JavaScript通常被称为“前端三剑客”之一。HTML负责页面结构CSS负责样式外观JavaScript负责行为交互。你可以把页面想象成一间房子HTML是毛坯房的墙体框架CSS是刷墙铺砖、家具摆放JavaScript则是水电布线、开关控制。没有JavaScript的网页所有内容只是静态摆在那里点什么都不动。JavaScript最初就是为浏览器设计的脚本语言所以你在浏览器里能看到的动态效果、弹窗、数据刷新几乎都是它的功劳。后来Node.js让它跑到了服务器端但那不是入门阶段的重点本文所有示例都基于浏览器环境。2.2 JavaScript的几个重要特性JavaScript有四个特性决定了你写代码时思考方式也解释了为什么它有一些让人困惑的“坑”。了解这些至少能在入门阶段少踩一半雷。第一动态类型。变量在声明时不需要指定类型同一个变量可以先存数字后存字符串代码不会报错。这个特性灵活但也容易埋下隐患。let data 100; data hello; // 不会报错类型自动变成了字符串 console.log(data);第二单线程。JavaScript在浏览器里同时只能执行一段代码。你可能听说过Web Workers可以实现多线程但那是特殊场景。平时写的代码是逐行执行的所以才需要后面讲到的异步机制。第三事件驱动。程序不是一口气跑完就结束而是挂在页面上等着用户操作。用户点击按钮触发点击事件用户按下键盘触发键盘事件页面的资源加载触发加载事件。你写的很多代码其实都是“当某事件发生时应该执行什么函数”。第四弱类型。运算符会尝试自动转换变量的类型。最常见的例子是字符串拼接字符串会把内容拼接起来把数字和字符串用加号连接时会得到字符串。这是JavaScript被吐槽最多的地方之一。console.log(1 2); // 12 console.log(1 - 2); // -1 console.log(1 1); // true console.log(1 1); // false小结论入门阶段不需要过度纠结这些特性的深层原理但要知道它们存在。尤其是“动态类型”和“弱类型”两个特性会在你写业务逻辑时频繁影响结果。3. 环境准备与前置条件JavaScript入门的环境准备可能比你想象得还要简单。不需要安装复杂的开发工具第一步只需要两样东西一个现代浏览器Chrome、Edge、Firefox都可以和一个代码编辑器推荐VS Code。3.1 打开浏览器控制台按键盘上的F12或者右键点击页面选择“检查”在开发者工具里找到Console控制台面板。这就是你学习JavaScript最好的练习场。你在控制台里写的每一行代码回车之后立即执行不需要保存文件不需要重启服务。很多初学者忽略了这个工具实际上它不仅能用来验证语法写错了还会直接给出红色报错信息这是你排查问题的第一个入口。3.2 在HTML中引入JavaScript的三种方式实际开发中JavaScript代码是要和HTML配合使用的。引入方式有三种对应的写法如下第一种行内事件button onclickalert(hello)点击/button第二种script标签内嵌!DOCTYPE html html head meta charsetUTF-8 title示例/title /head body script console.log(hello js); /script /body /html第三种外部JS文件这也是实际项目中最多用的方式!-- index.html -- script src./app.js/script// app.js console.log(hello from external file);这里有一个容易踩的坑script标签放在body底部和放在head里是有区别的。如果放在head中浏览器会先加载JS文件再渲染页面此时脚本里去操作页面元素很可能因为元素还没渲染出来而找不到。所以初学阶段建议把script标签放到body结束标签之前。后面学到defer属性和DOM加载时机你会发现有更优雅的解法但入门阶段先记住“放底部更稳”。3.3 版本说明JavaScript语言本身没有安装配置这一说你需要在浏览器里运行它所以不存在版本选择问题。在编写代码时可以使用较新的ES6语法这是目前所有主流浏览器都支持的。需要注意的是不同浏览器对个别新特性的支持程度可能不同但常见的语法let、const、箭头函数、Promise完全没有兼容性问题。本文示例在Chrome和Edge中可以直接运行Node.js环境也支持。4. 变量与数据类型入门阶段必须一次搞清的知识如果用一句话概括变量它就是一个用来存数据的容器。你在页面里需要记录用户输入的内容、请求回来的数据、计算的结果都需要先把它们存在变量里。4.1 声明变量的三种方式var、let、constES6之前JavaScript只有var一种声明方式现在推荐使用let和const。它们的核心区别看这张表就够了声明方式作用域能否重复声明能否重新赋值使用建议var函数作用域可以可以旧代码中会遇到新代码不建议使用let块级作用域不可以可以需要改变值的场景const块级作用域不可以不可以默认优先使用看一个实际区别if (true) { var a 1; let b 2; const c 3; } console.log(a); // 1var不关心块级外面能访问 // console.log(b); // 报错b is not defined // console.log(c); // 报错c is not defined而const的“不能改”指的是不能重新赋值。如果存的是对象对象内部的属性仍然可以修改因为引用关系没有变const user { name: 张三 }; user.name 李四; // 可以修改的是内容 // user { name: 王五 }; // 报错不能重新赋值4.2 数据类型入门只需要掌握这些JavaScript基础数据类型分成两类原始类型和引用类型。入门阶段需要掌握以下7种类型说明示例number数字包括整数和浮点数42、3.14string字符串hello、world、模板字符串boolean布尔值true、falseundefined声明了变量但没赋值let x;null空值表示“没有”let obj null;symbol唯一值主要用于对象属性较少用到object对象、数组、函数等都算{}、[]最容易让新手困惑的是null和undefined的区别。简单理解undefined是“声明了变量但是没有给它值”是系统给的默认状态null是你主动表示“这个位置我要放一个空值”需要你手动赋值。面试里经常问但工作里其实不需要过度纠结判定空值时要清楚两者的差异就行。用typeof可以检测一个变量的类型console.log(typeof hello); // string console.log(typeof 42); // number console.log(typeof true); // boolean console.log(typeof undefined); // undefined console.log(typeof null); // object 历史上遗留的bug但一直保留下来 console.log(typeof {}); // object console.log(typeof []); // object 数组也是object小结论声明变量时优先用const确实需要修改再换成let。这个习惯能从根源上减少很多“变量被意外改动”的问题。5. 运算符与分支循环代码怎么“做决定”变量只能解决存储问题运算符和控制语句负责让代码“活”起来。JS的基础逻辑和大多数编程语言相似但这部分有两个容易出错的点值得单独说。5.1 比较运算符什么时候用什么时候用会先做类型转换再比较则要求类型和值都相等几乎所有人都建议你使用。原因很简单会做隐式转换导致“1 1”成立这在业务代码里会带来很难排查的bug。如果你判断一个输入框里的值和某个数字是否相等输入框的值是字符串直接比较永远相等结果就错了。5.2 逻辑运算符的短路特性逻辑与和逻辑或||都有短路特性。只要左边为假右边就不会执行||只要左边为真右边就不会执行。这个特性在实际开发中常用来简写代码// 如果用户存在才打印名字 user console.log(user.name); // 给参数设置默认值 const name inputName || 默认用户;ES2020还引入了空值合并运算符??它和||的区别是??只在左边为null或undefined时取右边而||在左边为false、0、空字符串时也会取右边。这两者的区别在实际项目中很容易踩坑const count 0; console.log(count || 100); // 100因为0被当成false console.log(count ?? 100); // 0只有null/undefined才走默认值5.3 分支与循环if...else是开发中最常用的逻辑控制方式这里不做展开。switch在判断多个固定值时比一大串if更清晰但使用频率不高。循环方面for和while仍然要掌握但真实项目中你更多会对**数组使用map、filter、forEach**而不是写一个传统for循环。这一点会在数组部分详细展开。小结论入门阶段记住“比较一律用”“给默认值想一想??和||的区别”就能避开JS里最经典的几个逻辑坑。6. 函数、作用域与箭头函数函数的作用远不止“把代码包起来”。说得直接一点函数就是你来定义的一套“输入到输出”的处理流程。传入数据经过函数内部的逻辑返回一个结果。页面上的每个按钮点击、每次事件触发背后几乎都藏着一个函数。6.1 函数声明与函数表达式// 函数声明 function add(a, b) { return a b; } // 函数表达式把函数赋值给变量 const add2 function(a, b) { return a b; };两者的一个区别是“提升”机制但日常使用中最推荐的其实不是它们俩而是箭头函数。6.2 箭头函数箭头函数是ES6引入的写法语法更简洁。它的写法细节很多但入门阶段你只需要把它当作“和普通函数功能类似的更简写形式”// 普通写法 const greet function(name) { return 你好 name; }; // 箭头函数写法 const greet2 (name) { return 你好 name; }; // 只有一行return时可以省略大括号和return const greet3 (name) 你好 name;一个重要提醒箭头函数不只是简写它的this绑定规则和普通函数完全不同。但this是JavaScript里著名的“大坑”不适合入门阶段深挖。你只要知道“箭头函数没有自己的this它会沿用外层作用域的this”这个结论即可等项目里真的踩到再回头细看这一块的知识。关于箭头函数和普通函数的this区别建议收藏单独研究一遍。6.3 参数默认值与rest参数function createUser(name 匿名, age 18) { return { name, age }; } function sum(...nums) { return nums.reduce((total, n) total n, 0); }6.4 回调函数把函数当作参数传进另一个函数JavaScript里函数可以当作参数传给另一个函数被传递的函数称为回调函数。这不是一个难以理解的概念你只需要知道function processData(data, callback) { const result data * 2; callback(result); } processData(10, function(value) { console.log(处理结果 value); }); // 处理结果20小结论函数是JavaScript最核心的组成部分强烈建议你在入门阶段就把箭头函数和回调函数练熟。很多新人卡在下阶段的DOM操作其实不是DOM难而是函数的基本功不牢导致看到回调就懵。7. 对象、数组与常用API在实际项目中你从后端拿到的数据绝大多数都是对象数组的结构。比如用户列表是一个数组数组里每个元素又是一个对象。所以对象和数组是操作数据的基础这一节内容非常实用。7.1 对象用键值对描述一个事物对象就是把相关数据打包在一起的容器。一个用户有姓名、年龄、邮箱用对象来表达非常自然const user { name: 张三, age: 25, email: zhangsanexample.com, isVip: true, sayHello: function() { console.log(我是 this.name); } }; // 读取属性有两种方式 console.log(user.name); // 点语法 console.log(user[age]); // 方括号语法 // 修改属性 user.age 26; // 解构赋值从对象中取出属性 const { name, age } user;对象中的函数称为方法。this在方法里指向当前对象但这块内容建议先记结论后面深入学习时再理解为什么这样设计。7.2 数组map、filter、find、reduce数组是一组有序数据的集合。以下是日常开发中高频使用的API几乎每个前端项目都会遇到const users [ { id: 1, name: 张三, age: 20, score: 88 }, { id: 2, name: 李四, age: 24, score: 92 }, { id: 3, name: 王五, age: 22, score: 76 } ]; // forEach遍历每一项 users.forEach(user console.log(user.name)); // map把数组每一项转换成另一种形式返回新数组 const names users.map(user user.name); // [张三, 李四, 王五] // filter筛选符合条件的项返回新数组 const adultUsers users.filter(user user.age 22); // find找到符合条件的第一个项返回对象本身 const zhangsan users.find(user user.name 张三); // some / every判断是否存在 / 是否全部符合 const hasHighScore users.some(user user.score 90); const allPass users.every(user user.score 60); // reduce累加计算 const totalScore users.reduce((total, user) total user.score, 0);强烈建议用map和filter而不是for循环去做数据转换因为代码更短、意图更清晰、也不容易出错。你把这个习惯建立起来之后看框架源码时会轻松很多因为Vue和React的源码里到处都是这些数组方法的影子。7.3 展开运算符与JSON序列化展开运算符...常用来合并数组、复制对象const arr1 [1, 2, 3]; const arr2 [4, 5, 6]; const merged [...arr1, ...arr2]; const obj1 { a: 1, b: 2 }; const obj2 { ...obj1, c: 3 };前后端数据交互时还需要把对象转成JSON字符串或者解析JSON字符串const json JSON.stringify({ name: 张三, age: 20 }); const obj JSON.parse(json);性能提示频繁调用JSON.stringify处理大量数据时确实会带来性能开销所以在循环里应避免重复序列化同一份数据。先把数据处理好再序列化而不是循环内反复转换。小结论对象和数组的方法必须练到“想都不用想就能写出来”。最快的练习方法不是背文档而是把用户列表的增删改查用console全部跑一遍。8. DOM操作与事件机制到这里你的JavaScript才开始真正“碰页面”。DOMDocument Object Model是浏览器把HTML页面结构变成的一棵对象树JavaScript可以通过DOM接口来选中、修改、删除页面上的元素。很多新手觉得DOM操作复杂其实核心就是两件事选中元素、改它。8.1 选中元素最常见的四个方法是// 通过CSS选择器选中第一个匹配元素 const title document.querySelector(.title); const button document.querySelector(#submitBtn); // 通过CSS选择器选中所有匹配元素返回一个类数组 const items document.querySelectorAll(.item); // 按id选中老写法但仍然常见 const info document.getElementById(info);querySelector是现代前端开发中使用频率最高的一种Vue和React源码里也大量使用。你需要熟悉CSS选择器语法比如.class、#id、标签名。8.2 修改内容和样式const title document.querySelector(.title); // 修改文本内容 title.textContent 新的标题; // 修改HTML内容慎重有XSS风险 // title.innerHTML span新的内容/span; // 修改样式 title.style.color red; title.style.fontSize 20px; // 切换类名 title.classList.add(active); title.classList.remove(active); title.classList.toggle(active); // 有则删无则加8.3 事件监听浏览器里最常见的事情是“用户操作页面触发事件”。给按钮绑定点击事件写法如下const button document.querySelector(#submitBtn); button.addEventListener(click, function(event) { console.log(按钮被点击了); console.log(event); // 事件对象 });event对象包含了这次事件的相关信息比如点击的位置、触发事件的元素、键盘按下的键等遇到具体需求时再查即可。这里有一个新手经常碰到的困惑为什么给多个按钮绑定事件时只用document.querySelector选中的永远是第一个因为querySelector只返回第一个匹配元素。要给所有匹配元素绑定事件需要遍历const buttons document.querySelectorAll(.btn); buttons.forEach(button { button.addEventListener(click, function() { console.log(clicked); }); });8.4 关于javascript:void(0)的一点说明你可能在很多人写的HTML示例里见过a hrefjavascript:void(0)这种写法。它的作用是让a标签点击后不跳转页面同时又能被当成一个“点击控件”来使用。void(0)在JavaScript里的含义是“执行表达式但返回undefined”所以浏览器就不再跳转。这是一个有历史包袱的写法新项目里更推荐用button元素加事件监听来替代因为语义更清晰也更符合可访问性要求。如果你在老项目里看到它知道它的作用就足够。8.5 事件代理如果你有很多子元素都要监听相同事件给每个元素单独绑定不仅代码重复还有性能问题。事件冒泡原理允许你只在父元素上绑定一次事件再判断事件目标const list document.querySelector(.list); list.addEventListener(click, function(event) { const target event.target; if (target.classList.contains(item)) { console.log(点击了列表项, target.textContent); } });小结论DOM操作的套路很固定不必死记所有API。你只需要掌握querySelector选中元素、textContent和style修改内容、addEventListener绑定事件就已经能完成绝大多数交互需求了。9. 异步JavaScript从setTimeout到fetch新手最容易卡住的地方就是异步。明明把接口返回的数据console.log了控制台打出来的却是Promise或者数据没显示就报错了。要理解异步先要接受一个现实浏览器里有些操作要花时间比如请求服务器、读取文件、等待定时器执行JavaScript不会停下来傻等而是先继续执行后面的代码等耗时操作完成后再回来执行回调。9.1 setTimeout与setIntervalsetTimeout(() { console.log(1秒后执行); }, 1000); console.log(立即执行); // 输出顺序立即执行 - 1秒后执行这个结果可能颠覆新手的直觉但它正好解释了JavaScript的执行模型。9.2 Promise把异步操作变成链条Promise用来表示一个“将来才会有结果”的操作。它有resolve成功和reject失败两个状态用then和catch来接收结果function getData() { return new Promise((resolve, reject) { setTimeout(() { const success true; if (success) { resolve({ code: 200, data: [1, 2, 3] }); } else { reject(new Error(请求失败)); } }, 1000); }); } getData() .then(data { console.log(data.data); }) .catch(error { console.error(error); });传统写法中用回调函数嵌套回调很容易写成“回调地狱”。Promise把这种嵌套改成了链式调用可读性提升非常明显。9.3 async/await异步代码的终极形态async/await是ES2017引入的语法它让异步代码看起来就像同步代码一样。函数前加async表示这是异步函数函数内部用await等待一个Promise的结果async function main() { try { const data await getData(); console.log(data.data); } catch (error) { console.error(error); } } main();实际项目中绝大多数接口请求都会用async/await配合try...catch来写。它不仅代码整洁也方便排查问题。9.4 fetch API前端向后端请求数据fetch是浏览器原生提供的发请求方法返回一个Promise。一个最基本的GET请求如下async function fetchUsers() { try { const response await fetch(https://api.example.com/users); // 先判断HTTP状态 if (!response.ok) { throw new Error(请求失败状态码 response.status); } const data await response.json(); console.log(data); } catch (error) { console.error(请求出错, error); } }POST请求需要额外指定方法和请求体async function createUser() { try { const response await fetch(https://api.example.com/users, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ name: 张三, age: 20 }) }); const data await response.json(); console.log(data); } catch (error) { console.error(请求出错, error); } }这里有一个经典误区fetch只有在网络层面出错例如断网、跨域被拒绝时才会走进catch而HTTP状态码是404、500时它也不会报错。所以你必须自己检查response.ok这是新手最容易漏掉的地方。小结论异步是JavaScript的灵魂也是从“会写代码”到“能连后端写业务”的分水岭。入门阶段请死记一个流程先用fetch请求接口再用response.json()解析数据然后操作DOM把数据显示到页面。这个流程练熟前端基础就稳了。10. 完整实战示例手写一个可交互的待办清单理论知识讲得再清楚不如一个完整项目把知识串起来。下面这个待办清单应用覆盖了本文讲到的变量、函数、数组、DOM操作、事件监听和本地存储。请自己动手敲一遍而不是复制粘贴。10.1 项目文件结构todo/ ├── index.html ├── style.css └── app.js10.2 HTML结构!-- 文件路径todo/index.html -- !DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title待办清单/title link relstylesheet href./style.css /head body div classcontainer h1我的待办清单/h1 div classinput-area input typetext idtodoInput placeholder请输入待办事项 button idaddBtn添加/button /div ul idtodoList/ul /div script src./app.js/script /body /html10.3 CSS样式/* 文件路径todo/style.css */ * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: Arial, sans-serif; background: #f5f5f5; display: flex; justify-content: center; padding-top: 50px; } .container { width: 500px; background: #fff; padding: 24px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } h1 { font-size: 24px; margin-bottom: 16px; } .input-area { display: flex; gap: 8px; margin-bottom: 16px; } .input-area input { flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; } .input-area button { padding: 10px 16px; background: #40a9ff; color: #fff; border: none; border-radius: 4px; cursor: pointer; } .input-area button:hover { background: #1890ff; } #todoList { list-style: none; } #todoList li { display: flex; justify-content: space-between; align-items: center; padding: 10px; border-bottom: 1px solid #eee; } #todoList li.completed { text-decoration: line-through; color: #999; } #todoList li button { padding: 4px 8px; background: #ff4d4f; color: #fff; border: none; border-radius: 4px; cursor: pointer; }10.4 JavaScript逻辑// 文件路径todo/app.js // 1. 先获取页面上的元素 const todoInput document.querySelector(#todoInput); const addBtn document.querySelector(#addBtn); const todoList document.querySelector(#todoList); // 2. 定义一个数组用来存放待办事项 // 每个事项是 { id, text, completed } 这样的对象 let todos []; // 3. 渲染列表到页面上 function render() { // 清空列表避免重复渲染 todoList.innerHTML ; if (todos.length 0) { const emptyTip document.createElement(li); emptyTip.textContent 暂无待办事项添加一个吧; todoList.appendChild(emptyTip); return; } todos.forEach(todo { const li document.createElement(li); li.textContent todo.text; li.dataset.id todo.id; if (todo.completed) { li.classList.add(completed); } // 点击整个列表项切换完成状态 li.addEventListener(click, function() { toggleTodo(todo.id); }); // 为每一项添加一个删除按钮 const deleteBtn document.createElement(button); deleteBtn.textContent 删除; deleteBtn.addEventListener(click, function(event) { // 阻止点击事件继续冒泡避免同时触发li的点击 event.stopPropagation(); deleteTodo(todo.id); }); li.appendChild(deleteBtn); todoList.appendChild(li); }); } // 4. 添加待办事项 function addTodo() { const text todoInput.value.trim(); // 输入内容为空时提醒用户 if (!text) { alert(请输入待办内容); return; } todos.push({ id: Date.now(), text: text, completed: false }); todoInput.value ; render(); } // 5. 切换完成状态 function toggleTodo(id) { const todo todos.find(item item.id id); if (todo) { todo.completed !todo.completed; render(); } } // 6. 删除待办事项 function deleteTodo(id) { todos todos.filter(item item.id ! id); render(); } // 7. 绑定事件 addBtn.addEventListener(click, addTodo); // 支持按回车键添加 todoInput.addEventListener(keypress, function(event) { if (event.key Enter) { addTodo(); } }); // 8. 页面加载时渲染一次 render();10.5 代码逻辑拆解这个项目虽然小但结构是真实前端工程的缩影。第一步先获取DOM元素全部集中在文件顶部方便后续维护。第二步用一个todos数组管理所有数据页面上的UI完全由这个数组渲染出来这就是“数据驱动视图”的雏形Vue和React的核心思想也是这样。render函数负责把数组渲染成列表项每次增删改之后都调用它。如果不熟悉这种方法新手容易想到“新建一个li然后append”但每次新增都要手动操作DOM逻辑越写越乱。用数据驱动页面重绘是这个时代前端开发的基本思路。添加、切换、删除三个函数分别操作数组操作完成后再统一调render逻辑非常清晰。删除按钮里的event.stopPropagation()用来阻止事件冒泡这是实际开发中经常遇到的场景不阻止的话点击删除按钮会同时触发li的点击事件导致刚删完又被切换完成状态。11. 运行结果与效果验证现在验证一下项目是否正常。打开index.html预期效果如下页面顶部有一个输入框和一个“添加”按钮。输入“学习JavaScript”点击添加列表中出现这条待办事项。点击这条待办事项的文本区域文字出现删除线表示已完成。再次点击删除线消失表示未完成。点击这条事项旁边的“删除”按钮事项从列表中移除。不输入内容直接点击添加页面弹出提示“请输入待办内容”。输入内容后按回车键也能完成添加。如果某一项没有出现预期效果先按下键盘F12打开开发者工具查看Console面板。如果代码有语法错误、获取元素名称拼写错误、引用的函数不存在这里都会给出红色的报错信息。记住报错信息是JavaScript调试的第一线索不要忽略它。判断代码是否成功的另一个方法是在关键位置临时插入console.log来观察数据的变化例如在addTodo函数里添加一行console.log(todos)然后添加一条事项观察控制台打印出来的数组结构。确认完再删除调试代码。12. 常见问题与排查思路新手运行JavaScript代码时会遇到的报错其实高度集中。整理常见问题如下问题现象可能原因排查方式解决方案xxx is not defined变量或函数名拼写错误或在使用之前没有声明检查变量名大小写是否一致确认声明位置统一命名规范使用const或let声明后再使用Cannot read properties of null选择器没匹配到元素元素为null确认querySelector选择器语法正确确认script标签放在元素之后把script放到body底部或使用defer属性点击按钮没有反应事件没有绑定成功或JS文件加载失败Console里看是否有报错检查addEventListener是否写到了函数内部在事件回调里加console.log验证事件是否触发修改了数据但页面没有变化没有调用render方法重新渲染检查所有数据操作函数末尾是否有render()养成“数据变完就重绘”的习惯JSON.stringify报错循环引用数据结构中有循环引用打印数据结构检查是否对象里引用了自身深层拷贝或只序列化需要的字段fetch请求不返回数据跨域、接口地址错误、没有处理response.okConsole里看请求Network面板确认请求状态码用try...catch包裹请求逻辑异常时打印错误信息修改const声明的变量报错试图给const重新赋值看报错信息位置需要重新赋值时改为let声明再补充一个比较隐蔽但新手常犯的问题给多个元素绑定事件时querySelector只选了第一个结果后面的按钮点击都没反应。处理方式是改用querySelectorAll配合forEach遍历绑定。13. 最佳实践与工程建议写完能跑只是第一步让代码在真实项目里可维护才是更重要的能力。下面这些建议未必能在2小时内全部消化但建议你先收藏后面的项目里逐条实践。**命名要符合直觉。**变量名用名词比如userList、totalPrice函数名用动词比如getUsers、addTodo、renderList。好的命名让代码读起来像文章同事接手时不需要反复猜测作者意图。命名不要用拼音缩写、不要用无意义的a、b、tmp。**坚决不在全局作用域里堆代码。**把逻辑拆成函数让每个函数只负责一件事。addTodo只负责把数据推入数组render只负责渲染这样各自可以独立修改、独立测试。一个函数超过15行时就要思考是不是该拆分了。**多用const少用let不用var。**这个习惯能减少很多“变量被意外修改”的问题也能让别人更快理解你的意图。如果你不确定一个值将来会不会变先写成const等需要修改时再改成let。**处理用户输入时必须有校验。**待办清单示例里空的内容不允许添加这就是一个最小校验。真实业务里输入框、表单都可能有恶意内容直接使用innerHTML渲染用户输入的数据有XSS风险建议优先使用textContent来设置文本。**异步操作一定要写错误处理。**无论是fetch还是其他异步操作都要用try...catch包裹并在catch里给用户一个可见的反馈比如弹窗提示“网络请求失败”。不要因为“觉得不会出错”就把错误处理省略生产环境中网络错误极其常见。写下“调试日志”不是可耻的事情。console.log是入门阶段最好的调试工具。代码不工作时先在关键节点打印数据观察执行流程是否符合预期。等确认逻辑正确之后再把调试日志删掉。建议根据不同的场景使用console.error和console.warn方便区分错误等级。**最后说说框架与原生JavaScript的关系。**不少新手学完原生JS后看到Vue或React会疑惑学原生JavaScript到底有什么用框架固然能帮你省去操作DOM的繁琐步骤但框架底层仍然是JavaScript。你理解了querySelector和事件冒泡才能真正理解Vue的模板语法和事件修饰符是怎么回事你理解了fetch和Promise才能在框架里知道请求数据时的错误为什么会在控制台里报成那样。框架是技巧原生JavaScript是功底两者不是互斥关系而是先后关系。14. 总结与后续学习方向回到开头的问题2小时能不能快速入门JavaScript从“能把代码跑起来、能做交互、能发请求”这个标准来说完全可以。但从“完全理解这门语言”来说还远远不够。本文更像是一条路径地图帮你把JavaScript最核心的技术串起来变量与类型、函数与箭头函数、数组操作、DOM交互、事件机制、异步请求最后用待办清单应用巩固整个流程。按照这个顺序练熟你已经具备了基本的页面交互开发能力。如果你接下来想继续深入下面几个方向是按推荐顺序排的**第一个方向深入学习JavaScript进阶知识。**重点是this的指向、闭包、原型链、事件循环。这些概念面试经常问也决定你能不能读懂框架源码。但请务必在基础扎实之后再学不要本末倒置。**第二个方向学习模块化和工程化工具。**比如ES6模块的import/export以及常用的npm、Vite、Webpack。这能帮助你理解真实前端项目是如何组织代码的。**第三个方向选一个主流框架进入实战。**Vue和React二选一即可。开始前请确认自己已经熟练掌握了本文的数组map、filter、展开运算符、解构赋值等操作这些内容是框架里最常用的基础技能。最后提一个实际的建议把本文的待办清单代码拆掉重写三遍。第一遍照着敲第二遍不看代码自己写第三遍在原有功能上增加一个“编辑待办”的功能。每次重写都会让你对DOM操作、数据驱动视图和事件机制的理解加深一个层级。入门从来不是看会的过程而是写会的过程。建议收藏本文遇到不懂的基础问题时回来看一眼比重新翻整本教程高效得多。
返回列表