目录
- [一、CSS 响应式布局(Responsive Design)深度解析](#一、CSS 响应式布局(Responsive Design)深度解析)
- [1.1 什么是响应式布局](#1.1 什么是响应式布局)
- [1.2 视口 Viewport 详解](#1.2 视口 Viewport 详解)
- [1.3 媒体查询 Media Query](#1.3 媒体查询 Media Query)
- [1.4 弹性布局 Flexbox](#1.4 弹性布局 Flexbox)
- [1.5 响应式图片方案](#1.5 响应式图片方案)
- [1.6 经典响应式布局实战](#1.6 经典响应式布局实战)
- [二、BFC 块级格式上下文深度解析](#二、BFC 块级格式上下文深度解析)
- [2.1 官方定义解读](#2.1 官方定义解读)
- [2.2 BFC 的触发条件](#2.2 BFC 的触发条件)
- [2.3 BFC 解决的经典问题](#2.3 BFC 解决的经典问题)
- [三、JavaScript 完全入门指南](#三、JavaScript 完全入门指南)
- [3.1 JavaScript 是什么](#3.1 JavaScript 是什么)
- [3.2 浏览器端 JS 组成](#3.2 浏览器端 JS 组成)
- [3.3 三种使用方式](#3.3 三种使用方式)
- [3.4 基本语法规则](#3.4 基本语法规则)
- [3.5 输出内容的三种方法](#3.5 输出内容的三种方法)
- 四、变量(Variable)深度解析
- [4.1 数据、直接量与变量的区别](#4.1 数据、直接量与变量的区别)
- [4.2 变量的语法](#4.2 变量的语法)
- [4.3 命名规范](#4.3 命名规范)
- [五、数据类型(Data Types)完全指南](#五、数据类型(Data Types)完全指南)
- [5.1 类型分类](#5.1 类型分类)
- [5.2 typeof 运算符](#5.2 typeof 运算符)
- [5.3 number 数值类型](#5.3 number 数值类型)
- [5.4 string 字符串类型](#5.4 string 字符串类型)
- [5.5 boolean 布尔类型](#5.5 boolean 布尔类型)
- [5.6 null 与 undefined](#5.6 null 与 undefined)
- 六、知识总结与思维导图
- 七、经典作业解析
- 附录:推荐学习资源
- [八、ES6+ 进阶:let、const 与变量提升](#八、ES6+ 进阶:let、const 与变量提升)
- [8.1 var 的局限性与历史问题](#8.1 var 的局限性与历史问题)
- [8.2 let 和 const:ES6 的解决方案](#8.2 let 和 const:ES6 的解决方案)
- [8.3 Symbol 和 BigInt:ES6+ 新增的原始类型](#8.3 Symbol 和 BigInt:ES6+ 新增的原始类型)
- 九、综合实战:购物车计算器
- 十、全章注意事项汇总
- [10.1 CSS 响应式布局注意事项](#10.1 CSS 响应式布局注意事项)
- [10.2 BFC 注意事项](#10.2 BFC 注意事项)
- [10.3 JavaScript 语法注意事项](#10.3 JavaScript 语法注意事项)
- [10.4 ES6+ 注意事项](#10.4 ES6+ 注意事项)
- 十一、知识点速查总结
- [11.1 CSS 响应式布局速查](#11.1 CSS 响应式布局速查)
- [11.2 JavaScript 数据类型速查表](#11.2 JavaScript 数据类型速查表)
- [11.3 var/let/const 三分钟速记](#11.3 var/let/const 三分钟速记)
- [11.4 BFC 三分钟速记](#11.4 BFC 三分钟速记)
- 十二、学习路线建议
一、CSS 响应式布局
1.1 什么是响应式布局
响应式布局(Responsive Web Design,简称 RWD) 是由设计师 Ethan Marcotte 在 2010 年提出的概念,其核心思想是:同一套 HTML 代码,根据不同设备的屏幕尺寸,自动调整布局、字体、图片等元素的显示方式,从而在手机、平板、桌面端都能提供良好的用户体验。
名词解释
| 名词 | 全称 | 含义 |
|---|---|---|
| RWD | Responsive Web Design | 响应式网页设计 |
| Breakpoint | 断点 | 触发布局变化的屏幕宽度阈值 |
| Viewport | 视口 | 浏览器可视区域的大小 |
| Media Query | 媒体查询 | CSS 中根据设备特性应用不同样式的技术 |
| Fluid Layout | 流式布局 | 使用百分比代替固定像素的布局方式 |
响应式设计的三大核心原则
响应式设计三大核心
流式网格 Fluid Grid
弹性图片 Flexible Images
媒体查询 Media Queries
使用百分比/rem/em代替px
img width: 100%
@media screen and (min-width: 768px)
真实网站中的响应式应用
| 网站 | 响应式策略 | 断点设置 |
|---|---|---|
| GitHub | 移动端隐藏侧边栏、折叠导航 | 768px / 1024px |
| Twitter / X | 侧边栏→底部导航栏 | 500px / 1000px / 1280px |
| Bootstrap 框架 | 12列栅格系统 | 576/768/992/1200/1400px |
| Tailwind CSS | 预设5个断点 | sm/md/lg/xl/2xl |
1.2 视口 Viewport 详解
名词解释:视口(Viewport)
视口分为两种:
- 布局视口(Layout Viewport): 浏览器默认的布局宽度,移动端通常为 980px,用于兼容桌面端页面。
- 视觉视口(Visual Viewport): 用户实际看到的区域。
- 理想视口(Ideal Viewport): 设备屏幕的物理宽度,通过
meta标签设置。
html
<!-- 设置理想视口 ------ 这是响应式布局的必备标签 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
| 属性 | 含义 | 常用值 |
|---|---|---|
width |
视口宽度 | device-width(设备宽度) |
initial-scale |
初始缩放比例 | 1.0(不缩放) |
maximum-scale |
最大缩放比例 | 1.0(禁止缩放) |
user-scalable |
是否允许用户缩放 | no(禁止) |
1.3 媒体查询 Media Query
名词解释:媒体查询(Media Query)
媒体查询是 CSS3 的特性,允许根据设备的特性(如屏幕宽度、分辨率、方向等)来应用不同的 CSS 规则。它是响应式布局的核心技术。
基础语法
css
/* 当屏幕宽度 >= 640px 时应用 */
@media (min-width: 640px) {
.container { width: 80%; }
}
/* 当屏幕宽度 <= 1024px 时应用 */
@media (max-width: 1024px) {
.sidebar { display: none; }
}
/* 组合条件:屏幕宽度在 640px ~ 1024px 之间 */
@media (min-width: 640px) and (max-width: 1024px) {
.nav { flex-direction: row; }
}
/* 竖屏模式 */
@media (orientation: portrait) {
.hero { height: 80vh; }
}
移动优先 vs 桌面优先
两种响应式策略
移动优先 Mobile First
桌面优先 Desktop First
基础样式适配手机
min-width 向上覆盖
推荐✅ 性能更好
基础样式适配桌面
max-width 向下覆盖
旧项目改造用
移动优先(推荐):
css
/* 基础样式:手机(< 640px) */
.nav-list { display: none; }
/* 平板(>= 640px) */
@media (min-width: 640px) {
.nav-list { display: flex; }
}
/* 桌面(>= 1024px) */
@media (min-width: 1024px) {
.nav-list { gap: 20px; }
}
完整可运行示例:媒体查询响应式导航
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>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
/* 移动端基础样式 */
header {
background: #2c3e50;
color: white;
padding: 0 20px;
}
.header-inner {
display: flex;
align-items: center;
justify-content: space-between;
height: 56px;
}
.logo { font-size: 20px; font-weight: bold; color: #3498db; }
/* 汉堡菜单按钮(移动端显示) */
.hamburger {
display: flex;
flex-direction: column;
gap: 5px;
cursor: pointer;
padding: 5px;
}
.hamburger span {
display: block;
width: 24px;
height: 2px;
background: white;
border-radius: 2px;
transition: 0.3s;
}
/* 移动端导航(默认隐藏) */
nav ul {
list-style: none;
background: #34495e;
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease;
}
nav ul.active { max-height: 300px; }
nav ul li a {
display: block;
padding: 14px 20px;
color: #ecf0f1;
text-decoration: none;
font-size: 15px;
border-bottom: 1px solid #455a64;
}
nav ul li a:hover { background: #3498db; }
/* 平板端(>= 640px) */
@media (min-width: 640px) {
.hamburger { display: none; }
nav ul {
display: flex !important;
max-height: none;
background: transparent;
overflow: visible;
}
nav ul li a {
border: none;
padding: 0 16px;
line-height: 56px;
color: #bdc3c7;
}
nav ul li a:hover {
background: transparent;
color: #3498db;
}
}
/* 桌面端(>= 1024px) */
@media (min-width: 1024px) {
.header-inner { max-width: 1200px; margin: 0 auto; }
nav ul li a { padding: 0 24px; }
}
.content {
padding: 40px 20px;
text-align: center;
font-family: Arial, sans-serif;
}
.badge {
display: inline-block;
padding: 4px 12px;
background: #3498db;
color: white;
border-radius: 20px;
font-size: 12px;
margin: 4px;
}
.size-indicator {
margin-top: 20px;
padding: 20px;
border: 2px dashed #ccc;
border-radius: 8px;
}
.size-mobile { display: block; color: #e74c3c; font-size: 18px; font-weight: bold; }
.size-tablet { display: none; color: #f39c12; font-size: 18px; font-weight: bold; }
.size-desktop { display: none; color: #27ae60; font-size: 18px; font-weight: bold; }
@media (min-width: 640px) {
.size-mobile { display: none; }
.size-tablet { display: block; }
}
@media (min-width: 1024px) {
.size-tablet { display: none; }
.size-desktop { display: block; }
}
</style>
</head>
<body>
<header>
<div class="header-inner">
<div class="logo">⚡ WebDev</div>
<div class="hamburger" onclick="toggleNav()">
<span></span>
<span></span>
<span></span>
</div>
<nav>
<ul id="navList">
<li><a href="#">首页</a></li>
<li><a href="#">教程</a></li>
<li><a href="#">实战</a></li>
<li><a href="#">社区</a></li>
</ul>
</nav>
</div>
</header>
<div class="content">
<h2>媒体查询响应式布局演示</h2>
<p>拖动浏览器窗口改变宽度,观察导航栏变化</p>
<div class="size-indicator">
<span class="size-mobile">📱 当前:手机端(宽度 < 640px)--- 显示汉堡菜单</span>
<span class="size-tablet">📲 当前:平板端(640px ~ 1023px)--- 显示横排导航</span>
<span class="size-desktop">🖥️ 当前:桌面端(宽度 >= 1024px)--- 宽松横排导航</span>
</div>
<div style="margin-top: 20px;">
断点:
<span class="badge">手机 <640px</span>
<span class="badge">平板 640px+</span>
<span class="badge">桌面 1024px+</span>
</div>
</div>
<script>
function toggleNav() {
const nav = document.getElementById('navList');
nav.classList.toggle('active');
}
</script>
</body>
</html>

💡 代码解释
HTML 结构分析:
<meta name="viewport">- 设置理想视口,这是响应式布局的必备标签<header>结构 :.logo- Logo 区域.hamburger- 汉堡菜单按钮(3条横线)<nav>- 导航菜单容器
CSS 核心技巧:
css
/* 技巧1:移动优先策略 - 基础样式适配手机 */
nav ul {
max-height: 0; /* 默认折叠,高度为0 */
overflow: hidden; /* 隐藏溢出内容 */
transition: max-height 0.3s ease; /* 平滑动画 */
}
nav ul.active { max-height: 300px; } /* 点击后展开 */
/* 技巧2:媒体查询 - 平板端及以上显示横排导航 */
@media (min-width: 640px) {
.hamburger { display: none; } /* 隐藏汉堡菜单 */
nav ul {
display: flex !important; /* 横排显示 */
max-height: none; /* 取消高度限制 */
}
}
/* 技巧3:桌面端优化 - 增加间距 */
@media (min-width: 1024px) {
.header-inner {
max-width: 1200px; /* 限制最大宽度 */
margin: 0 auto; /* 居中显示 */
}
}
JavaScript 交互逻辑:
javascript
function toggleNav() {
const nav = document.getElementById('navList');
nav.classList.toggle('active'); // 切换 active 类,控制菜单展开/收起
}
响应式效果:
- 📱 手机端(<640px):显示汉堡菜单,点击展开导航
- 📲 平板端(640px~1023px):横排导航,隐藏汉堡菜单
- 🖥️ 桌面端(≥1024px):宽松横排导航,居中显示
1.4 弹性布局 Flexbox
名词解释:Flexbox(弹性盒模型)
Flexbox 全称 CSS Flexible Box Layout Module ,是 CSS3 的布局模块。通过设置 display: flex,父容器变为弹性容器(Flex Container) ,其直接子元素成为弹性项目(Flex Items),从而实现一维方向(行或列)上的灵活布局。
Flex 核心属性速查
Flex 属性
容器属性
项目属性
flex-direction: 主轴方向
flex-wrap: 是否换行
justify-content: 主轴对齐
align-items: 交叉轴对齐
gap: 项目间距
flex: grow shrink basis
align-self: 自身对齐
order: 排列顺序
响应式导航中的 Flexbox 应用(来自课堂案例)
css
/* 来自课堂案例:01-响应式布局案例/index.html */
/* 头部:flex 横向排列 */
.page-header {
display: flex;
height: 40px;
padding: 8px 0 15px;
}
/* 搜索框:flex 实现 input + button 紧贴 */
.search-box {
display: flex;
width: 60%;
}
/* 平板端:导航列表改为 flex */
@media (min-width: 640px) {
.nav-list {
display: flex;
width: 100%;
height: 40px;
}
/* 每个导航项等分 */
.nav-list li {
flex: 1 1 0;
}
}
/* 课程列表:flex 响应式网格 */
@media (min-width: 640px) {
.courses ul {
display: flex;
flex-wrap: wrap; /* 允许换行,实现网格效果 */
}
.course-item { width: 50%; } /* 两列 */
}
@media (min-width: 1024px) {
.course-item { width: 25%; } /* 四列 */
}
💡 代码解释
Flexbox 核心概念:
css
/* 1. 创建 Flex 容器 */
.page-header {
display: flex; /* 父元素设置为 flex 容器 */
}
/* 效果:所有直接子元素自动变为 flex 项目,默认横向排列 */
/* 2. Flex 项目自动填充空间 */
.search-box {
display: flex; /* 搜索框内部也是 flex */
width: 60%; /* 占父容器60%宽度 */
}
/* input 和 button 会自动横向排列并紧贴 */
/* 3. Flex 项目等分空间 */
.nav-list li {
flex: 1 1 0;
/* 相当于:flex-grow: 1, flex-shrink: 1, flex-basis: 0 */
/* 效果:所有导航项平均分配剩余空间 */
}
/* 4. Flex + wrap 实现响应式网格 */
.courses ul {
display: flex;
flex-wrap: wrap; /* 允许换行 */
}
.course-item {
width: 50%; /* 每行2个 */
}
/* 当空间不足时,第3个项目会自动换到下一行 */
Flexbox 与 Grid 的区别:
- Flexbox:一维布局(单行或单列),适合组件级布局
- Grid:二维布局(行+列),适合页面级布局
真实应用场景:
- ✅ GitHub 的文件列表(一列布局)
- ✅ Twitter 的推文卡片(响应式多列)
- ✅ 淘宝的商品网格(flex-wrap 换行)
真实场景: GitHub 的文件列表、Twitter 的推文卡片、淘宝的商品网格,均大量使用 Flexbox 实现响应式布局。
1.5 响应式图片方案
响应式图片要解决的问题:不同屏幕加载合适尺寸的图片,避免手机加载桌面大图浪费流量。
四种方案对比
响应式图片方案
方案一: CSS background-image
方案二: img display 切换
方案三: picture + source 元素
方案四: img srcset 属性
@media 中切换 background-image URL
准备多张img,用display:none切换
浏览器原生支持,按条件加载
提供宽度描述符,浏览器自动选择
✅ 推荐,语义化好
✅ 推荐,简洁
方案三:<picture> 元素(来自课堂案例)
html
<!-- 来自课堂案例:响应式布局 Banner 实现 -->
<!-- 从上到下依次判断,满足条件加载图片,后面不再执行 -->
<picture>
<!-- 桌面端加载大图 -->
<source srcset="./images/Banner-L.png" media="(min-width: 1024px)">
<!-- 平板端加载中图 -->
<source srcset="./images/Banner-M.png" media="(min-width: 640px)">
<!-- 默认(手机端)加载小图 -->
<img src="./images/Banner-S.png" alt="banner">
</picture>
💡 代码解释
<picture> 元素的工作原理:
html
<picture>
<!-- 1. 浏览器从上到下依次检查 -->
<source srcset="大图.png" media="(min-width: 1024px)">
<!-- 如果屏幕 >= 1024px,加载大图,后面不再检查 -->
<source srcset="中图.png" media="(min-width: 640px)">
<!-- 如果屏幕 >= 640px(但 < 1024px),加载中图 -->
<img src="小图.png" alt="...">
<!-- 如果都不满足(<640px),加载默认小图 -->
<!-- img 标签是必须的,作为后备方案 -->
</picture>
核心优势:
- ✅ 按需加载:只下载符合条件的那一张图片
- ✅ 节省流量:手机端不会下载桌面大图
- ✅ 性能优化:浏览器原生支持,无需 JavaScript
适用场景:
- 内容图片(文章配图、产品图)
- 艺术方向调整(不同屏幕显示不同裁剪的图片)
方案四:srcset 属性
html
<!-- 使用 srcset 和宽度描述符,浏览器自动选择合适图片 -->
<img srcset="./images/Banner-S.png 640w,
./images/Banner-M.png 1024w,
./images/Banner-L.png 1440w"
src="./images/Banner-L.png"
alt="响应式 Banner">
💡 代码解释
srcset 属性的工作原理:
html
<img srcset="图片1.png 640w, ← 宽度640px的图片
图片2.png 1024w, ← 宽度1024px的图片
图片3.png 1440w" ← 宽度1440px的图片
src="图片3.png"> ← 后备方案(旧浏览器)
宽度描述符(w)的含义:
640w表示图片的实际宽度是 640 像素- 浏览器根据设备屏幕宽度、DPR(设备像素比)等因素,自动选择最合适的图片
示例场景:
用户屏幕宽度 = 375px (iPhone)
→ 浏览器选择 640w 的图片(最接近且不小于需要的尺寸)
用户屏幕宽度 = 768px (iPad)
→ 浏览器选择 1024w 的图片
用户屏幕宽度 = 1920px (桌面)
→ 浏览器选择 1440w 的图片
对比 <picture>:
<picture>:完全控制,适合需要精确断点的场景srcset:浏览器智能选择,更简洁但控制力弱
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| CSS 背景图切换 | 简单,只需 CSS | 语义化差 | 装饰性背景图 |
| display 切换 | 直观 | 所有图片都会下载,浪费流量 | 不推荐 |
<picture> |
语义化好,灵活 | 代码略多 | 内容图片,艺术方向调整 |
srcset |
简洁,浏览器智能选择 | 控制精度低于 picture | 同一图片不同分辨率 |
1.6 经典响应式布局实战
基于课堂案例,以下是完整可运行的响应式电商首页布局:
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>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; font-family: Arial, sans-serif; }
a { text-decoration: none; }
ul { list-style: none; }
img { display: block; max-width: 100%; }
body { background: #f5f5f5; }
/* ===== 头部 ===== */
.page-header {
position: sticky;
top: 0;
z-index: 100;
display: flex;
align-items: center;
height: 56px;
padding: 0 16px;
background: #ff6b35;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
}
.logo {
flex-shrink: 0;
font-size: 20px;
font-weight: bold;
color: white;
margin-right: 16px;
}
.search-box {
display: flex;
flex: 1;
max-width: 400px;
margin: 0 auto;
}
.search-box input {
flex: 1;
height: 36px;
padding: 0 12px;
border: none;
border-radius: 18px 0 0 18px;
outline: none;
font-size: 14px;
}
.search-box button {
width: 60px;
height: 36px;
background: #e55b2d;
color: white;
border: none;
border-radius: 0 18px 18px 0;
cursor: pointer;
font-size: 13px;
}
/* 汉堡菜单 */
.menu-btn {
display: flex;
flex-direction: column;
justify-content: center;
gap: 5px;
width: 36px;
height: 36px;
cursor: pointer;
margin-left: 8px;
}
.menu-btn span {
display: block;
height: 2px;
background: white;
border-radius: 1px;
}
/* 手机端下拉导航 */
.nav-drawer {
position: absolute;
top: 56px;
left: 0;
right: 0;
background: #ff6b35;
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease;
}
.nav-drawer.open { max-height: 240px; }
.nav-drawer a {
display: block;
padding: 14px 24px;
color: rgba(255,255,255,0.9);
font-size: 15px;
border-top: 1px solid rgba(255,255,255,0.15);
}
.nav-drawer a:hover { background: rgba(255,255,255,0.1); }
/* ===== 平板端:横排导航 ===== */
@media (min-width: 640px) {
.menu-btn { display: none; }
.nav-drawer {
position: static;
max-height: none;
overflow: visible;
display: flex;
background: transparent;
}
.nav-drawer a {
padding: 0 16px;
border: none;
line-height: 56px;
color: rgba(255,255,255,0.85);
font-size: 14px;
white-space: nowrap;
}
.nav-drawer a:hover {
background: rgba(255,255,255,0.15);
color: white;
}
}
/* ===== 桌面端 ===== */
@media (min-width: 1024px) {
.page-header { padding: 0 40px; }
}
/* ===== Banner ===== */
.banner {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
height: 200px;
display: flex;
align-items: center;
justify-content: center;
color: white;
text-align: center;
}
.banner h2 { font-size: 28px; }
.banner p { margin-top: 8px; opacity: 0.85; }
@media (min-width: 640px) { .banner { height: 280px; } .banner h2 { font-size: 36px; } }
@media (min-width: 1024px) { .banner { height: 360px; } .banner h2 { font-size: 48px; } }
/* ===== 商品网格 ===== */
.products {
max-width: 1200px;
margin: 20px auto;
padding: 0 16px;
}
.section-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 16px;
padding-left: 12px;
border-left: 4px solid #ff6b35;
color: #333;
}
.product-grid {
display: grid;
grid-template-columns: repeat(2, 1fr); /* 手机端:2列 */
gap: 12px;
}
@media (min-width: 640px) {
.product-grid { grid-template-columns: repeat(3, 1fr); gap: 16px; }
}
@media (min-width: 1024px) {
.product-grid { grid-template-columns: repeat(4, 1fr); gap: 20px; }
}
.product-card {
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 1px 4px rgba(0,0,0,0.08);
transition: transform 0.2s, box-shadow 0.2s;
}
.product-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}
.product-img {
width: 100%;
aspect-ratio: 1;
object-fit: cover;
background: linear-gradient(135deg, #f5f7fa, #c3cfe2);
display: flex;
align-items: center;
justify-content: center;
font-size: 48px;
}
.product-info { padding: 12px; }
.product-name { font-size: 14px; color: #333; margin-bottom: 8px; line-height: 1.4; }
.product-price { font-size: 18px; font-weight: bold; color: #ff6b35; }
.product-price span { font-size: 13px; color: #999; margin-left: 6px; text-decoration: line-through; }
@media (min-width: 640px) {
.product-name { font-size: 15px; }
}
</style>
</head>
<body>
<header class="page-header" style="position: relative;">
<div class="logo">🛒 乐购</div>
<form class="search-box">
<input type="text" placeholder="搜索商品...">
<button type="button">搜索</button>
</form>
<div class="menu-btn" onclick="document.querySelector('.nav-drawer').classList.toggle('open')">
<span></span><span></span><span></span>
</div>
<nav class="nav-drawer">
<a href="#">首页</a>
<a href="#">分类</a>
<a href="#">特惠</a>
<a href="#">我的</a>
</nav>
</header>
<div class="banner">
<div>
<h2>响应式布局演示</h2>
<p>拖动窗口查看布局变化效果</p>
</div>
</div>
<div class="products">
<h3 class="section-title">热门商品</h3>
<div class="product-grid">
<div class="product-card">
<div class="product-img">📱</div>
<div class="product-info">
<div class="product-name">智能手机 Pro Max 128G 全面屏</div>
<div class="product-price">¥2,999 <span>¥3,999</span></div>
</div>
</div>
<div class="product-card">
<div class="product-img">💻</div>
<div class="product-info">
<div class="product-name">轻薄笔记本电脑 16寸 高性能</div>
<div class="product-price">¥5,499 <span>¥6,999</span></div>
</div>
</div>
<div class="product-card">
<div class="product-img">🎧</div>
<div class="product-info">
<div class="product-name">降噪蓝牙耳机 Hi-Fi 音质</div>
<div class="product-price">¥399 <span>¥599</span></div>
</div>
</div>
<div class="product-card">
<div class="product-img">⌚</div>
<div class="product-info">
<div class="product-name">智能手表 健康监测 多功能</div>
<div class="product-price">¥899 <span>¥1,299</span></div>
</div>
</div>
<div class="product-card">
<div class="product-img">📷</div>
<div class="product-info">
<div class="product-name">数码相机 4K超清 防抖镜头</div>
<div class="product-price">¥3,200 <span>¥4,100</span></div>
</div>
</div>
<div class="product-card">
<div class="product-img">🖥️</div>
<div class="product-info">
<div class="product-name">27寸 4K 显示器 IPS 广色域</div>
<div class="product-price">¥1,899 <span>¥2,499</span></div>
</div>
</div>
<div class="product-card">
<div class="product-img">🎮</div>
<div class="product-info">
<div class="product-name">游戏手柄 无线连接 震动反馈</div>
<div class="product-price">¥199 <span>¥299</span></div>
</div>
</div>
<div class="product-card">
<div class="product-img">🔋</div>
<div class="product-info">
<div class="product-name">快充充电宝 20000mAh 65W</div>
<div class="product-price">¥149 <span>¥249</span></div>
</div>
</div>
</div>
</div>
</body>
</html>

💡 代码解释:响应式电商页面综合解析
这个示例综合运用了多种响应式技术,是实际项目中的典型布局模式。
一、CSS Grid 响应式网格(核心技术)
css
/* 手机端:2列布局 */
.product-grid {
display: grid;
grid-template-columns: repeat(2, 1fr); /* 2个等宽列 */
gap: 12px; /* 列间距12px */
}
/* 平板端:3列布局 */
@media (min-width: 640px) {
.product-grid {
grid-template-columns: repeat(3, 1fr); /* 自动变成3列 */
gap: 16px; /* 增加间距 */
}
}
/* 桌面端:4列布局 */
@media (min-width: 1024px) {
.product-grid {
grid-template-columns: repeat(4, 1fr); /* 自动变成4列 */
gap: 20px;
}
}
Grid 自动换行原理:
- Grid 项目(
.product-card)会自动填充列 - 当一行放满2/3/4个后,自动换到下一行
- 无需手动计算宽度百分比
二、Sticky Header 粘性头部
css
.page-header {
position: sticky; /* 粘性定位 */
top: 0; /* 距离顶部0px时开始粘住 */
z-index: 100; /* 确保在其他内容上方 */
}
效果: 向下滚动时,头部会固定在顶部,不会消失(类似淘宝/京东的顶部导航栏)
三、渐变背景与悬停效果
css
/* 渐变背景 Banner */
.banner {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
/* 从左上到右下,蓝紫渐变 */
}
/* 商品卡片悬停效果 */
.product-card {
transition: transform 0.2s, box-shadow 0.2s; /* 平滑过渡 */
}
.product-card:hover {
transform: translateY(-4px); /* 向上移动4px */
box-shadow: 0 8px 24px rgba(0,0,0,0.12); /* 阴影加深 */
}
四、响应式字体与高度
css
.banner h2 { font-size: 28px; } /* 手机端 */
.banner { height: 200px; }
@media (min-width: 640px) {
.banner h2 { font-size: 36px; } /* 平板端:字体变大 */
.banner { height: 280px; } /* 高度增加 */
}
@media (min-width: 1024px) {
.banner h2 { font-size: 48px; } /* 桌面端:字体更大 */
.banner { height: 360px; } /* 高度再增加 */
}
五、aspect-ratio 纵横比(CSS 新特性)
css
.product-img {
aspect-ratio: 1; /* 1:1 纵横比(正方形)*/
/* 等价于:width: 100%; padding-top: 100%; */
}
好处: 图片容器始终保持正方形,即使宽度自适应
六、JavaScript 交互
javascript
onclick="document.querySelector('.nav-drawer').classList.toggle('open')"
// 1. 点击汉堡菜单
// 2. 找到 .nav-drawer 元素
// 3. 切换 open 类(有则删除,无则添加)
// 4. CSS 中的 .nav-drawer.open { max-height: 240px; } 生效
响应式效果总结:
| 屏幕尺寸 | 导航 | Banner高度 | 字体大小 | 商品列数 | 间距 |
|---|---|---|---|---|---|
| 📱 手机 (<640px) | 汉堡菜单 | 200px | 28px | 2列 | 12px |
| 📲 平板 (640-1023px) | 横排 | 280px | 36px | 3列 | 16px |
| 🖥️ 桌面 (≥1024px) | 横排宽松 | 360px | 48px | 4列 | 20px |
真实应用案例:
- 小米官网:类似的响应式网格布局
- 京东/淘宝:手机2列,平板3列,桌面5-6列
- Bootstrap 5 :
container+row+col-*栅格系统
实际网站参考:
二、BFC 块级格式上下文
2.1 官方定义解读
名词解释:BFC(Block Formatting Context)
BFC(块级格式上下文) 是 CSS 视觉渲染中的一个重要概念,它是一个独立的渲染区域,规定了内部块级盒子如何布局,且这个区域内的布局不影响外部,外部也不影响内部。
可以把 BFC 理解为一个隔离的"沙盒容器":
┌─────────────────────────────────┐
│ BFC 容器(隔离区域) │
│ ┌──────────┐ ┌──────────┐ │
│ │ Box A │ │ Box B │ │
│ └──────────┘ └──────────┘ │
│ │
│ 内部元素之间相互影响, │
│ 但不影响 BFC 外部 │
└─────────────────────────────────┘
W3C 官方定义(英文)
Floats, absolutely positioned elements, block containers (such as inline-blocks, table-cells, and table-captions) that are not block boxes, and block boxes with 'overflow' other than 'visible' establish new block formatting contexts for their contents.
------ W3C CSS2.2 规范
MDN 定义(英文)
A block formatting context is a part of a visual CSS rendering of a web page. It's the region in which the layout of block boxes occurs and in which floats interact with other elements.
------ MDN Web Docs
中文总结: BFC 是页面上的一块独立区域,区域内元素按块级格式化规则进行布局,浮动元素也在此区域内与其他元素交互。
2.2 BFC 的触发条件
以下 CSS 属性/值会创建新的 BFC:
创建 BFC 的方式
根元素 html
float 不为 none
position: absolute 或 fixed
display: inline-block
display: table / table-cell / table-caption
overflow 不为 visible
display: flex / grid 的直接子项
display: flow-root ⭐推荐
column-span: all
overflow: hidden
overflow: scroll
overflow: auto
专门为创建BFC设计,无副作用
各触发方式的副作用对比
| 创建 BFC 的方式 | 常用场景 | 副作用 |
|---|---|---|
overflow: hidden |
清除浮动 | 会裁剪超出内容 |
overflow: scroll |
可滚动区域 | 始终显示滚动条 |
float: left/right |
浮动布局 | 元素脱离文档流 |
display: inline-block |
行内块 | 影响元素显示方式 |
display: flow-root |
纯粹创建 BFC | 无副作用,最推荐 ⭐ |
position: absolute/fixed |
定位布局 | 元素脱离文档流 |
2.3 BFC 解决的经典问题
问题一:清除子元素浮动的影响(高度塌陷)
问题描述: 当子元素全部浮动时,父元素的高度变为 0,导致布局混乱。
子元素2(float) 子元素1(float) 父元素(无BFC) 子元素2(float) 子元素1(float) 父元素(无BFC) 我的高度从哪里来? 我浮动了,不参与高度计算 我也浮动了,不参与高度计算 高度 = 0 😱 高度塌陷!
解决方案(来自课堂案例):
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BFC - 清除浮动</title>
<style>
/* 对比演示 */
.demo { display: flex; gap: 40px; padding: 20px; font-family: Arial; }
.case { flex: 1; }
.case h3 { margin-bottom: 12px; color: #333; }
/* 问题:父元素高度塌陷 */
.wrapper-bad {
width: 300px;
border: 3px dashed #e74c3c;
/* 没有 BFC,高度塌陷 */
}
/* 解决:给父元素创建 BFC */
.wrapper-good {
width: 300px;
border: 3px dashed #27ae60;
overflow: hidden; /* 方法1:overflow: hidden 创建 BFC */
/* display: flow-root; */ /* 方法2(更推荐,无副作用)*/
}
.float-box {
float: left;
width: 80px;
height: 80px;
margin: 10px;
background: #3498db;
color: white;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
font-size: 13px;
}
.clearfix { clear: both; }
.label {
clear: both;
padding: 6px 10px;
font-size: 12px;
border-radius: 4px;
margin-top: 4px;
}
.label-bad { background: #ffeaea; color: #e74c3c; }
.label-good { background: #eafaf1; color: #27ae60; }
.after-box {
margin-top: 10px;
padding: 10px;
background: #f9e79f;
border: 2px solid #f1c40f;
border-radius: 4px;
}
</style>
</head>
<body>
<div class="demo">
<div class="case">
<h3>❌ 问题:高度塌陷</h3>
<div class="wrapper-bad">
<div class="float-box">浮动1</div>
<div class="float-box">浮动2</div>
<div class="float-box">浮动3</div>
</div>
<div class="label label-bad">父元素高度为0,红色虚线边框几乎不可见!</div>
<div class="after-box">父元素之后的元素被浮动影响 😱</div>
</div>
<div class="case">
<h3>✅ 解决:overflow: hidden 创建 BFC</h3>
<div class="wrapper-good">
<div class="float-box">浮动1</div>
<div class="float-box">浮动2</div>
<div class="float-box">浮动3</div>
</div>
<div class="label label-good">父元素正确包裹了浮动子元素 ✅</div>
<div class="after-box">父元素之后的元素不受影响 ✅</div>
</div>
</div>
<div style="padding: 20px; font-family: Arial;">
<h3>最佳实践:display: flow-root</h3>
<pre style="background: #f8f9fa; padding: 16px; border-radius: 6px; font-size: 14px;">
.wrapper {
display: flow-root; /* 专门为创建BFC设计,无任何副作用 ⭐ */
}</pre>
</div>
</body>
</html>

💡 代码解释:BFC 清除浮动原理
一、高度塌陷的根本原因
正常文档流:父元素通过子元素的高度来计算自己的高度
┌─────────────────┐
│ 父元素 │
│ ┌─────┐ ┌─────┐│
│ │子1 │ │子2 ││ → 父元素高度 = 子元素高度总和
│ └─────┘ └─────┘│
└─────────────────┘
浮动脱离文档流:浮动元素不参与父元素高度计算
┌─────────────────┐
│ 父元素(高度=0)│
└─────────────────┘
┌─────┐ ┌─────┐
│浮动1│ │浮动2│ → 浮动元素脱离文档流,父元素高度塌陷
└─────┘ └─────┘
二、为什么 BFC 能解决高度塌陷?
根据 W3C 规范,BFC 的布局规则之一是:
"计算 BFC 的高度时,浮动元素也参与计算"
css
.wrapper {
overflow: hidden; /* 创建 BFC */
}
/* 或者更推荐: */
.wrapper {
display: flow-root; /* 专门创建 BFC,无副作用 */
}
效果对比:
无 BFC:
┌──────────────┐← 父元素(红色虚线,高度≈0)
└──────────────┘
[浮动1][浮动2][浮动3] ← 脱离文档流
有 BFC:
┌──────────────────────┐
│ 父元素(绿色边框) │← 父元素正确包裹浮动子元素
│ [浮动1][浮动2] │
│ [浮动3] │
└──────────────────────┘
三、不同解决方案对比
| 方案 | CSS 代码 | 优点 | 缺点 |
|---|---|---|---|
overflow: hidden |
overflow: hidden; |
兼容性好 | 超出内容会被裁剪 |
overflow: auto |
overflow: auto; |
内容多时自动滚动 | 可能出现滚动条 |
display: flow-root |
display: flow-root; |
无副作用 ⭐ | IE 不支持(现代浏览器推荐) |
| 伪元素清除浮动 | .clearfix::after { clear: both; } |
传统方案 | 代码较多 |
四、代码中的关键技术点
css
/* 关键1:float 导致脱离文档流 */
.float-box {
float: left; /* 脱离文档流,不占据父元素高度 */
}
/* 关键2:对比演示 */
.wrapper-bad {
/* 没有创建 BFC,高度塌陷 */
}
.wrapper-good {
overflow: hidden; /* 创建 BFC,包裹浮动子元素 */
}
/* 关键3:视觉提示 */
border: 3px dashed #e74c3c; /* 红色虚线边框,塌陷时几乎看不见 */
border: 3px dashed #27ae60; /* 绿色虚线边框,正常高度很明显 */
五、真实应用场景
- 早期浮动布局 :2010年前网页常用
float做多列布局,必须清除浮动 - 图文混排:文章中左右浮动的图片,需要父容器包裹
- 卡片布局:多个浮动卡片在容器内,容器需要撑开高度
- 现代替代方案:Flexbox/Grid 已经取代浮动布局,但清除浮动仍是面试常考点
问题二:解决外边距塌陷(Margin Collapsing)
名词解释: 外边距塌陷(Margin Collapsing)是指相邻块级元素之间的 margin 会发生合并,取两者中较大的值,而不是相加。这在父子元素之间也会发生。
外边距塌陷场景
兄弟元素塌陷
父子元素塌陷
上元素 margin-bottom: 30px
下元素 margin-top: 20px
实际间距 = max(30,20) = 30px,而非50px
父元素无边框/padding/BFC
第一个子元素有 margin-top
子元素的 margin-top 穿透到父元素外!
解决方案(来自课堂案例):
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BFC - 外边距塌陷</title>
<style>
body { font-family: Arial; padding: 20px; }
.demo { display: flex; gap: 60px; }
.case h3 { color: #333; margin-bottom: 8px; }
/* 场景一:父子外边距塌陷 */
/* 问题:子元素的 margin-top 穿透到父元素外 */
.parent-bad {
background: #e74c3c;
width: 200px;
/* 没有 BFC */
}
/* 解决:给父元素创建 BFC */
.parent-good {
background: #27ae60;
width: 200px;
overflow: hidden; /* 创建 BFC */
}
.child {
margin: 30px;
padding: 20px;
background: rgba(255,255,255,0.7);
border-radius: 4px;
font-size: 14px;
text-align: center;
}
.ground {
height: 20px;
background: #3498db;
margin-bottom: 8px;
}
.note {
font-size: 12px;
color: #666;
margin-top: 6px;
}
/* 场景二:兄弟外边距塌陷 */
.sib-container {
width: 200px;
}
.sib-a {
height: 50px;
background: #9b59b6;
margin-bottom: 40px;
display: flex; align-items: center; justify-content: center;
color: white; font-size: 12px;
}
.sib-b {
height: 50px;
background: #e67e22;
margin-top: 20px;
display: flex; align-items: center; justify-content: center;
color: white; font-size: 12px;
}
</style>
</head>
<body>
<div class="demo">
<div class="case">
<h3>❌ 父子外边距塌陷</h3>
<div class="ground"></div>
<div class="parent-bad">
<div class="child">子元素 margin: 30px<br>margin-top 穿透了父元素!</div>
</div>
<p class="note">红色父元素没有与蓝色地面分开<br>因为子元素的 margin-top 塌陷到外面了</p>
</div>
<div class="case">
<h3>✅ overflow: hidden 解决</h3>
<div class="ground"></div>
<div class="parent-good">
<div class="child">子元素 margin: 30px<br>BFC 阻止了 margin 穿透</div>
</div>
<p class="note">绿色父元素与蓝色地面之间无额外间距<br>子元素 margin 被正确限制在父元素内</p>
</div>
<div class="case">
<h3>📌 兄弟外边距塌陷</h3>
<div class="sib-container">
<div class="sib-a">margin-bottom: 40px</div>
<div class="sib-b">margin-top: 20px</div>
</div>
<p class="note">两者间距 = max(40, 20) = 40px<br>而非 40+20=60px<br>(兄弟间的塌陷通常不需要特别解决)</p>
</div>
</div>
</body>
</html>

💡 代码解释:外边距塌陷原理与BFC解决方案
一、什么是外边距塌陷(Margin Collapsing)?
根据 CSS 规范,在正常文档流 中,相邻块级元素的垂直外边距会发生合并,取两者中的较大值,这就是"外边距塌陷"。
二、两种典型的塌陷场景
场景1:父子元素的外边距塌陷
问题:子元素的 margin-top 穿透到父元素外部
视觉效果:
┌─────────────┐ ← 蓝色地面
│ │
├─────────────┤ ← 红色父元素(无 BFC)
│ ┌───────┐ │
│ │子元素 │ │ ← 子元素 margin-top: 30px
│ └───────┘ │
└─────────────┘
实际渲染:
┌─────────────┐ ← 蓝色地面
← 30px 间距出现在这里(本应在父元素内)
┌─────────────┐ ← 红色父元素边框
│ ┌───────┐ │
│ │子元素 │ │ ← 子元素的 margin 穿透了!
│ └───────┘ │
└─────────────┘
场景2:兄弟元素的外边距塌陷
css
.sib-a {
margin-bottom: 40px; /* 下外边距 40px */
}
.sib-b {
margin-top: 20px; /* 上外边距 20px */
}
实际间距 = max(40px, 20px) = 40px,而不是 40+20=60px
[ 元素A ]
← 实际间距 40px(不是 60px)
[ 元素B ]
三、为什么 BFC 能解决父子外边距塌陷?
根据 W3C 规范,BFC 的布局规则之一是:
"BFC 区域内的元素不会与外部元素发生 margin 塌陷"
css
.parent-bad {
background: #e74c3c;
/* 没有 BFC,子元素 margin-top 穿透 */
}
.parent-good {
background: #27ae60;
overflow: hidden; /* 创建 BFC,阻止 margin 穿透 */
}
四、触发 BFC 的常用方法(解决父子塌陷)
| 方法 | CSS 代码 | 副作用 | 推荐度 |
|---|---|---|---|
overflow: hidden |
overflow: hidden; |
超出内容会被裁剪 | ⭐⭐⭐⭐ 常用 |
overflow: auto |
overflow: auto; |
可能出现滚动条 | ⭐⭐⭐ |
display: flow-root |
display: flow-root; |
无副作用 | ⭐⭐⭐⭐⭐ 最佳 |
padding/border |
padding-top: 1px; |
改变盒模型尺寸 | ⭐⭐ 不推荐 |
五、其他解决方案(不使用BFC)
css
/* 方案1:给父元素加 padding/border */
.parent {
padding-top: 1px; /* 阻断 margin 穿透 */
/* 或者:border-top: 1px solid transparent; */
}
/* 方案2:给父元素加伪元素 */
.parent::before {
content: '';
display: table; /* 阻断 margin 穿透 */
}
六、代码中的关键技术点
css
/* 关键1:演示父子塌陷 */
.parent-bad {
background: #e74c3c; /* 红色背景 */
/* 无 BFC,子元素的 margin-top 会穿透到父元素外 */
}
.child {
margin: 30px; /* 上外边距30px 本应在父元素内,但会穿透 */
}
/* 关键2:BFC 解决方案 */
.parent-good {
background: #27ae60; /* 绿色背景 */
overflow: hidden; /* 创建 BFC,margin 不再穿透 */
}
/* 关键3:视觉参考 */
.ground {
height: 20px;
background: #3498db; /* 蓝色地面,用于观察父元素是否紧贴地面 */
}
七、兄弟元素塌陷是否需要解决?
通常不需要解决兄弟元素的塌陷,因为:
- 这是 CSS 规范的预期行为,不是 Bug
- 塌陷后的效果往往是我们想要的(避免间距过大)
- 如果真的需要相加的间距,可以只设置一个元素的 margin
css
/* 不推荐:会塌陷 */
.box1 { margin-bottom: 30px; }
.box2 { margin-top: 30px; }
/* 实际间距只有 30px */
/* 推荐:精确控制 */
.box1 { margin-bottom: 60px; }
.box2 { margin-top: 0; }
/* 实际间距就是 60px */
八、真实应用场景
- 卡片布局 :父容器包含多个卡片,第一个卡片的
margin-top可能穿透 - 文章排版:段落之间的间距、标题与正文的间距
- 栅格系统 :Bootstrap/Tailwind 的
.row容器必须解决子元素的 margin 塌陷 - 模态框/弹窗:内部内容的 margin 不应该影响外部布局
九、调试技巧
css
/* 给元素添加边框,快速查看塌陷位置 */
* {
outline: 1px solid red; /* 临时调试用 */
}
浏览器开发者工具中:
- Computed 面板查看实际的 margin 值
- Layout 面板查看盒模型图示
- Elements 面板悬停时会显示 margin 区域(橙色)
BFC 知识总结
BFC
本质
独立的渲染容器
内外隔离
触发条件
overflow hidden/scroll/auto
float left/right
position absolute/fixed
display inline-block
display flow-root ⭐最佳
解决问题
高度塌陷 清除浮动
父子外边距塌陷
阻止文字环绕浮动元素
最佳实践
display flow-root 无副作用
overflow hidden 最常用
三、JavaScript 完全入门指南
3.1 JavaScript 是什么
名词解释:JavaScript
JavaScript 是一门在 1995 年由 Brendan Eich 在 10 天内为 Netscape 浏览器创建的脚本语言。最初叫 LiveScript ,后更名为 JavaScript(借助 Java 的热度营销,但与 Java 毫无关系)。
JavaScript 是:
JavaScript
动态语言
弱类型语言
解释型语言
基于对象的语言
脚本语言
运行时才确定数据类型
(对比:TypeScript 是静态类型)
数据类型可自动转换
'5' + 3 = '53'(字符串拼接)
边编译边运行,无需提前编译
(对比:C/Java 需要先编译)
基于原型链的面向对象
(对比:Java 基于类)
可嵌入 HTML 中执行
或在 Node.js 中独立运行
语言特性对比表
| 特性 | JavaScript | Python | Java | C++ |
|---|---|---|---|---|
| 类型系统 | 弱类型 | 强类型 | 强类型 | 强类型 |
| 类型检查时机 | 动态(运行时) | 动态(运行时) | 静态(编译时) | 静态(编译时) |
| 执行方式 | 解释型 | 解释型 | 编译+JVM | 编译型 |
| 主要用途 | Web前端/后端 | 数据科学/后端 | 企业后端 | 系统/游戏 |
3.2 浏览器端 JS 组成
浏览器端的 JavaScript 由三个核心部分组成:
浏览器端 JavaScript
ECMAScript (ES)
基础语法规范
BOM
Browser Object Model
浏览器对象模型
DOM
Document Object Model
文档对象模型
变量/数据类型
运算符/流程控制
函数/对象/数组
ES6+ 新特性
let/const/箭头函数/Promise
window 对象
navigator 浏览器信息
location 地址栏
history 历史记录
screen 屏幕信息
document 文档
元素节点操作
事件处理
样式操作
ECMA 组织制定
W3C 规范化
W3C 规范化
名词解释
| 术语 | 全称 | 制定机构 | 功能 |
|---|---|---|---|
| ECMAScript | European Computer Manufacturers Association Script | ECMA 国际 | JS 的核心语法规范 |
| BOM | Browser Object Model | W3C(非正式) | 操作浏览器窗口、地址栏、历史记录 |
| DOM | Document Object Model | W3C | 操作 HTML 文档结构、样式、事件 |
| V8 | --- | Chrome/Node.js 的 JS 引擎,将 JS 编译为机器码 |
3.3 三种使用方式
JavaScript 在 HTML 中有三种引入方式,各有优缺点:
JS 引入方式
行内式 内联脚本
内嵌式 嵌入脚本
外链式 外部脚本
οnclick='js代码'
❌ 耦合度高,不推荐
script 标签内写代码
✅ 小型项目可用
script src='文件路径'
✅✅ 推荐,可缓存复用
方式一:行内式(内联脚本)
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>
<style>
.demo-btn {
padding: 10px 20px;
background: #3498db;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 15px;
margin: 8px;
}
.demo-btn:hover { background: #2980b9; }
.demo-p {
padding: 16px 20px;
width: 400px;
background: #ecf0f1;
border-radius: 6px;
cursor: pointer;
margin: 8px;
font-family: Arial;
}
</style>
</head>
<body>
<h2>行内式(内联脚本)示例</h2>
<hr>
<!-- 单个事件 -->
<button class="demo-btn" onclick="alert('按钮被点击了!')">
单击我
</button>
<!-- 多个事件 -->
<button class="demo-btn"
onclick="alert('鼠标左键单击!')"
oncontextmenu="alert('鼠标右键单击!'); return false;">
左右键都试试
</button>
<!-- 段落点击 -->
<p class="demo-p" onclick="alert('段落被点击!'); alert('行内式可以写多行代码,但不推荐');">
点击这段文字触发事件(行内式JS不推荐在实际项目中使用,维护困难)
</p>
<p style="color: #e74c3c; font-family: Arial; margin: 8px;">
⚠️ 行内式的缺点:HTML 与 JS 代码混在一起,难以维护,不推荐在实际项目中大量使用。
</p>
</body>
</html>

💡 代码解释:行内式(内联脚本)
一、什么是行内式JavaScript?
行内式JavaScript是指将JavaScript代码直接写在HTML元素的事件属性中(如onclick、onmouseover等)。
html
<button onclick="alert('Hello')">点击我</button>
↑
事件处理属性直接写JS代码
二、代码中的关键技术点
html
<!-- 示例1:单个简单语句 -->
<button onclick="alert('按钮被点击了!')">单击我</button>
<!-- onclick 属性值就是JavaScript代码 -->
<!-- 示例2:多个语句用分号分隔 -->
<button onclick="alert('第一句'); alert('第二句')">多语句</button>
<!-- ↑ 分号分隔多条语句 -->
<!-- 示例3:return false 阻止默认行为 -->
<button oncontextmenu="alert('右键'); return false;">
<!-- return false 阻止浏览器默认的右键菜单 -->
</button>
<!-- 示例4:段落元素也可以绑定事件 -->
<p onclick="alert('段落被点击')">点击文字</p>
三、行内式的优缺点分析
优点:
- ✅ 简单快速:适合快速原型、学习演示
- ✅ 直观可见:HTML代码中就能看到事件处理逻辑
缺点(致命问题):
- ❌ HTML与JS耦合:代码混在一起,难以维护
- ❌ 复用性差:相同逻辑需要在每个元素上重复写
- ❌ 可读性差:复杂逻辑会导致HTML属性值过长
- ❌ CSP限制:现代网站的内容安全策略(CSP)可能禁止行内脚本
- ❌ 性能问题:浏览器需要为每个行内事件创建新的函数作用域
四、对比更好的替代方案
html
<!-- ❌ 不推荐:行内式 -->
<button onclick="alert('Hello')">点击</button>
<!-- ✅ 推荐:外部脚本 + addEventListener -->
<button id="myBtn">点击</button>
<script>
document.getElementById('myBtn').addEventListener('click', function() {
alert('Hello');
});
</script>
五、什么时候可以用行内式?
- ✅ 学习阶段的快速演示
- ✅ 简单的页面原型
- ✅ 临时测试代码
❌ 实际项目中应避免使用!
六、常见的事件属性
| 事件属性 | 触发时机 | 示例 |
|---|---|---|
onclick |
鼠标左键点击 | <button onclick="..."> |
ondblclick |
鼠标双击 | <div ondblclick="..."> |
onmouseover |
鼠标悬停 | <img onmouseover="..."> |
onmouseout |
鼠标移出 | <div onmouseout="..."> |
oncontextmenu |
鼠标右键 | <button oncontextmenu="..."> |
onload |
页面/图片加载完成 | <body onload="..."> |
onchange |
表单值改变 | <input onchange="..."> |
七、真实场景对比
早期网站(2000年代):
html
<!-- 早期大量使用行内式 -->
<a href="#" onclick="return confirm('确定删除?')">删除</a>
现代网站(推荐):
html
<a href="#" class="delete-btn" data-id="123">删除</a>
<script src="app.js"></script> <!-- 所有逻辑在外部文件 -->
方式二:内嵌式(嵌入脚本)
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>
<style>
body { font-family: Arial; padding: 20px; }
button {
padding: 10px 20px;
background: #2ecc71;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 15px;
}
button:hover { background: #27ae60; }
#output {
margin-top: 16px;
padding: 12px;
background: #f8f9fa;
border-left: 4px solid #2ecc71;
border-radius: 4px;
min-height: 40px;
}
</style>
</head>
<body>
<h2>内嵌式(嵌入脚本)示例</h2>
<hr>
<button id="btn">点击我</button>
<div id="output">点击按钮后这里会显示内容...</div>
<!-- script 标签建议放在 body 末尾,确保 DOM 已加载 -->
<script>
var btn = document.getElementById('btn');
var output = document.getElementById('output');
var count = 0;
btn.onclick = function() {
count++;
output.innerHTML = '你已经点击了 <strong>' + count + '</strong> 次!';
output.style.color = count > 5 ? '#e74c3c' : '#333';
};
</script>
</body>
</html>

💡 代码解释:内嵌式(嵌入脚本)
一、什么是内嵌式JavaScript?
内嵌式JavaScript是指将JavaScript代码写在HTML文档的<script>标签内部,代码直接嵌入HTML文件中。
html
<script>
// JavaScript代码写在这里
var a = 100;
alert(a);
</script>
二、代码中的关键技术点
html
<!-- 关键点1:script 标签的位置 -->
<!DOCTYPE html>
<html>
<head>
<script>
// ❌ 在head中的script,此时DOM还没加载
// var btn = document.getElementById('btn'); // 报错:btn为null
</script>
</head>
<body>
<button id="btn">按钮</button>
<!-- ✅ 推荐:script放在body末尾,确保DOM已加载 -->
<script>
var btn = document.getElementById('btn'); // ✅ 能正确获取到按钮
</script>
</body>
</html>

关键点2:getElementById 获取DOM元素
javascript
var btn = document.getElementById('btn');
// ↑ ↑
// document对象 元素的id属性值
// 返回:HTML元素对象,如果找不到则返回null
关键点3:事件绑定(推荐方式)
javascript
// 方式1:通过 onclick 属性(类似行内式,但在JS中)
btn.onclick = function() {
count++;
output.innerHTML = '点击了 ' + count + ' 次';
};
// 方式2:addEventListener(最推荐)
btn.addEventListener('click', function() {
count++;
output.innerHTML = '点击了 ' + count + ' 次';
});
关键点4:innerHTML vs textContent
javascript
// innerHTML:可以插入HTML标签
output.innerHTML = '点击了 <strong>' + count + '</strong> 次';
// ↑ HTML标签会被解析
// textContent:纯文本,标签会被转义
output.textContent = '点击了 <strong>5</strong> 次';
// 显示结果:点击了 <strong>5</strong> 次(标签会显示出来)
三、代码执行流程分析
javascript
var btn = document.getElementById('btn');
var output = document.getElementById('output');
var count = 0; // 计数器变量
btn.onclick = function() {
count++; // 每次点击,count加1
output.innerHTML = '你已经点击了 <strong>' + count + '</strong> 次!';
// 三元运算符:如果count > 5,颜色变红,否则黑色
output.style.color = count > 5 ? '#e74c3c' : '#333';
// ↑ 条件 ↑ true值 ↑ false值
};
四、内嵌式的优缺点
优点:
- ✅ 比行内式更易维护:代码集中在
<script>标签内 - ✅ 可以写复杂逻辑:不受HTML属性值长度限制
- ✅ 适合页面专属逻辑:不需要额外HTTP请求加载文件
缺点:
- ❌ 不能跨页面复用:每个页面都要复制代码
- ❌ 不能利用浏览器缓存:每次加载HTML都会重新解析JS代码
- ❌ 代码混在HTML中:大型项目中难以管理
五、内嵌式 vs 行内式对比
| 对比维度 | 行内式 | 内嵌式 |
|---|---|---|
| 代码位置 | HTML元素属性中 | <script>标签内 |
| 代码结构 | 分散在各个元素 | 集中在一处 |
| 代码复杂度 | 只适合简单语句 | 可以写复杂逻辑 |
| 维护性 | 差 | 一般 |
| 适用场景 | 快速演示 | 页面专属逻辑 |
六、最佳实践建议
html
<!-- ✅ 推荐:外部脚本 -->
<script src="app.js"></script>
<!-- ✅ 可以:内嵌式(页面专属逻辑,不会复用) -->
<script>
// 仅在当前页面使用的初始化代码
window.onload = function() {
initPageSpecificLogic();
};
</script>
<!-- ❌ 不推荐:行内式 -->
<button onclick="alert('...')">点击</button>
七、script 标签的位置最佳实践
html
<!DOCTYPE html>
<html>
<head>
<title>页面标题</title>
<!-- ❌ 不推荐:head中的script会阻塞页面渲染 -->
<!-- <script src="large-script.js"></script> -->
</head>
<body>
<div id="content">页面内容...</div>
<!-- ✅ 推荐:放在body末尾 -->
<!-- 1. DOM已加载完成,可以安全操作元素 -->
<!-- 2. 不会阻塞页面渲染 -->
<script src="app.js"></script>
</body>
</html>

八、现代替代方案
html
<!-- ✅ 最推荐:defer 属性(异步加载,DOMContentLoaded前执行) -->
<script src="app.js" defer></script>
<!-- ✅ async 属性(异步加载,立即执行) -->
<script src="analytics.js" async></script>
<!-- ✅ 模块化(ES6 Modules) -->
<script type="module" src="main.js"></script>
方式三:外链式(外部脚本)
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>
<style>
body { font-family: Arial; padding: 20px; }
button {
padding: 10px 20px;
background: #9b59b6;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
}
</style>
</head>
<body>
<h2>外链式(外部脚本)示例</h2>
<hr>
<p>JS 代码写在独立的 .js 文件中,通过 src 属性引入。</p>
<button id="btn">点击我</button>
<!-- 外部脚本:引用 index.js 文件 -->
<!-- 注意:有 src 属性的 script 标签内部的代码会被忽略 -->
<script src="./index.js"></script>
</body>
</html>

对应的 index.js 文件(来自课堂案例):
js
// 来自课堂案例:03-JS在HTML中使用方式/index.js
alert('Hello,欢迎学习 JavaScript!');
var btn = document.querySelector('button');
btn.onclick = function() {
alert('外部脚本被执行了!');
};
💡 代码解释:外链式(外部脚本)
一、什么是外链式JavaScript?
外链式JavaScript是指将JavaScript代码写在独立的.js文件中,然后通过<script src="路径">标签引入到HTML文档中。
html
<!-- HTML文件 -->
<script src="./index.js"></script>
↑
引用外部JS文件
二、代码中的关键技术点
HTML部分:
html
<!-- 关键1:src 属性指定文件路径 -->
<script src="./index.js"></script>
<!-- ↑ 相对路径:当前目录下的index.js -->
<!-- 关键2:有src属性时,标签内的代码会被忽略 -->
<script src="./app.js">
alert('这行代码不会执行!'); // ❌ 被忽略
</script>
<!-- 关键3:如果需要同时引入外部文件和写内联代码,用两个script标签 -->
<script src="./app.js"></script>
<script>
alert('这行代码会执行!'); // ✅ 正常执行
</script>
JavaScript部分(index.js):
javascript
// 代码1:页面加载时立即执行
alert('Hello,欢迎学习 JavaScript!');
// 代码2:querySelector 选择元素(比 getElementById 更灵活)
var btn = document.querySelector('button');
// ↑ CSS选择器语法,选择第一个button元素
// 代码3:绑定点击事件
btn.onclick = function() {
alert('外部脚本被执行了!');
};
三、querySelector vs getElementById
| 方法 | 语法 | 选择能力 | 性能 |
|---|---|---|---|
getElementById |
document.getElementById('btn') |
只能通过id选择 | 快 |
querySelector |
document.querySelector('button') |
CSS选择器(更强大) | 稍慢 |
querySelectorAll |
document.querySelectorAll('.btn') |
选择所有匹配元素 | 稍慢 |
javascript
// getElementById:只能通过id
var elem1 = document.getElementById('myBtn');
// querySelector:可以用CSS选择器
var elem2 = document.querySelector('#myBtn'); // id选择器
var elem3 = document.querySelector('.btn'); // class选择器
var elem4 = document.querySelector('button'); // 标签选择器
var elem5 = document.querySelector('[type="submit"]'); // 属性选择器
四、外链式的文件路径规则
html
<!-- 相对路径 -->
<script src="./index.js"></script> <!-- 当前目录 -->
<script src="../utils.js"></script> <!-- 上级目录 -->
<script src="./js/app.js"></script> <!-- 子目录 -->
<!-- 绝对路径 -->
<script src="/static/js/app.js"></script> <!-- 从网站根目录 -->
<!-- 外部CDN -->
<script src="https://cdn.example.com/jquery.min.js"></script>
五、外链式的优势(为什么推荐)
| 优势 | 说明 | 实际效果 |
|---|---|---|
| ✅ 代码复用 | 多个HTML页面可以共享同一个JS文件 | 100个页面只需1个app.js |
| ✅ 浏览器缓存 | 浏览器会缓存.js文件,第二次访问无需重新下载 | 加载速度提升80%+ |
| ✅ 代码组织 | HTML和JS分离,结构清晰 | 易于维护和团队协作 |
| ✅ 便于调试 | 浏览器开发工具可以直接显示源文件 | 断点调试更方便 |
| ✅ 版本管理 | 可以对JS文件进行版本控制(Git等) | 代码历史可追溯 |
六、外链式加载时机(重要!)
html
<!DOCTYPE html>
<html>
<head>
<!-- ❌ 方式1:head中加载(阻塞渲染) -->
<script src="app.js"></script>
<!-- 浏览器遇到此标签会:
1. 暂停HTML解析
2. 下载app.js
3. 执行app.js
4. 继续解析HTML
→ 页面显示缓慢! -->
</head>
<body>
<div id="content">内容...</div>
<!-- ✅ 方式2:body末尾加载(推荐) -->
<script src="app.js"></script>
<!-- 优点:
1. 页面内容已渲染,用户能看到东西
2. DOM已加载完成,JS可以安全操作元素 -->
</body>
</html>
七、现代最佳实践:defer 和 async
html
<!-- ✅ 最推荐:defer(异步下载,HTML解析完后按顺序执行) -->
<script src="jquery.js" defer></script>
<script src="app.js" defer></script>
<!-- 执行顺序:先jquery.js,后app.js(保证依赖关系) -->
<!-- ✅ async(异步下载,下载完立即执行) -->
<script src="analytics.js" async></script>
<script src="ads.js" async></script>
<!-- 执行顺序:谁先下载完谁先执行(不保证顺序) -->
<!-- 适用于:独立的脚本(统计、广告等)-->
<!-- 三种方式对比 -->
图示:三种加载方式的时间线
普通(无defer/async):
HTML解析 ----[暂停]---- HTML解析继续
下载JS
执行JS
defer:
HTML解析 --------------------------------> 完成
下载JS ----
执行JS(HTML解析完后)
async:
HTML解析 --------------------------------> 完成
下载JS ----
执行JS(下载完立即执行)
八、真实项目中的最佳实践
html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>现代网站结构</title>
<!-- CSS(阻塞渲染,所以放head) -->
<link rel="stylesheet" href="style.css">
<!-- 外部库(defer,按顺序加载) -->
<script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js" defer></script>
<!-- 项目JS(defer,在Vue之后执行) -->
<script src="./app.js" defer></script>
<!-- 独立脚本(async,不依赖其他代码) -->
<script src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID" async></script>
</head>
<body>
<div id="app">{{ message }}</div>
</body>
</html>
九、课堂案例解析
javascript
// index.js(课堂案例)
alert('Hello,欢迎学习 JavaScript!');
// → 页面加载时立即弹出欢迎提示
var btn = document.querySelector('button');
// → 选择页面中的第一个button元素
btn.onclick = function() {
alert('外部脚本被执行了!');
};
// → 给按钮绑定点击事件,点击时弹框
运行流程:
- 浏览器加载HTML,遇到
<script src="./index.js"> - 下载并执行
index.js - 立即执行
alert('Hello,欢迎学习 JavaScript!')弹框 - 用户点击"确定"关闭弹框
- 执行
var btn = document.querySelector('button')获取按钮 - 执行
btn.onclick = ...绑定点击事件 - 用户点击按钮时,触发事件,弹出"外部脚本被执行了!"
十、常见错误与解决
错误1:找不到JS文件(404)
html
<script src="./index.js"></script> <!-- ❌ 路径错误 -->
<!-- 解决:检查文件名、路径是否正确 -->
错误2:获取不到DOM元素
html
<head>
<script src="app.js"></script> <!-- ❌ HTML还没加载 -->
</head>
<body>
<button id="btn">按钮</button>
</body>
<!-- 解决方案1:script放body末尾 -->
<!-- 解决方案2:使用defer属性 -->
<!-- 解决方案3:使用DOMContentLoaded事件 -->
<script>
document.addEventListener('DOMContentLoaded', function() {
var btn = document.getElementById('btn'); // ✅ 此时DOM已加载
});
</script>
错误3:src属性内的代码不执行
html
<script src="app.js">
alert('这行不会执行'); <!-- ❌ 被忽略 -->
</script>
<!-- 解决:拆分成两个script标签 -->
<script src="app.js"></script>
<script>
alert('这行会执行'); <!-- ✅ 正常 -->
</script>
三种方式总结对比
| 方式 | 代码位置 | 维护性 | 缓存 | 适用场景 |
|---|---|---|---|---|
| 行内式 | HTML 属性中 | 差 | 无 | 极简单的交互,不推荐 |
| 内嵌式 | <script> 标签内 |
一般 | 无 | 页面专属的小段代码 |
| 外链式 | 独立 .js 文件 |
好 | 有 | 推荐,几乎所有项目 |
最佳实践: 大型项目应将 JS 代码拆分为多个模块化的
.js文件,通过外链式引入,充分利用浏览器缓存提升性能。
3.4 基本语法规则
注释(Comments)
注释是程序员写给自己或团队看的说明文字,不会被 JavaScript 引擎执行。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JavaScript 注释示例</title>
<!-- HTML 注释:不会被显示在页面上 -->
<style>
/* CSS 注释:不会影响样式 */
body { font-family: Arial; padding: 20px; }
pre {
background: #282c34;
color: #abb2bf;
padding: 20px;
border-radius: 8px;
font-size: 14px;
line-height: 1.6;
}
</style>
</head>
<body>
<h2>JavaScript 注释</h2>
<pre>
// 这是单行注释
// 单行注释从 // 开始,到行尾结束
var price = 99; // 可以放在代码后面,注释该行后面的部分
/*
这是多行注释(块注释)
可以跨越多行
在调试时常用于临时注释掉一段代码
alert(100);
alert(200);
alert(300);
*/
/**
* JSDoc 注释风格(文档注释)
* 用于描述函数、参数、返回值
* @param {string} name - 用户姓名
* @param {number} age - 用户年龄
* @returns {string} 问候语
*/
function greet(name, age) {
return 'Hello, ' + name + '! 你今年 ' + age + ' 岁。';
}
</pre>
<script>
// 控制台查看效果
console.log('单行注释:// 开头');
/*
多行注释不会执行
console.log('这行不会执行');
*/
console.log('多行注释之后继续执行');
</script>
</body>
</html>

💡 代码解释:JavaScript注释
一、三种注释类型对比
| 注释类型 | 语法 | 用途 | 示例 |
|---|---|---|---|
| 单行注释 | // 注释内容 |
简短说明、临时禁用代码 | // 这是注释 |
| 多行注释 | /* 注释内容 */ |
长段说明、注释代码块 | /* 多行<br>内容 */ |
| JSDoc注释 | /** @param ... */ |
函数文档、API说明 | /** @returns {string} */ |
二、单行注释详解
javascript
// 单行注释从 // 开始,到行尾结束
// 用途1:代码说明
var price = 99; // 商品价格(单位:元)
// 用途2:临时禁用代码
// alert('这行代码被注释了,不会执行');
// 用途3:调试标记
console.log('开始执行'); // DEBUG:检查执行流程
三、多行注释详解
javascript
/*
多行注释(块注释)
可以跨越多行
常用于:
1. 长段说明
2. 临时注释掉一大段代码
3. 代码顶部的版权声明
*/
/* 单行也可以用 */
/*
调试时常用:注释掉一段代码
alert(100);
alert(200);
alert(300);
*/
⚠️ 多行注释的陷阱:不能嵌套!
javascript
/*
外层注释开始
/* 内层注释 */ ← 这里会提前结束外层注释!
这行代码会报错,因为它在注释外!
*/
// 正确的做法:用单行注释代替嵌套
/*
外层注释开始
// 内层注释
正常内容
*/
四、JSDoc注释(文档注释)
JSDoc是一种标准化的注释格式,用于生成API文档。
javascript
/**
* 计算两个数的和
* @param {number} a - 第一个数字
* @param {number} b - 第二个数字
* @returns {number} 两数之和
* @example
* add(2, 3); // 返回 5
*/
function add(a, b) {
return a + b;
}
/**
* 用户类
* @class
* @param {string} name - 用户姓名
* @param {number} age - 用户年龄
*/
function User(name, age) {
this.name = name;
this.age = age;
}
常用JSDoc标签:
| 标签 | 说明 | 示例 |
|---|---|---|
@param |
函数参数 | @param {string} name |
@returns |
返回值 | @returns {number} |
@example |
使用示例 | @example add(1, 2) |
@description |
详细描述 | @description 这是一个工具函数 |
@throws |
可能抛出的异常 | @throws {Error} |
@deprecated |
已废弃 | @deprecated 请使用newFunc代替 |
五、注释的最佳实践
✅ 好的注释:解释"为什么",而不是"做什么"
javascript
// ❌ 不好:重复代码内容
var price = 99; // 设置price为99
// ✅ 好:解释意图
var price = 99; // 限时特价,原价199
// ❌ 不好:显而易见的注释
var sum = a + b; // 将a和b相加
// ✅ 好:解释复杂逻辑
var sum = (price * quantity * 0.95) + shippingFee;
// 总价 = (单价 × 数量 × 会员95折) + 运费
✅ 注释的四个原则:
- 代码即文档:优先写易读的代码,减少注释需求
- 注释解释意图:不要重复代码,要解释为什么这样写
- 及时更新注释:代码改了,注释也要改
- 临时注释要清理 :
// TODO、// FIXME要及时处理
六、特殊标记注释(团队协作)
javascript
// TODO: 待完成的任务
// TODO: 增加输入验证逻辑
// FIXME: 已知的Bug,需要修复
// FIXME: 在IE浏览器中此处有兼容性问题
// HACK: 临时的解决方案(不优雅)
// HACK: 此处用setTimeout绕过异步问题,后续需重构
// NOTE: 重要说明
// NOTE: 此处的算法来自XXX论文
// OPTIMIZE: 性能优化点
// OPTIMIZE: 此处循环可以优化为哈希表查找
七、HTML、CSS、JavaScript注释对比
html
<!-- HTML注释:用 <!-- --> -->
<div class="container">
<!-- 这是HTML注释,不会显示在页面上 -->
</div>
<style>
/* CSS注释:用 /* */ */
.container {
width: 100%; /* 宽度100% */
}
</style>
<script>
// JavaScript注释:用 // 或 /* */
var a = 10; // 单行注释
/* 多行注释 */
</script>
八、快捷键(VS Code / Chrome DevTools)
| 操作 | Windows/Linux | Mac |
|---|---|---|
| 单行注释/取消注释 | Ctrl + / |
Cmd + / |
| 多行注释 | Shift + Alt + A |
Shift + Option + A |
| 块注释 | 选中后 Ctrl + / |
选中后 Cmd + / |
九、代码中的实际应用
javascript
// 控制台查看效果
console.log('单行注释:// 开头');
/*
多行注释不会执行
console.log('这行不会执行');
*/
console.log('多行注释之后继续执行');
执行结果:
单行注释:// 开头
多行注释之后继续执行
(注意:多行注释内的console.log('这行不会执行')不会输出)
十、真实项目示例
javascript
/**
* 购物车模块
* @module Cart
* @author 张三
* @since 2024-01-01
*/
/**
* 添加商品到购物车
* @param {Object} product - 商品对象
* @param {string} product.id - 商品ID
* @param {number} product.quantity - 数量
* @returns {boolean} 是否添加成功
*/
function addToCart(product) {
// 检查库存(调用库存API)
if (!checkStock(product.id, product.quantity)) {
console.warn('库存不足'); // 警告:库存不足
return false;
}
// TODO: 增加防重复提交逻辑
/*
注意:此处需要处理并发问题
当多个用户同时购买同一商品时,
可能导致超卖
*/
// 添加到购物车
cart.items.push(product);
return true;
}
指令结束符(语句结束符)
JavaScript 中,一条语句结束的标志有两种:
1. 分号 ;
2. 换行符(自动插入机制 ASI)
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>
</head>
<body>
<script>
// 方式一:使用分号 --- 同一行可以写多条语句
var a = 10; var b = 20; var c = a + b;
// 方式二:换行 --- 换行自动视为语句结束
var x = 100
var y = 200
var z = x + y
// 最佳实践:分号 + 换行(最清晰)
var firstName = 'JavaScript';
var lastName = 'Developer';
var fullName = firstName + ' ' + lastName;
console.log(c); // 30
console.log(z); // 300
console.log(fullName); // JavaScript Developer
// ⚠️ 大小写严格区分!
// Alert(100); // ❌ 报错:Alert 不是函数
alert(100); // ✅ 正确
</script>
</body>
</html>

💡 代码解释:JavaScript语句结束符
一、两种语句结束方式
JavaScript有两种方式表示语句结束:
- 分号
;- 明确的语句结束标记 - 换行符 - 依靠自动分号插入机制(ASI - Automatic Semicolon Insertion)
javascript
// 方式1:分号结束
var a = 10;
// 方式2:换行结束(依靠ASI)
var b = 20
二、自动分号插入机制(ASI)详解
JavaScript引擎会在以下情况自动插入分号:
javascript
// 情况1:遇到换行符
var a = 100
var b = 200 // 引擎自动在100后插入分号
// 实际等价于:
var a = 100;
var b = 200;
// 情况2:遇到 } 闭合大括号
function test() {
return 100
} // 引擎在 return 100 后插入分号
// 情况3:到达文件末尾
var x = 10 // 文件结束,自动插入分号
三、代码演示分析
javascript
// 同一行多条语句:必须用分号分隔
var a = 10; var b = 20; var c = a + b;
// 换行:自动视为语句结束
var x = 100
var y = 200
var z = x + y
// 最佳实践:分号 + 换行(最清晰)
var firstName = 'JavaScript';
var lastName = 'Developer';
var fullName = firstName + ' ' + lastName;
执行结果:
c = 30
z = 300
fullName = "JavaScript Developer"
四、ASI的陷阱(重要!)
陷阱1:return语句换行
javascript
// ❌ 错误:return后换行
function getObject() {
return
{
name: 'JavaScript'
};
}
console.log(getObject()); // 输出:undefined(而不是对象!)
// 原因:ASI在return后插入了分号
function getObject() {
return; // ← ASI在这里插入分号
{
name: 'JavaScript' // 这段代码永远不会执行
};
}
// ✅ 正确写法1:不换行
function getObject() {
return { name: 'JavaScript' };
}
// ✅ 正确写法2:用括号包裹
function getObject() {
return (
{
name: 'JavaScript'
}
);
}
陷阱2:数组访问换行
javascript
// ❌ 错误:换行后访问数组
var arr = [1, 2, 3]
[0].toString() // 报错:Cannot read property 'toString' of undefined
// 原因:ASI没有插入分号,被解析为:
var arr = [1, 2, 3][0].toString()
// ↑ 这里被当作数组访问,arr[3]为undefined
// ✅ 正确写法:加分号
var arr = [1, 2, 3];
[0].toString();
陷阱3:立即执行函数(IIFE)
javascript
// ❌ 错误:连续两个IIFE
var a = 10
(function() {
console.log('IIFE');
})();
// 报错:10 is not a function
// 原因:被解析为
var a = 10(function() { ... })();
// ↑ 试图把10当作函数调用
// ✅ 正确写法1:加分号
var a = 10;
(function() {
console.log('IIFE');
})();
// ✅ 正确写法2:IIFE前加分号
var a = 10
;(function() {
console.log('IIFE');
})();
五、分号 vs 无分号:两大阵营
阵营1:加分号派(传统)
- ✅ 明确、清晰,不依赖ASI
- ✅ 避免ASI陷阱
- ✅ 压缩代码时更安全
- 代表:传统JavaScrip开发者、企业项目
阵营2:无分号派(现代)
- ✅ 代码更简洁
- ✅ 减少视觉干扰
- ✅ 现代构建工具会自动处理
- 代表:Vue.js、React部分项目、现代前端
六、最佳实践建议
| 场景 | 建议 | 原因 |
|---|---|---|
| 初学者 | ✅ 加分号 | 避免踩坑,代码更清晰 |
| 企业项目 | ✅ 加分号 | 团队规范,减少歧义 |
| 个人项目 | ⚖️ 根据喜好 | 保持一致即可 |
| 开源项目 | ✅ 遵循项目规范 | 统一风格 |
七、ESLint配置(代码规范工具)
json
// .eslintrc.json
{
"rules": {
// 要求语句末尾加分号
"semi": ["error", "always"],
// 或者:允许不加分号
"semi": ["error", "never"]
}
}
八、大小写敏感性(Case Sensitivity)
javascript
// JavaScript严格区分大小写!
// ❌ 错误:Alert(首字母大写)
Alert(100); // 报错:Alert is not defined
// ✅ 正确:alert(全小写)
alert(100); // 正常弹框
// 其他示例
var name = 'JavaScript';
var Name = 'Python';
var NAME = 'Java';
// 这是三个不同的变量!
// 函数名也区分大小写
function myFunction() {}
myfunction(); // 报错:myfunction is not defined
myFunction(); // ✅ 正确
九、常见错误对比
| 错误写法 | 正确写法 | 说明 |
|---|---|---|
Alert(100) |
alert(100) |
alert全小写 |
Console.log() |
console.log() |
console全小写 |
Document.write() |
document.write() |
document全小写 |
Window.alert() |
window.alert() |
window全小写 |
十、代码风格统一工具
Prettier(代码格式化工具)
json
// .prettierrc
{
"semi": true, // 自动加分号
"singleQuote": true // 使用单引号
}
安装与使用:
bash
npm install --save-dev prettier
npx prettier --write "**/*.js" # 格式化所有JS文件
十一、代码中的实际应用
javascript
// 方式一:使用分号
var a = 10; var b = 20; var c = a + b;
// 方式二:换行
var x = 100
var y = 200
var z = x + y
// 方式三:分号 + 换行(最清晰,推荐)
var firstName = 'JavaScript';
var lastName = 'Developer';
var fullName = firstName + ' ' + lastName;
console.log(c); // 30
console.log(z); // 300
console.log(fullName); // JavaScript Developer
// ⚠️ 大小写严格区分!
// Alert(100); // ❌ 报错:Alert is not a function
alert(100); // ✅ 正确
执行流程:
- 前三组变量声明(分号/换行/分号+换行)都正常执行
- 三个
console.log输出计算结果 - 注释的
Alert(100)如果取消注释会报错 alert(100)正常弹框
十二、真实项目规范示例(Google JavaScript Style Guide)
javascript
// Google推荐:始终加分号
function calculateTotal(price, quantity) {
var subtotal = price * quantity;
var tax = subtotal * 0.1;
var total = subtotal + tax;
return total;
}
// Airbnb推荐:也是加分号
const user = {
name: 'John',
age: 30,
};
// StandardJS推荐:不加分号(但需注意陷阱)
var arr = [1, 2, 3]
console.log(arr)
3.5 输出内容的三种方法
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JavaScript 输出方法完整示例</title>
<style>
body { font-family: Arial; padding: 30px; background: #f0f4f8; }
.card {
background: white;
border-radius: 10px;
padding: 24px;
margin-bottom: 24px;
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
}
h2 { color: #2c3e50; border-bottom: 3px solid #3498db; padding-bottom: 8px; }
h3 { color: #34495e; margin: 16px 0 8px; }
code {
background: #e8f4fd;
padding: 2px 8px;
border-radius: 4px;
font-size: 14px;
color: #2980b9;
}
.btn {
display: inline-block;
padding: 8px 18px;
margin: 6px;
background: #3498db;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
}
.btn:hover { background: #2980b9; }
.btn-alert { background: #e74c3c; }
.btn-alert:hover { background: #c0392b; }
.btn-write { background: #e67e22; }
.btn-write:hover { background: #d35400; }
#log-output {
background: #1e1e1e;
color: #d4d4d4;
padding: 16px;
border-radius: 6px;
font-family: 'Courier New', monospace;
font-size: 13px;
min-height: 100px;
max-height: 200px;
overflow-y: auto;
}
.log-line { padding: 2px 0; border-bottom: 1px solid #333; }
.log-info { color: #4fc3f7; }
.log-warn { color: #ffb74d; }
.log-error { color: #ef9a9a; }
.comparison-table { width: 100%; border-collapse: collapse; }
.comparison-table th, .comparison-table td {
padding: 10px 14px;
border: 1px solid #ddd;
font-size: 14px;
}
.comparison-table th { background: #3498db; color: white; }
.comparison-table tr:nth-child(even) { background: #f8f9fa; }
</style>
</head>
<body>
<div class="card">
<h2>JavaScript 三种输出方式</h2>
<div class="card">
<h3>① alert() --- 弹框输出</h3>
<p><code>alert(内容)</code> --- 弹出一个警告框,会阻塞代码执行</p>
<button class="btn btn-alert" onclick="alert('你好,我是 alert 弹框!\n可以输出数字、字符串等任意类型')">
点击查看 alert
</button>
<button class="btn btn-alert" onclick="alert(3.14159)">输出数字</button>
<button class="btn btn-alert" onclick="alert(true)">输出布尔值</button>
</div>
<div class="card">
<h3>② document.write() --- 写入页面</h3>
<p><code>document.write(内容)</code> --- 将内容输出到 HTML 文档中</p>
<p style="color: #e74c3c; font-size: 13px;">⚠️ 在页面加载完成后调用会清空整个页面!通常只在学习阶段使用。</p>
<button class="btn btn-write" id="writeBtn">点击写入文本到页面</button>
<div id="writeArea" style="min-height: 40px; border: 2px dashed #e67e22; border-radius: 6px; padding: 10px; margin-top: 8px; color: #666; font-size: 13px;">
document.write 的内容将显示在这里(模拟效果)
</div>
</div>
<div class="card">
<h3>③ console.log() --- 控制台输出</h3>
<p><code>console.log(内容)</code> --- 输出到浏览器开发者工具的控制台(按 F12 打开)</p>
<p>这是开发中最常用的调试手段!</p>
<button class="btn" onclick="logToConsoleAndUI()">点击输出到控制台</button>
<button class="btn" onclick="logWarnAndError()">输出警告和错误</button>
<div style="margin-top: 12px;">
<strong style="font-size: 13px;">模拟控制台输出效果:</strong>
<div id="log-output">
<div class="log-line log-info">// 点击上方按钮,这里会显示 console 输出内容</div>
</div>
</div>
</div>
</div>
<div class="card">
<h2>三种输出方式对比</h2>
<table class="comparison-table">
<tr>
<th>方法</th>
<th>输出位置</th>
<th>是否阻塞</th>
<th>用途</th>
<th>推荐程度</th>
</tr>
<tr>
<td><code>alert()</code></td>
<td>弹框</td>
<td>✅ 阻塞</td>
<td>弹出提示、简单调试</td>
<td>⭐⭐(学习用)</td>
</tr>
<tr>
<td><code>document.write()</code></td>
<td>页面文档流</td>
<td>❌ 不阻塞</td>
<td>动态写入内容(少用)</td>
<td>⭐(了解即可)</td>
</tr>
<tr>
<td><code>console.log()</code></td>
<td>开发者控制台</td>
<td>❌ 不阻塞</td>
<td>调试信息、开发调试</td>
<td>⭐⭐⭐⭐⭐(强烈推荐)</td>
</tr>
<tr>
<td><code>console.warn()</code></td>
<td>控制台(橙色)</td>
<td>❌ 不阻塞</td>
<td>输出警告信息</td>
<td>⭐⭐⭐⭐</td>
</tr>
<tr>
<td><code>console.error()</code></td>
<td>控制台(红色)</td>
<td>❌ 不阻塞</td>
<td>输出错误信息</td>
<td>⭐⭐⭐⭐</td>
</tr>
</table>
</div>
<script>
// alert 演示
// alert(250);
// document.write 模拟
document.getElementById('writeBtn').onclick = function() {
var area = document.getElementById('writeArea');
area.innerHTML = '<strong style="color:#e67e22">document.write 写入的内容:</strong><br>' +
'数字:500<br>' +
'字符串:Hello, JavaScript!<br>' +
'<span style="color:#e74c3c">⚠️ 实际的 document.write() 会直接插入到文档流中</span>';
area.style.color = '#333';
};
// console 演示
function logToConsoleAndUI() {
var output = document.getElementById('log-output');
var lines = [
{ text: '> console.log(666)', cls: 'log-info' },
{ text: ' 666', cls: '' },
{ text: '> console.log("Hello, JavaScript!")', cls: 'log-info' },
{ text: ' Hello, JavaScript!', cls: '' },
{ text: '> console.log(typeof "hello")', cls: 'log-info' },
{ text: ' "string"', cls: '' },
{ text: '> console.log(0.1 + 0.2)', cls: 'log-info' },
{ text: ' 0.30000000000000004 // 浮点精度问题!', cls: 'log-warn' },
];
output.innerHTML = '';
lines.forEach(function(line) {
var div = document.createElement('div');
div.className = 'log-line ' + line.cls;
div.textContent = line.text;
output.appendChild(div);
});
// 同时真正输出到控制台(按 F12 查看)
console.log(666);
console.log('Hello, JavaScript!');
console.log(typeof 'hello');
console.log(0.1 + 0.2);
}
function logWarnAndError() {
var output = document.getElementById('log-output');
output.innerHTML = '<div class="log-line log-warn">⚠ console.warn("这是一个警告") --- 橙色</div>' +
'<div class="log-line log-error">✖ console.error("这是一个错误") --- 红色</div>' +
'<div class="log-line log-info">ℹ console.info("这是信息") --- 蓝色</div>' +
'<div class="log-line"> console.table([{name:"JS", year:1995}]) --- 表格</div>';
console.warn('这是一个警告');
console.error('这是一个错误');
console.info('这是信息');
console.table([{ name: 'JavaScript', year: 1995, creator: 'Brendan Eich' }]);
}
</script>
</body>
</html>

💡 代码解释:JavaScript三种输出方式
一、三种输出方式的本质区别
| 方法 | 输出位置 | 用途 | 是否阻塞代码执行 |
|---|---|---|---|
alert() |
浏览器弹框 | 用户提示、简单调试 | ✅ 阻塞(必须点"确定"才能继续) |
document.write() |
HTML文档流 | 动态生成内容(少用) | ❌ 不阻塞 |
console.log() |
开发者控制台 | 开发调试(最常用) | ❌ 不阻塞 |
二、alert() 详解
javascript
// 基本用法
alert('你好,我是 alert 弹框!');
// 输出不同数据类型
alert(3.14159); // 数字 → 自动转为字符串"3.14159"
alert(true); // 布尔值 → 转为"true"
alert(undefined); // → 转为"undefined"
alert(null); // → 转为"null"
// 换行:使用 \n
alert('第一行\n第二行\n第三行');
// 拼接字符串
var name = 'JavaScript';
alert('欢迎学习 ' + name + '!');
alert() 的工作原理:
- 调用
alert()时,浏览器显示模态对话框 - 代码执行暂停(阻塞),直到用户点击"确定"
- 用户点击"确定"后,代码继续执行下一句
javascript
console.log('执行1');
alert('弹框'); // 代码在这里暂停
console.log('执行2'); // 点击"确定"后才会执行
alert() 的局限性:
- ❌ 无法格式化输出(只能显示纯文本)
- ❌ 阻塞用户操作(体验不好)
- ❌ 无法控制样式(外观固定)
- ❌ 不适合生产环境(只适合学习/调试)
三、document.write() 详解
javascript
// 基本用法:将内容写入HTML文档流
document.write('数字:500');
document.write('字符串:Hello, JavaScript!');
// 可以写入HTML标签
document.write('<h2 style="color: red;">红色标题</h2>');
document.write('<strong>粗体文本</strong>');
// 换行:使用<br>标签
document.write('第一行<br>');
document.write('第二行<br>');
⚠️ document.write() 的致命问题:
javascript
// 场景1:页面加载时调用(✅ 正常)
document.write('这是加载时的内容'); // 内容插入到文档流中
// 场景2:页面加载完成后调用(❌ 清空整个页面!)
window.onload = function() {
document.write('这会清空整个页面!');
// 原因:文档流已关闭,调用write()会重新打开文档流,
// 导致原有内容全部丢失!
};
// 场景3:点击按钮后调用(❌ 清空整个页面!)
button.onclick = function() {
document.write('页面被清空了!');
};
为什么会清空页面?
- 页面加载时,文档流处于打开状态 ,
document.write()正常写入 - 页面加载完成(
DOMContentLoaded/load事件后),文档流关闭 - 此时调用
document.write()会重新打开文档流,之前的内容全部丢失
现代替代方案:
javascript
// ✅ 推荐:使用 innerHTML
document.getElementById('output').innerHTML = '新内容';
// ✅ 推荐:使用 textContent(更安全,防止XSS)
document.getElementById('output').textContent = '纯文本内容';
// ✅ 推荐:使用 createElement + appendChild
var p = document.createElement('p');
p.textContent = '新段落';
document.body.appendChild(p);
四、console.log() 详解(最重要!)
javascript
// 基本用法
console.log(666);
console.log('Hello, JavaScript!');
console.log(typeof 'hello'); // "string"
console.log(0.1 + 0.2); // 0.30000000000000004
// 多参数输出(自动用空格分隔)
console.log('姓名:', 'JavaScript', '年龄:', 25);
// 输出:姓名: JavaScript 年龄: 25
// 输出对象(可展开查看)
var user = { name: 'John', age: 30 };
console.log(user); // { name: 'John', age: 30 }
// 输出数组
var arr = [1, 2, 3, 4, 5];
console.log(arr); // [1, 2, 3, 4, 5]
console的其他方法:
javascript
// console.warn() - 警告(橙色)
console.warn('这是一个警告');
// console.error() - 错误(红色)
console.error('这是一个错误');
// console.info() - 信息(蓝色,某些浏览器)
console.info('这是一条信息');
// console.table() - 表格展示(超级好用!)
var users = [
{ name: 'Alice', age: 25, city: 'Beijing' },
{ name: 'Bob', age: 30, city: 'Shanghai' },
{ name: 'Charlie', age: 35, city: 'Guangzhou' }
];
console.table(users);
// 浏览器会以表格形式展示数据,非常直观!
// console.dir() - 查看对象的所有属性
console.dir(document.body);
// console.time() / console.timeEnd() - 计时
console.time('循环耗时');
for (var i = 0; i < 1000000; i++) {
// 执行某些操作
}
console.timeEnd('循环耗时'); // 输出:循环耗时: 12.5ms
// console.group() / console.groupEnd() - 分组
console.group('用户信息');
console.log('姓名:张三');
console.log('年龄:25');
console.log('城市:北京');
console.groupEnd();
五、代码中的关键技术点
技术点1:模拟控制台输出
javascript
var output = document.getElementById('log-output');
var lines = [
{ text: '> console.log(666)', cls: 'log-info' },
{ text: ' 666', cls: '' },
// ...
];
output.innerHTML = ''; // 清空之前的内容
lines.forEach(function(line) {
var div = document.createElement('div');
div.className = 'log-line ' + line.cls;
div.textContent = line.text;
output.appendChild(div); // 添加到页面
});
技术点2:同时输出到UI和控制台
javascript
function logToConsoleAndUI() {
// 1. 输出到页面UI(给用户看)
document.getElementById('log-output').innerHTML = '<div>输出内容</div>';
// 2. 同时输出到浏览器控制台(给开发者看)
console.log(666);
console.log('Hello, JavaScript!');
}
技术点3:浮点数精度问题
javascript
console.log(0.1 + 0.2); // 0.30000000000000004(不是0.3!)
原因: JavaScript使用IEEE 754双精度浮点数标准,二进制无法精确表示某些十进制小数。
解决方案:
javascript
// 方案1:四舍五入到指定位数
(0.1 + 0.2).toFixed(2); // "0.30"
// 方案2:转为整数计算
(0.1 * 10 + 0.2 * 10) / 10; // 0.3
// 方案3:使用专门的库(如decimal.js)
六、三种方法的使用场景
| 场景 | 推荐方法 | 说明 |
|---|---|---|
| 学习阶段快速测试 | alert() |
直观,但阻塞代码 |
| 开发调试 | console.log() |
最常用,不影响页面 |
| 输出复杂对象 | console.log() / console.table() |
可展开查看 |
| 性能分析 | console.time() |
测量代码执行时间 |
| 页面动态内容 | innerHTML / textContent |
不用document.write() |
| 用户通知 | alert() / 自定义弹框 |
现代项目用UI库(如Element UI) |
七、真实开发场景
javascript
// 调试API响应
fetch('/api/users')
.then(res => res.json())
.then(data => {
console.log('API响应:', data); // 查看返回的数据
console.table(data); // 表格形式展示
})
.catch(err => {
console.error('请求失败:', err); // 错误提示
});
// 调试函数执行
function processData(data) {
console.log('开始处理数据:', data);
console.time('处理耗时');
// ...复杂处理逻辑
console.timeEnd('处理耗时');
console.log('处理完成');
}
// 条件日志(只在开发环境输出)
if (process.env.NODE_ENV === 'development') {
console.log('开发环境日志');
}
八、代码执行结果分析
点击示例按钮后:
alert按钮:
- 浏览器弹出警告框,显示相应内容
- 代码执行暂停,直到用户点击"确定"
document.write按钮:
- 模拟效果,更新页面上的
#writeArea区域 - (实际的
document.write()会直接插入到文档流)
console.log按钮:
- 页面上显示模拟的控制台输出
- 同时,真正的输出在浏览器控制台(按F12查看)
九、开发者工具(DevTools)使用技巧
打开控制台:
- Windows/Linux:
F12或Ctrl + Shift + I - Mac:
Cmd + Option + I - 右键页面 → "检查" → "Console"标签
控制台快捷操作:
$0- 当前选中的DOM元素$$('div')- 等同于document.querySelectorAll('div')clear()- 清空控制台↑/↓- 浏览历史命令
四、变量深度解析
4.1 数据、直接量与变量的区别
名词解释
| 术语 | 英文 | 含义 | 示例 |
|---|---|---|---|
| 数据 | Data | 程序处理的信息 | 数字、文字、图片... |
| 直接量 | Literal | 直接写出来的固定值 | 250、'hello'、true |
| 变量 | Variable | 给数据贴的"标签"(内存地址的别名) | var price = 250 |
| 常量 | Constant | 一旦赋值不可更改的量 | const PI = 3.14 |
直接量(字面量)就像便利贴上的数字 → 写死了
变量就像一个空盒子,可以存不同的东西
var box = 100; // 盒子里放了 100
box = 200; // 盒子里换成 200
变量的意义
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>
<style>
body { font-family: Arial; padding: 20px; }
.case { background: #f8f9fa; padding: 16px; border-radius: 8px; margin: 12px 0; }
.bad { border-left: 4px solid #e74c3c; }
.good { border-left: 4px solid #27ae60; }
pre { background: #282c34; color: #abb2bf; padding: 16px; border-radius: 6px; font-size: 13px; margin-top: 8px; }
.keyword { color: #c678dd; }
.string { color: #98c379; }
.comment { color: #5c6370; font-style: italic; }
.number { color: #d19a66; }
</style>
</head>
<body>
<h2>变量的意义:方便复用 & 保证一致性</h2>
<div class="case bad">
<h3>❌ 不用变量(直接量重复出现)</h3>
<pre><span class="comment">// 商品原价 199,如果价格变了,需要改很多处</span>
console.log('商品原价:' + <span class="number">199</span> + '元');
console.log('折扣后:' + <span class="number">199</span> * 0.8 + '元');
console.log('会员价:' + <span class="number">199</span> * 0.7 + '元');
console.log('节日特惠:' + <span class="number">199</span> * 0.5 + '元');</pre>
</div>
<div class="case good">
<h3>✅ 使用变量(一改全改)</h3>
<pre><span class="keyword">var</span> originalPrice = <span class="number">199</span>; <span class="comment">// 只需改这一处</span>
console.log('商品原价:' + originalPrice + '元');
console.log('折扣后:' + originalPrice * <span class="number">0.8</span> + '元');
console.log('会员价:' + originalPrice * <span class="number">0.7</span> + '元');
console.log('节日特惠:' + originalPrice * <span class="number">0.5</span> + '元');</pre>
</div>
<script>
// 演示变量的实际效果
var originalPrice = 199;
console.log('商品原价:' + originalPrice + '元');
console.log('折扣后:' + originalPrice * 0.8 + '元');
console.log('会员价:' + originalPrice * 0.7 + '元');
console.log('节日特惠:' + originalPrice * 0.5 + '元');
</script>
</body>
</html>

💡 代码解释:变量意义与使用规范
一、为什么需要变量?
javascript
// 问题:不用变量,代码难维护
console.log('商品原价:' + 199 + '元'); // 硬编码
console.log('折扣后:' + 199 * 0.8 + '元'); // 要改价格,每处都要改!
// 解决:用变量,修改一处即全改
var originalPrice = 199; // 只需改这一个地方
console.log('商品原价:' + originalPrice + '元');
console.log('折扣后:' + originalPrice * 0.8 + '元');
变量的两大核心价值:
| 价值 | 说明 | 例子 |
|---|---|---|
| 语义化 | 给数据一个有意义的名字 | 199 → originalPrice |
| 可维护性 | 修改一处影响所有引用 | 价格从 199 改到 299,只改变量定义 |
二、直接量(字面量)vs 变量的使用场景
javascript
// ✅ 适合直接量:只用一次,含义明显
Math.PI // π 本身就是常量
if (age >= 18) { ... } // 18 有明确含义
// ✅ 适合变量:会复用,或含义不清晰
var VOTING_AGE = 18; // 赋予含义
if (age >= VOTING_AGE) { ... } // 更清晰
var MAX_FILE_SIZE = 1024 * 1024 * 10; // 10MB
三、代码中的关键技术
javascript
// 使用了 CSS 类名做视觉区分
.case.bad → 红色左边框(不好的示例)
.case.good → 绿色左边框(好的示例)
// 使用 <pre> 标签展示代码
// <pre>:预格式化文本,保留空格和换行
// 内部使用 <span> 标签给代码片段着色(模拟语法高亮)
// 着色方案
.keyword { color: #c678dd; } // 关键字(紫色)
.string { color: #98c379; } // 字符串(绿色)
.number { color: #d19a66; } // 数字(橙色)
.comment { color: #5c6370; } // 注释(灰色)
四、真实项目应用价值
场景:配置集中管理
javascript
// ❌ 散落各处的硬编码(难维护)
function createRequest() {
return fetch('https://api.myapp.com/v1' + '/users', {
timeout: 30000
});
}
function checkAge(age) {
return age >= 18;
}
// ✅ 集中配置(一处修改,全部生效)
const API_BASE = 'https://api.myapp.com/v1';
const TIMEOUT = 30000;
const MIN_AGE = 18;
function createRequest() {
return fetch(API_BASE + '/users', { timeout: TIMEOUT });
}
function checkAge(age) {
return age >= MIN_AGE;
}
4.2 变量的语法
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>
<style>
body { font-family: Arial; padding: 20px; max-width: 800px; }
.section { background: #f8f9fa; padding: 16px; border-radius: 8px; margin: 16px 0; }
h3 { color: #2c3e50; }
.console-output {
background: #1e1e1e;
color: #d4d4d4;
padding: 14px;
border-radius: 6px;
font-family: monospace;
font-size: 13px;
}
.info-box {
background: #e8f4fd;
border: 1px solid #3498db;
border-radius: 6px;
padding: 12px 16px;
margin: 12px 0;
font-size: 14px;
}
.warn-box {
background: #fef9e7;
border: 1px solid #f39c12;
border-radius: 6px;
padding: 12px 16px;
margin: 12px 0;
font-size: 14px;
}
</style>
</head>
<body>
<h2>变量语法详解</h2>
<div class="section">
<h3>变量的生命周期</h3>
<div class="console-output" id="output1"></div>
</div>
<div class="info-box">
💡 <strong>var 的特点:</strong>
<ul style="margin-top: 8px; padding-left: 20px; line-height: 1.8;">
<li>可以重复声明(<code>var x; var x;</code> 不报错)</li>
<li>没有块级作用域(在 if/for 中声明的变量,外部也可访问)</li>
<li>存在变量提升(Hoisting):声明会被提升到函数/全局顶部</li>
</ul>
</div>
<div class="warn-box">
⚠️ <strong>ES6 推荐:</strong> 现代 JavaScript 开发推荐使用 <code>let</code>(可变)和 <code>const</code>(常量)代替 <code>var</code>,
它们有块级作用域,行为更可预期。本课程先学 <code>var</code> 打基础。
</div>
<div class="section">
<h3>变量交换经典题:交换 a 和 b 的值</h3>
<div class="console-output" id="output2"></div>
</div>
<script>
// ===== 变量基本操作 =====
// 声明变量(此时值为 undefined)
var num01;
// 赋值
num01 = 250;
// 声明并赋值
var num02 = 500;
// 重新赋值
num02 = 600;
// 变量赋值给变量
num01 = num02;
// 声明未赋值
var num03;
var out1 = document.getElementById('output1');
out1.innerHTML = [
'// 声明变量 var num01; 然后赋值 num01 = 250;',
'// 然后赋值 num02 = 600;',
'// 然后 num01 = num02;',
'',
'num01 = ' + num01,
'num02 = ' + num02,
'',
'// 声明但未赋值的变量',
'num03 = ' + num03, // undefined
'',
'// ⚠️ 使用不存在的变量会报错:',
'// console.log(num99); → ReferenceError: num99 is not defined',
].map(function(line) {
return '<div>' + line + '</div>';
}).join('');
// ===== 变量交换 =====
var a = 100;
var b = 200;
// 方法一:使用临时变量(最通用)
var temp = a;
a = b;
b = temp;
var out2 = document.getElementById('output2');
out2.innerHTML = [
'// 原始值:a = 100, b = 200',
'// 方法一:使用临时变量',
'var temp = a; // temp = 100',
'a = b; // a = 200',
'b = temp; // b = 100',
'',
'交换后:a = ' + a + ', b = ' + b,
'',
'// 方法二(ES6):解构赋值',
'// [a, b] = [b, a]; // 一行搞定!',
].map(function(line) {
return '<div>' + line + '</div>';
}).join('');
</script>
</body>
</html>

💡 代码解释:变量语法与操作
一、变量的四个基本操作
javascript
// 操作1:声明(创建变量,但不赋值)
var num01;
console.log(num01); // undefined(已声明但未赋值)
// 操作2:赋值
num01 = 250; // 给变量赋值
// 操作3:声明并赋值(一步到位)
var num02 = 500; // 推荐写法
// 操作4:重新赋值
num02 = 600; // 覆盖原来的值
二、undefined 详解
javascript
var x; // 声明但未赋值
console.log(x); // undefined
// undefined 的含义:
// 1. 变量已声明但未赋值
// 2. 函数没有返回值时,返回 undefined
// 3. 对象访问不存在的属性时,返回 undefined
三、变量赋值给变量
javascript
var a = 100;
var b = a; // b 得到 a 的值(值复制)
// 原始类型:值复制(互不影响)
a = 200;
console.log(b); // 100(b 不受 a 的影响)
// 对象类型:引用复制(会相互影响)
var obj1 = { name: 'JavaScript' };
var obj2 = obj1; // obj2 指向 obj1 的内存地址
obj1.name = 'TypeScript';
console.log(obj2.name); // 'TypeScript'(obj2 受影响)
四、变量交换的两种方法
方法一:使用临时变量(经典方法)
javascript
var a = 100;
var b = 200;
// 步骤拆解:
var temp = a; // 1. 临时变量保存 a 的值(temp = 100)
a = b; // 2. a 得到 b 的值(a = 200)
b = temp; // 3. b 得到 temp 的值(b = 100)
console.log(a, b); // 200 100
为什么需要临时变量?
javascript
// ❌ 错误:直接赋值会丢失数据
var a = 100;
var b = 200;
a = b; // a 变成 200,原来的 100 丢失了!
b = a; // b 也变成 200(而不是期望的 100)
console.log(a, b); // 200 200(错误!)
// ✅ 正确:用临时变量保存数据
var temp = a; // 先保存 a 的值
a = b;
b = temp; // 从临时变量恢复
方法二:ES6 解构赋值(现代方法)
javascript
let a = 100;
let b = 200;
// 一行搞定!
[a, b] = [b, a];
console.log(a, b); // 200 100
五、var 的三大特点(重要!)
特点1:可以重复声明
javascript
var x = 10;
var x = 20; // ✅ 不报错(let/const 会报错)
console.log(x); // 20
特点2:没有块级作用域
javascript
if (true) {
var y = 30;
}
console.log(y); // 30(✅ 能访问到,var 没有块级作用域)
// 对比 let:
if (true) {
let z = 40;
}
// console.log(z); // ❌ 报错:z is not defined(let 有块级作用域)
特点3:变量提升(Hoisting)
javascript
console.log(num); // undefined(不报错!)
var num = 100;
// 实际执行顺序(提升后):
var num; // 声明被提升到顶部
console.log(num); // undefined
num = 100; // 赋值留在原地
六、var vs let vs const 对比
| 特性 | var | let | const |
|---|---|---|---|
| 作用域 | 函数作用域 | 块级作用域 | 块级作用域 |
| 重复声明 | ✅ 允许 | ❌ 不允许 | ❌ 不允许 |
| 变量提升 | ✅ 有(值为undefined) | ✅ 有(暂时性死区) | ✅ 有(暂时性死区) |
| 重新赋值 | ✅ 允许 | ✅ 允许 | ❌ 不允许 |
| 初始化要求 | 可选 | 可选 | 必须立即初始化 |
七、真实应用场景
场景1:商品价格计算
javascript
var originalPrice = 199;
var discount = 0.8; // 8折
var finalPrice = originalPrice * discount;
console.log('折后价:' + finalPrice); // 159.2
场景2:计数器
javascript
var count = 0;
button.onclick = function() {
count++; // 每次点击加1
console.log('点击次数:' + count);
};
场景3:配置开关
javascript
var isDebugMode = true;
if (isDebugMode) {
console.log('调试信息:...');
}
八、常见错误
错误1:使用未声明的变量
javascript
console.log(xyz); // ❌ ReferenceError: xyz is not defined
// 解决:先声明再使用
var xyz = 100;
console.log(xyz); // ✅ 100
错误2:变量名拼写错误
javascript
var userNmae = '张三'; // 拼错了(Name 写成 Nmae)
console.log(userName); // ❌ ReferenceError: userName is not defined
错误3:忘记声明变量(隐式全局变量)
javascript
function test() {
x = 10; // ❌ 忘记写 var,会创建全局变量
}
test();
console.log(x); // 10(意外的全局变量!)
// 正确做法:
function test() {
var x = 10; // ✅ 局部变量
}
4.3 命名规范
JavaScript 命名规范
强制规则
建议规范
✅ 字母、数字、_、 组成
✅ 不能以数字开头
✅ 不能是关键字/保留字
如:name, age, _id, price, num01
❌ 3d, 1st, 99lives
❌ var, if, for, while, function
使用有意义的词语
小驼峰命名 推荐
大驼峰命名 类名
蛇形命名 snake_case
userName, maxLength, isActive
UserProfile, ProductCard
user_name, max_length 较少用于JS
| 命名风格 | 示例 | 使用场景 |
|---|---|---|
| 小驼峰(camelCase) | userName, maxLength, isLoading |
JS 变量、函数(最常用) |
| 大驼峰(PascalCase) | UserProfile, ProductCard |
类名、构造函数、React组件 |
| 蛇形(snake_case) | user_name, max_length |
Python惯用;JS中少用 |
| 全大写下划线 | MAX_SIZE, API_URL |
常量(CONSTANT_CASE) |
| 烤串(kebab-case) | user-name, nav-bar |
HTML属性、CSS类名 |
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>
</head>
<body>
<script>
// ===== 合法的变量名 =====
var name;
var age;
var num100;
var _;
var _$;
var $23;
var _hello;
var userName;
var homeAddress;
var isActive;
var MAX_RETRY_COUNT = 3; // 常量用全大写
// ===== 非法的变量名(会报错)=====
// var 34hello; // ❌ 数字开头
// var user-name; // ❌ 含连字符
// var var; // ❌ 关键字
// var while; // ❌ 关键字
// var class; // ❌ 关键字
// ===== 小驼峰(推荐)=====
var homeAddress = '上海';
var secondHomeAddress = '北京';
var userFullName = '张三';
var isLoginSuccess = true;
var currentPageIndex = 0;
var maxRetryCount = 3;
// ===== 大驼峰(类名用)=====
function UserProfile(name, age) { // 构造函数用大驼峰
this.name = name;
this.age = age;
}
// ===== 常量(全大写)=====
var API_BASE_URL = 'https://api.example.com';
var MAX_FILE_SIZE = 1024 * 1024 * 5; // 5MB
console.log('合法变量名示例演示完毕,请查看代码');
console.log('homeAddress:', homeAddress);
console.log('userFullName:', userFullName);
</script>
</body>
</html>
💡 代码解释:JavaScript变量命名规范
一、强制规则(必须遵守,否则报错)
javascript
// 规则1:只能包含 字母、数字、下划线_、美元符号$
var name; // ✅ 字母
var age18; // ✅ 字母+数字
var _id; // ✅ 下划线开头
var $price; // ✅ 美元符号开头
var user_name; // ✅ 包含下划线
// 规则2:不能以数字开头
// var 1st; // ❌ SyntaxError
// var 99bottles; // ❌ SyntaxError
var a1st; // ✅ 字母开头,数字在后面
// 规则3:不能使用关键字/保留字
// var var; // ❌ var 是关键字
// var if; // ❌ if 是关键字
// var while; // ❌ while 是关键字
// var class; // ❌ class 是关键字(ES6+)
var myVar; // ✅ 加前缀避开关键字
var condition; // ✅ 用其他词代替
JavaScript 保留字和关键字(不能用作变量名):
break, case, catch, class, const, continue, debugger, default, delete,
do, else, export, extends, finally, for, function, if, import, in,
instanceof, let, new, return, super, switch, this, throw, try, typeof,
var, void, while, with, yield, enum, implements, interface, package,
private, protected, public, static, await, abstract, boolean, byte,
char, double, final, float, goto, int, long, native, short, synchronized,
throws, transient, volatile
二、命名风格详解
风格1:小驼峰命名法(camelCase)
javascript
// JavaScript 最常用的命名风格!
var userName = '张三'; // 用户名
var userAge = 25; // 用户年龄
var isLoginSuccess = true; // 是否登录成功(布尔值常用 is/has 开头)
var maxRetryCount = 3; // 最大重试次数
var currentPageIndex = 0; // 当前页码
var homeAddress = '北京'; // 家庭地址
// 规则:第一个单词小写,后续单词首字母大写
// user + Name = userName
// max + Retry + Count = maxRetryCount
风格2:大驼峰命名法(PascalCase)
javascript
// 用于类名、构造函数、React组件
// 构造函数(ES5)
function UserProfile(name, age) {
this.name = name;
this.age = age;
}
var user = new UserProfile('张三', 25);
// 类(ES6+)
class ProductCard {
constructor(title, price) {
this.title = title;
this.price = price;
}
}
// React 组件
function NavigationBar(props) {
return <nav>{props.children}</nav>;
}
风格3:全大写下划线(CONSTANT_CASE)
javascript
// 用于常量(值不会改变的变量)
var MAX_FILE_SIZE = 1024 * 1024 * 5; // 5MB
var API_BASE_URL = 'https://api.example.com';
var MAX_RETRY_COUNT = 3;
var DEFAULT_TIMEOUT = 30000; // 30秒
// 数学常量
var PI = 3.14159;
var E = 2.71828;
风格4:蛇形命名法(snake_case)
javascript
// JavaScript 中较少使用(Python 风格)
var user_name = '张三';
var max_length = 100;
var is_active = true;
// 在 JavaScript 中推荐用小驼峰代替:
var userName = '张三'; // ✅ 更符合JS习惯
var maxLength = 100;
var isActive = true;
风格5:烤串命名法(kebab-case)
javascript
// ❌ JavaScript 变量名不能使用连字符(会被当作减号)
// var user-name = '张三'; // SyntaxError
// ✅ 但在 HTML 属性和 CSS 类名中常用
<div class="nav-bar"></div>
<input data-user-id="123">
.user-card { ... }
.nav-bar { ... }
三、命名最佳实践
实践1:使用有意义的名称
javascript
// ❌ 不好:无意义的名称
var a = 100;
var b = 200;
var x = a * b;
// ✅ 好:清晰的名称
var price = 100;
var quantity = 200;
var totalAmount = price * quantity;
实践2:布尔值用 is/has/can 开头
javascript
// ✅ 推荐
var isActive = true;
var hasPermission = false;
var canEdit = true;
var isLoading = false;
// ❌ 不推荐
var active = true; // 不明确是布尔值还是其他类型
var permission = false;
实践3:数组用复数形式
javascript
// ✅ 推荐
var users = ['张三', '李四', '王五'];
var products = [product1, product2];
var items = [item1, item2, item3];
// ❌ 不推荐
var user = ['张三', '李四']; // 看起来像单个用户,实际是数组
实践4:函数名用动词开头
javascript
// ✅ 推荐
function getUserName() { ... }
function setUserAge() { ... }
function validateEmail() { ... }
function calculateTotal() { ... }
// 常用动词:
// get/set(获取/设置)
// add/remove(添加/删除)
// create/delete(创建/删除)
// show/hide(显示/隐藏)
// open/close(打开/关闭)
// start/stop(开始/停止)
// validate/check(验证/检查)
// calculate/compute(计算)
实践5:避免使用保留字的变体
javascript
// ⚠️ 虽然合法,但不推荐
var _class = 'MyClass'; // class 的变体
var function_ = 'test'; // function 的变体
// ✅ 推荐:用更清晰的名称
var className = 'MyClass';
var functionName = 'test';
四、真实项目中的命名示例
示例1:电商网站
javascript
// 商品信息
var productId = 'P123456';
var productName = 'iPhone 14 Pro';
var productPrice = 7999;
var productStock = 50;
var isProductAvailable = true;
// 用户信息
var userId = 'U789';
var userName = '张三';
var userEmail = 'zhangsan@example.com';
var isUserVip = false;
// 订单信息
var orderId = 'O20240101001';
var orderTotalAmount = 15998;
var orderStatus = 'pending'; // pending/shipped/delivered
示例2:配置常量
javascript
// API 配置
var API_BASE_URL = 'https://api.myapp.com';
var API_TIMEOUT = 30000;
var API_RETRY_TIMES = 3;
// 应用配置
var APP_NAME = 'MyApp';
var APP_VERSION = '1.0.0';
var DEFAULT_LANGUAGE = 'zh-CN';
// 分页配置
var DEFAULT_PAGE_SIZE = 20;
var MAX_PAGE_SIZE = 100;
五、命名长度建议
| 变量作用域 | 推荐长度 | 示例 |
|---|---|---|
| 循环变量(局部) | 1-2个字符 | i, j, k, idx |
| 临时变量(局部) | 3-10个字符 | temp, result, count |
| 函数参数 | 4-15个字符 | userName, productId |
| 全局变量/常量 | 10-25个字符 | MAX_UPLOAD_FILE_SIZE |
javascript
// ✅ 循环变量:简短即可
for (var i = 0; i < 10; i++) {
console.log(i);
}
// ✅ 业务变量:清晰完整
var userRegistrationDate = '2024-01-01';
var productAvailableQuantity = 100;
六、常见错误与解决
错误1:中文变量名(不推荐,但合法)
javascript
var 姓名 = '张三'; // ⚠️ 合法,但强烈不推荐
console.log(姓名); // 张三
// ✅ 推荐:用拼音或英文
var xingMing = '张三';
var name = '张三';
错误2:使用关键字的错误写法
javascript
// ❌ 错误
// var new = 10; // SyntaxError
// ✅ 正确
var newValue = 10;
var isNew = true;
错误3:拼写不一致
javascript
var userName = '张三';
// console.log(username); // ❌ 报错:username is not defined
console.log(userName); // ✅ 正确(注意大小写)
七、团队协作建议
使用 ESLint 统一命名规范:
json
// .eslintrc.json
{
"rules": {
"camelcase": ["error", {
"properties": "always"
}],
"id-length": ["error", {
"min": 2,
"max": 30
}]
}
}