第三章:JavaScript — 让页面动起来
前两章我们学了 HTML 和 CSS,已经能做出好看的静态页面了。但”静态”两个字说明了一切——页面是死的,用户点什么都没反应。这一章,我们请出前端的第三位主角:JavaScript(简称 JS)。
3.1 JS 是什么
打个比方:
HTML 是骨架(页面的结构),CSS 是皮肤(页面的外观),JS 是大脑和肌肉(页面的行为)。
有了 JS,页面就活了。它能做什么?随便举几个:
- 点击按钮弹出提示框
- 输入内容后实时显示字数
- 提交表单前检查你填的对不对
- 从服务器拿数据,动态渲染到页面上(比如加载一条新闻列表)
- 做动画、做游戏、做聊天功能……
基本上你在网页上看到的所有”交互”,背后都是 JS 在干活。
在 HTML 中使用 JS
两种方式:
方式一:直接写在 <script> 标签里
<!DOCTYPE html>
<html>
<head>
<title>我的第一段 JS</title>
</head>
<body>
<h1>打开控制台看看</h1>
<script>
console.log('Hello JS!')
</script>
</body>
</html>
方式二:写在外部文件里(推荐)
<!-- index.html -->
<body>
<h1>打开控制台看看</h1>
<script src="main.js"></script>
</body>
// main.js
console.log('Hello JS!')
实际开发中基本都用外部文件,代码和页面分离,更好维护。
第一行代码 & 打开控制台
console.log() 是 JS 里的”打印”功能,它会把内容输出到浏览器的控制台。
怎么打开控制台?
- 用浏览器打开你的 HTML 文件
- 按 F12(或右键 → 检查)
- 点击上方的 Console(控制台) 标签
- 你就能看到
Hello JS!了
以后写 JS 的时候,控制台就是你的好朋友,调试全靠它。
3.2 变量与数据类型
变量:给数据起个名字
变量就像一个盒子,你给它贴个标签(名字),然后把东西(数据)放进去。
let age = 20 // 我今年 20 岁
let name = '小明' // 我叫小明
JS 里声明变量用两个关键字:
let— 值可以变的,用letconst— 值不会变的,用const
let score = 80
score = 95 // ✅ let 声明的变量可以重新赋值
const PI = 3.14159
PI = 3 // ❌ 报错!const 声明的不能改
一句话:能用
const就用const,需要改的才用let。至于var?忘掉它,那是老写法,容易出 bug。
常用数据类型
| 类型 | 长什么样 | 说明 |
|---|---|---|
| 字符串 | "你好" 或 'Hello' | 用引号包裹的文字 |
| 数字 | 42、3.14 | 直接写数字,不加引号 |
| 布尔值 | true / false | 表示”是”或”否” |
| 数组 | [1, 2, 3] | 一组数据的有序列表(后面详细讲) |
| 对象 | { name: "小明" } | 用键值对描述一个事物(后面详细讲) |
const nickname = '前端小白' // 字符串
const age = 20 // 数字
const isStudent = true // 布尔值
const hobbies = ['游戏', '音乐', '编程'] // 数组
const user = { name: '小明', age: 20 } // 对象
console.log(typeof nickname) // "string"
console.log(typeof age) // "number"
console.log(typeof isStudent) // "boolean"
模板字符串:更优雅的拼接方式
以前拼接字符串要用 + 号,又丑又容易出错:
const name = '小明'
const age = 20
console.log('我叫' + name + ',今年' + age + '岁') // 😩 太难受了
现在用反引号 ` 和 ${} 就舒服多了:
const name = '小明'
const age = 20
console.log(`我叫${name},今年${age}岁`) // 😎 清爽!
反引号在键盘左上角,数字 1 的左边那个键(
~键)。以后拼接字符串一律用模板字符串。
3.3 函数
什么是函数?
函数就是一段可以反复使用的代码。就像一台榨汁机:你放进去水果(参数),它吐出来果汁(返回值)。
普通函数声明
function greet(name) {
return `你好,${name}!欢迎学习 JS`
}
// 调用函数
console.log(greet('小明')) // 你好,小明!欢迎学习 JS
console.log(greet('小红')) // 你好,小红!欢迎学习 JS
name是参数,调用时传进来的值return是返回值,函数执行完吐出来的结果
箭头函数(现代写法)
const greet = (name) => {
return `你好,${name}!欢迎学习 JS`
}
如果函数体只有一行,还能更简洁:
const greet = (name) => `你好,${name}!欢迎学习 JS`
箭头函数是 ES6 引入的现代写法,后面学 Vue 的时候会大量使用,所以一定要熟悉。
更多示例
// 计算两个数的和
const add = (a, b) => a + b
console.log(add(3, 5)) // 8
// 判断是否成年
const isAdult = (age) => age >= 18
console.log(isAdult(20)) // true
console.log(isAdult(15)) // false
// 生成一句自我介绍
const introduce = (name, hobby) => {
return `大家好,我是${name},我喜欢${hobby}。`
}
console.log(introduce('小明', '打篮球'))
// 大家好,我是小明,我喜欢打篮球。
3.4 条件判断
程序经常需要做判断:如果怎样就怎样,否则怎样。这就是条件判断。
if / else if / else
const score = 85
if (score >= 90) {
console.log('优秀')
} else if (score >= 80) {
console.log('良好')
} else if (score >= 60) {
console.log('及格')
} else {
console.log('不及格')
}
// 输出:良好
比较运算符
| 运算符 | 含义 | 示例 |
|---|---|---|
=== | 严格等于 | 3 === 3 → true |
!== | 严格不等于 | 3 !== 5 → true |
> | 大于 | 5 > 3 → true |
< | 小于 | 3 < 5 → true |
>= | 大于等于 | 5 >= 5 → true |
<= | 小于等于 | 3 <= 5 → true |
重要:永远用
===而不是==。==会做类型转换,容易产生意想不到的结果。比如"1" == 1是true,但"1" === 1是false。用三等号更安全。
逻辑运算符
| 运算符 | 含义 | 说明 |
|---|---|---|
&& | 与(AND) | 两边都为 true 才是 true |
|| | 或(OR) | 有一边为 true 就是 true |
! | 非(NOT) | 取反,true 变 false |
const age = 20
const hasTicket = true
// 必须成年 并且 有票 才能进
if (age >= 18 && hasTicket) {
console.log('欢迎入场')
}
// 是 VIP 或者 有邀请码 就能进
const isVIP = false
const hasInviteCode = true
if (isVIP || hasInviteCode) {
console.log('欢迎入场')
}
实际示例:成绩等级判定函数
const getGrade = (score) => {
if (score >= 90) return 'A(优秀)'
if (score >= 80) return 'B(良好)'
if (score >= 70) return 'C(中等)'
if (score >= 60) return 'D(及格)'
return 'F(不及格)'
}
console.log(getGrade(92)) // A(优秀)
console.log(getGrade(75)) // C(中等)
console.log(getGrade(43)) // F(不及格)
3.5 数组与常用方法
数组就是一组数据排成一排,用方括号 [] 包裹,逗号隔开。
创建和访问
const fruits = ['苹果', '香蕉', '橘子', '葡萄']
// 用下标访问,从 0 开始数
console.log(fruits[0]) // 苹果
console.log(fruits[2]) // 橘子
console.log(fruits.length) // 4(数组长度)
注意:下标从 0 开始,不是 1。
fruits[0]是第一个元素。
常用方法
push() — 往数组末尾添加元素
const list = ['HTML', 'CSS']
list.push('JavaScript')
console.log(list) // ['HTML', 'CSS', 'JavaScript']
forEach() — 遍历数组(对每个元素执行操作)
const fruits = ['苹果', '香蕉', '橘子']
fruits.forEach((fruit, index) => {
console.log(`第${index + 1}个水果是:${fruit}`)
})
// 第1个水果是:苹果
// 第2个水果是:香蕉
// 第3个水果是:橘子
map() — 把每个元素变换成新的,返回新数组
const numbers = [1, 2, 3, 4, 5]
const doubled = numbers.map((n) => n * 2)
console.log(doubled) // [2, 4, 6, 8, 10]
map()在 Vue 中渲染列表时非常常用,一定要掌握。
filter() — 筛选出符合条件的元素
const scores = [45, 78, 92, 56, 88, 33, 71]
const passed = scores.filter((s) => s >= 60)
console.log(passed) // [78, 92, 88, 71]
综合示例
// 学生成绩处理
const students = [
{ name: '小明', score: 92 },
{ name: '小红', score: 56 },
{ name: '小刚', score: 78 },
{ name: '小美', score: 88 },
{ name: '小李', score: 45 },
]
// 筛选出及格的同学
const passedStudents = students.filter((s) => s.score >= 60)
// 打印及格同学的名字和成绩
passedStudents.forEach((s) => {
console.log(`${s.name}:${s.score}分`)
})
// 小明:92分
// 小刚:78分
// 小美:88分
// 提取所有同学的名字
const names = students.map((s) => s.name)
console.log(names) // ['小明', '小红', '小刚', '小美', '小李']
3.6 对象
什么是对象?
对象就是用来描述一个”东西”的所有信息。比如描述一个学生:
const student = {
name: '小明',
age: 20,
major: '计算机科学',
isGraduated: false,
}
对象用花括号 {} 包裹,里面是一对对的 键(key): 值(value)。
访问属性
// 方式一:点号(最常用)
console.log(student.name) // 小明
console.log(student.age) // 20
// 方式二:方括号(key 是变量或有特殊字符时用)
console.log(student['major']) // 计算机科学
const key = 'name'
console.log(student[key]) // 小明
对象数组:最常见的数据结构
实际开发中,你从服务器拿到的数据大多长这样——一个数组,里面每个元素是一个对象:
const products = [
{ id: 1, name: '手机壳', price: 29.9 },
{ id: 2, name: '数据线', price: 15.0 },
{ id: 3, name: '充电宝', price: 89.0 },
]
// 遍历显示商品信息
products.forEach((p) => {
console.log(`${p.name} — ¥${p.price}`)
})
后面学 Vue 做项目、调用 API 的时候,打交道最多的就是这种数据结构。一定要熟悉。
解构赋值:快速取出对象中的值
每次都写 student.name、student.age 太啰嗦了,可以用解构赋值一步到位:
const user = { name: '小明', age: 20, city: '北京' }
// 一行代码取出 name 和 age
const { name, age, city } = user
console.log(name) // 小明
console.log(age) // 20
console.log(city) // 北京
在函数参数中也能用:
const printUser = ({ name, age }) => {
console.log(`${name},${age}岁`)
}
printUser({ name: '小红', age: 19 }) // 小红,19岁
3.7 DOM 操作 — JS 操控页面的核心
前面我们写的 JS 都在控制台输出,但 JS 真正的威力是操控页面。它是怎么做到的呢?靠的就是 DOM。
什么是 DOM?
DOM(Document Object Model) 就是浏览器把 HTML 变成了一棵”对象树”。每个标签都变成了一个对象,JS 可以找到它们、修改它们、删除它们、创建新的。
简单说:HTML 是你写的代码,DOM 是浏览器读完代码后在内存中搭建的结构,JS 通过 DOM 来操控页面。
获取元素
// 获取单个元素(返回第一个匹配的)
const title = document.querySelector('h1')
const btn = document.querySelector('#myBtn')
const box = document.querySelector('.box')
// 获取多个元素(返回一个列表)
const items = document.querySelectorAll('.list-item')
querySelector里写的就是 CSS 选择器,和你在 CSS 里写的一样。
修改内容
const title = document.querySelector('h1')
// 修改文字内容
title.textContent = '新标题'
// 修改 HTML 内容(可以插入标签)
title.innerHTML = '新<em>标题</em>'
修改样式
const box = document.querySelector('.box')
// 直接改 style(适合改单个属性)
box.style.backgroundColor = '#3498db'
box.style.color = 'white'
box.style.padding = '20px'
// 操作 class(推荐,适合批量样式切换)
box.classList.add('active') // 添加 class
box.classList.remove('active') // 移除 class
box.classList.toggle('active') // 有就移除,没有就添加
监听事件
const btn = document.querySelector('#myBtn')
btn.addEventListener('click', () => {
console.log('按钮被点击了!')
})
常见事件:click(点击)、input(输入)、submit(提交)、mouseover(鼠标移入)……
创建和添加元素
// 创建一个新的 li 元素
const newItem = document.createElement('li')
newItem.textContent = '新的列表项'
// 把它添加到页面上的 ul 里
const list = document.querySelector('ul')
list.appendChild(newItem)
完整示例一:点击按钮修改文字
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>DOM 示例 1</title>
</head>
<body>
<h1 id="title">点击下面的按钮试试</h1>
<button id="btn">点我</button>
<script>
const title = document.querySelector('#title')
const btn = document.querySelector('#btn')
btn.addEventListener('click', () => {
title.textContent = 'JS 真的好神奇!🎉'
})
</script>
</body>
</html>
完整示例二:点击按钮切换样式
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>DOM 示例 2</title>
<style>
.box {
width: 200px;
height: 200px;
background-color: #3498db;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
transition: all 0.3s;
border-radius: 8px;
}
.box.active {
background-color: #e74c3c;
transform: scale(1.1);
}
</style>
</head>
<body>
<div class="box" id="box">点我变色</div>
<script>
const box = document.querySelector('#box')
box.addEventListener('click', () => {
box.classList.toggle('active')
})
</script>
</body>
</html>
重要提醒: 后面学了 Vue 之后,你就不需要手动操作 DOM 了——Vue 会帮你搞定。但理解 DOM 是必须的基础,因为 Vue 本质上也是在操作 DOM,只是帮你封装好了。
3.8 异步与网络请求
什么是异步?
一个类比:
你点了外卖,不需要站在门口等外卖到了才去做别的事。你可以先去写作业、打游戏,外卖到了手机会通知你。
JS 中的异步也是这个意思。发一个网络请求需要等服务器返回数据,如果”同步”等待,页面就会卡住不动。用”异步”的方式,JS 会先去干别的事,等数据回来了再处理。
async / await 语法
这是目前最简洁好读的异步写法:
// 在函数前加 async,表示这是一个异步函数
// 在需要等待的操作前加 await,表示"等这个操作完成再继续"
const fetchData = async () => {
const res = await fetch('https://api.example.com/data')
const data = await res.json()
console.log(data)
}
fetchData()
async加在函数前面,声明这是个异步函数await加在异步操作前面,意思是”等它完成,拿到结果再往下走”await只能在async函数里面用
fetch() 发送网络请求
fetch() 是浏览器内置的发请求的函数,不需要装任何库。
const getJoke = async () => {
// 发送 GET 请求
const res = await fetch('https://official-joke-api.appspot.com/random_joke')
// 把响应转成 JSON 对象
const joke = await res.json()
console.log(`问:${joke.setup}`)
console.log(`答:${joke.punchline}`)
}
getJoke()
错误处理:try / catch
网络请求可能失败(断网、服务器挂了……),所以最好包一层 try/catch:
const getData = async () => {
try {
const res = await fetch('https://api.example.com/users')
const users = await res.json()
console.log('获取到的用户数据:', users)
} catch (error) {
console.log('请求失败了:', error.message)
}
}
getData()
try里放正常逻辑- 如果出错了,自动跳到
catch里处理
这一节的
async/await和fetch是为下一章学 Vue 调用 API 做铺垫。到时候你会在 Vue 组件里用类似的写法从后端获取数据,现在先把语法搞熟就行。
3.9 实战练习:待办事项
学了这么多,来做个小项目吧!我们用纯 HTML + CSS + JS 做一个待办事项功能:
- 在输入框中输入内容,点击按钮添加到列表
- 点击列表项可以标记为”已完成”(加删除线)
完整代码
把下面的代码保存成 todo.html,用浏览器打开就能玩:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>待办事项</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, 'Microsoft YaHei', sans-serif;
background-color: #f0f2f5;
display: flex;
justify-content: center;
padding-top: 60px;
}
.todo-app {
background: white;
padding: 30px;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
width: 400px;
}
.todo-app h1 {
text-align: center;
margin-bottom: 20px;
color: #333;
font-size: 24px;
}
.input-area {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.input-area input {
flex: 1;
padding: 10px 14px;
border: 2px solid #ddd;
border-radius: 8px;
font-size: 15px;
outline: none;
transition: border-color 0.2s;
}
.input-area input:focus {
border-color: #3498db;
}
.input-area button {
padding: 10px 20px;
background-color: #3498db;
color: white;
border: none;
border-radius: 8px;
font-size: 15px;
cursor: pointer;
transition: background-color 0.2s;
}
.input-area button:hover {
background-color: #2980b9;
}
.todo-list {
list-style: none;
}
.todo-list li {
padding: 12px 14px;
border-bottom: 1px solid #eee;
cursor: pointer;
font-size: 15px;
color: #333;
transition: all 0.2s;
}
.todo-list li:hover {
background-color: #f8f9fa;
}
.todo-list li.done {
text-decoration: line-through;
color: #aaa;
}
.empty-tip {
text-align: center;
color: #bbb;
padding: 20px;
font-size: 14px;
}
</style>
</head>
<body>
<div class="todo-app">
<h1>📝 待办事项</h1>
<div class="input-area">
<input type="text" id="todoInput" placeholder="输入待办事项...">
<button id="addBtn">添加</button>
</div>
<ul class="todo-list" id="todoList">
<!-- 待办事项会被 JS 添加到这里 -->
</ul>
<p class="empty-tip" id="emptyTip">还没有待办事项,快添加一个吧!</p>
</div>
<script>
// 获取页面元素
const input = document.querySelector('#todoInput')
const addBtn = document.querySelector('#addBtn')
const todoList = document.querySelector('#todoList')
const emptyTip = document.querySelector('#emptyTip')
// 更新空提示的显示状态
const updateEmptyTip = () => {
if (todoList.children.length === 0) {
emptyTip.style.display = 'block'
} else {
emptyTip.style.display = 'none'
}
}
// 添加待办事项
const addTodo = () => {
const text = input.value.trim()
// 输入为空就不添加
if (text === '') return
// 创建新的列表项
const li = document.createElement('li')
li.textContent = text
// 点击列表项,切换"完成"状态
li.addEventListener('click', () => {
li.classList.toggle('done')
})
// 添加到列表中
todoList.appendChild(li)
// 清空输入框,方便继续输入
input.value = ''
input.focus()
// 更新空提示
updateEmptyTip()
}
// 点击按钮添加
addBtn.addEventListener('click', addTodo)
// 按回车也能添加
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
addTodo()
}
})
</script>
</body>
</html>
代码解读
- 获取元素:用
querySelector获取输入框、按钮、列表等 DOM 元素 addTodo函数:读取输入框内容 → 创建<li>元素 → 绑定点击事件 → 添加到列表- 点击切换完成:给每个
<li>添加click事件,点击时toggle一个doneclass,CSS 中定义了.done有删除线效果 - 按回车添加:监听
keydown事件,判断按的是不是 Enter 键
动手扩展
这个小项目有很多可以玩的地方,试试看:
- 给每个待办事项加一个”删除”按钮
- 在顶部显示”共 X 项,已完成 X 项”
- 用
localStorage保存数据,刷新页面后不会丢失(可以搜一下怎么用) - 给已完成和未完成的事项加上不同的背景色
恭喜你! 到这里,你已经掌握了 JavaScript 的核心基础。我们来回顾一下这一章学了什么:
- 变量和数据类型:
let、const、字符串、数字、布尔、数组、对象 - 函数:普通函数和箭头函数
- 条件判断:
if/else、比较运算符、逻辑运算符 - 数组方法:
forEach、map、filter - 对象:键值对、解构赋值、对象数组
- DOM 操作:获取元素、修改内容和样式、监听事件
- 异步请求:
async/await、fetch
下一章我们就要进入 Vue.js 的世界了。有了这一章的 JS 基础,你会发现 Vue 其实很好上手——它把 DOM 操作、数据绑定、事件处理都封装好了,让你用更少的代码做出更多的事情。加油!