news 2026/9/9 3:00:53

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 onclick="alert('hello')">点击</button>

第二种:<script>标签内嵌

<!DOCTYPE html> <html> <head> <meta charset="UTF-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、const

ES6之前,JavaScript只有var一种声明方式,现在推荐使用letconst。它们的核心区别看这张表就够了:

声明方式作用域能否重复声明能否重新赋值使用建议
var函数作用域可以可以旧代码中会遇到,新代码不建议使用
let块级作用域不可以可以需要改变值的场景
const块级作用域不可以不可以默认优先使用

看一个实际区别:

if (true) { var a = 1; let b = 2; const c = 3; } console.log(a); // 1,var不关心块级,外面能访问 // 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数字,包括整数和浮点数423.14
string字符串"hello"'world'`模板字符串`
boolean布尔值truefalse
undefined声明了变量但没赋值let x;
null空值,表示“没有”let obj = null;
symbol唯一值,主要用于对象属性较少用到
object对象、数组、函数等都算{}[]

最容易让新手困惑的是nullundefined的区别。简单理解: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还引入了空值合并运算符??,它和||的区别是:??只在左边为nullundefined时取右边,而||在左边为false0、空字符串时也会取右边。这两者的区别在实际项目中很容易踩坑:

const count = 0; console.log(count || 100); // 100,因为0被当成false console.log(count ?? 100); // 0,只有null/undefined才走默认值

5.3 分支与循环

if...else是开发中最常用的逻辑控制方式,这里不做展开。switch在判断多个固定值时比一大串if更清晰,但使用频率不高。循环方面,forwhile仍然要掌握,但真实项目中,你更多会对**数组使用mapfilterforEach**而不是写一个传统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: "zhangsan@example.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才开始真正“碰页面”。DOM(Document 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 href="javascript: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选中元素、textContentstyle修改内容、addEventListener绑定事件,就已经能完成绝大多数交互需求了。

9. 异步JavaScript:从setTimeout到fetch

新手最容易卡住的地方就是异步。明明把接口返回的数据console.log了,控制台打出来的却是Promise,或者数据没显示就报错了。要理解异步,先要接受一个现实:浏览器里有些操作要花时间,比如请求服务器、读取文件、等待定时器执行,JavaScript不会停下来傻等,而是先继续执行后面的代码,等耗时操作完成后再回来执行回调

9.1 setTimeout与setInterval

setTimeout(() => { console.log("1秒后执行"); }, 1000); console.log("立即执行"); // 输出顺序:立即执行 -> 1秒后执行

这个结果可能颠覆新手的直觉,但它正好解释了JavaScript的执行模型。

9.2 Promise:把异步操作变成链条

Promise用来表示一个“将来才会有结果”的操作。它有resolve(成功)和reject(失败)两个状态,用thencatch来接收结果:

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.js

10.2 HTML结构

<!-- 文件路径:todo/index.html --> <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>待办清单</title> <link rel="stylesheet" href="./style.css"> </head> <body> <div class="container"> <h1>我的待办清单</h1> <div class="input-area"> <input type="text" id="todoInput" placeholder="请输入待办事项"> <button id="addBtn">添加</button> </div> <ul id="todoList"></ul> </div> <script src="./app.js"></script> </body> </html>

10.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,预期效果如下:

  1. 页面顶部有一个输入框和一个“添加”按钮。
  2. 输入“学习JavaScript”,点击添加,列表中出现这条待办事项。
  3. 点击这条待办事项的文本区域,文字出现删除线,表示已完成。
  4. 再次点击,删除线消失,表示未完成。
  5. 点击这条事项旁边的“删除”按钮,事项从列表中移除。
  6. 不输入内容直接点击添加,页面弹出提示“请输入待办内容”。
  7. 输入内容后按回车键,也能完成添加。

如果某一项没有出现预期效果,先按下键盘F12打开开发者工具,查看Console面板。如果代码有语法错误、获取元素名称拼写错误、引用的函数不存在,这里都会给出红色的报错信息。记住,报错信息是JavaScript调试的第一线索,不要忽略它

判断代码是否成功的另一个方法,是在关键位置临时插入console.log来观察数据的变化,例如在addTodo函数里添加一行console.log(todos),然后添加一条事项,观察控制台打印出来的数组结构。确认完再删除调试代码。

12. 常见问题与排查思路

新手运行JavaScript代码时会遇到的报错,其实高度集中。整理常见问题如下:

问题现象可能原因排查方式解决方案
xxx is not defined变量或函数名拼写错误,或在使用之前没有声明检查变量名大小写是否一致,确认声明位置统一命名规范,使用constlet声明后再使用
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小时内全部消化,但建议你先收藏,后面的项目里逐条实践。

**命名要符合直觉。**变量名用名词,比如userListtotalPrice;函数名用动词,比如getUsersaddTodorenderList。好的命名让代码读起来像文章,同事接手时不需要反复猜测作者意图。命名不要用拼音缩写、不要用无意义的abtmp

**坚决不在全局作用域里堆代码。**把逻辑拆成函数,让每个函数只负责一件事。addTodo只负责把数据推入数组,render只负责渲染,这样各自可以独立修改、独立测试。一个函数超过15行时,就要思考是不是该拆分了。

**多用const,少用let,不用var。**这个习惯能减少很多“变量被意外修改”的问题,也能让别人更快理解你的意图。如果你不确定一个值将来会不会变,先写成const,等需要修改时再改成let

**处理用户输入时必须有校验。**待办清单示例里,空的内容不允许添加,这就是一个最小校验。真实业务里,输入框、表单都可能有恶意内容,直接使用innerHTML渲染用户输入的数据有XSS风险,建议优先使用textContent来设置文本。

**异步操作一定要写错误处理。**无论是fetch还是其他异步操作,都要用try...catch包裹,并在catch里给用户一个可见的反馈,比如弹窗提示“网络请求失败”。不要因为“觉得不会出错”就把错误处理省略,生产环境中网络错误极其常见。

写下“调试日志”不是可耻的事情。console.log是入门阶段最好的调试工具。代码不工作时,先在关键节点打印数据,观察执行流程是否符合预期。等确认逻辑正确之后,再把调试日志删掉。建议根据不同的场景使用console.errorconsole.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二选一即可。开始前请确认自己已经熟练掌握了本文的数组mapfilter、展开运算符、解构赋值等操作,这些内容是框架里最常用的基础技能。

最后提一个实际的建议:把本文的待办清单代码拆掉重写三遍。第一遍照着敲,第二遍不看代码自己写,第三遍在原有功能上增加一个“编辑待办”的功能。每次重写都会让你对DOM操作、数据驱动视图和事件机制的理解加深一个层级。入门从来不是看会的过程,而是写会的过程。建议收藏本文,遇到不懂的基础问题时回来看一眼,比重新翻整本教程高效得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 2:57:17

TreeView 测试程序完全指南:节点、递归与性能边界全覆盖

简介&#xff1a;面向VB初学者的TreeView控件测试程序&#xff0c;围绕节点添加、删除、展开折叠、选择、编辑与遍历等典型操作&#xff0c;演示如何在Visual Basic工程中集成并控制该控件&#xff0c;适合正在学习WinForms或经典VB界面编程、希望以实例理解层次数据展示的开发…

作者头像 李华
网站建设 2026/9/9 2:57:12

全平台免费抓包工具详解:Wireshark、Fiddler与mitmproxy场景化选型

抓包这件事&#xff0c;听起来像黑客专属技能&#xff0c;其实早就成了后端开发、前端联调、移动端排障、协议分析甚至硬件调试的日常刚需。Windows 上有人双击打开 Wireshark 就蒙了&#xff0c;满屏花花绿绿的包不知道看哪个&#xff1b;macOS 用户到处找 Charles 的破解版&a…

作者头像 李华
网站建设 2026/9/9 2:56:35

Qt打包工具选型与部署实战:从依赖插件到免安装分发

凡是做过Qt客户端开发的人&#xff0c;多少都被“打包”这件事折磨过。我最早用Qt 5.9写了一个不到两千行的小工具&#xff0c;开发调试一切正常&#xff0c;结果把exe发给朋友&#xff0c;对方直接双击&#xff0c;弹了个“no qt platform plugin could be initialized”的窗口…

作者头像 李华
网站建设 2026/9/9 2:53:02

PDF翻译工具格式保留能力深度对比

1. 这不是“点开就翻”的小工具&#xff0c;而是一场格式保卫战你有没有过这种经历&#xff1a;花半小时整理好一份带目录、页眉页脚、多级标题、表格和图片标注的PDF技术白皮书&#xff0c;准备发给海外同事&#xff1b;结果随手拖进某个在线翻译器——再下载回来时&#xff0…

作者头像 李华
网站建设 2026/9/9 2:52:25

Ubuntu 20.04 x86环境下Qt 5.15.2源码编译完整指南

简介&#xff1a;面向需要在 Ubuntu 20.04 x86 环境编译或使用 Qt 5.15.2 的 C 开发者&#xff0c;这份资源整理了对应 Linux x86 平台源码包中的头文件集合&#xff0c;解决了从源码树中反复查找 Qt 声明的痛点。压缩包采用 zip 格式&#xff0c;包含 2000 个 .h 文件&#xf…

作者头像 李华
网站建设 2026/9/9 2:51:36

Windows上Git升级全攻略:从安全补丁到配置备份的完整指南

总有人问我&#xff1a;Windows上的Git到底要不要升级&#xff1f;怎么升&#xff1f;大多数电脑上那个Git&#xff0c;装完那天是什么版本&#xff0c;可能到现在还是什么版本。我自己见过不少开发机&#xff0c; git --version 打出来还是几年以前的版本&#xff0c;一问就…

作者头像 李华