主题
1-16对象综合案例
本页对应 第一章JavaScript基础,共 7 段视频,约 1 小时 3 分。
学习目标
- 能使用
Math.random()与Math.floor()生成指定范围的随机整数,并将其作为数组索引随机抽取数组元素。 - 能使用
while循环、if...else分支和break实现猜数字游戏,并在猜对后正确退出循环。 - 能使用
for循环限定猜数字次数,通过开关变量控制次数用完提示,并说明flag赋值需写在break之前的执行逻辑。 - 能封装带布尔参数和默认参数的颜色函数,根据参数返回不同格式的随机颜色。
- 能分别生成随机 RGB 颜色和十六进制颜色,包括生成 3 个 0–255 随机数、循环 6 次随机取字符并拼接字符串。
- 能根据对象数组使用循环和模板字符串拼接标签,并将渲染结果写入指定容器完成学成在线页面渲染。
知识点
案例目标与效果

本节做一个“学成在线”精品推荐模块的综合案例:根据对象数组数据渲染列表页面。页面里每个课程卡片结构相同,只有图片、标题、学习人数不同。如果继续手写 10 个 li,代码重复、后期改数据也麻烦。所以核心思路是:准备对象数组 → 用循环拼接字符串生成多个 li → 把生成的字符串放入 ul 容器中渲染。这和之前做学生信息表渲染每一行的思路相似,只是这次渲染的是真正的 li 标签。
数据准备:对象数组
为什么需要数据?因为页面中变化的只有 src、title、num 三个位置,其余结构一致。怎么想?把每个课程卡片抽象成一个对象,多个卡片就是对象数组。怎么做?声明 let data = [...],每个对象包含图片地址、标题、学习人数。易错点:字段名要统一,后面模板字符串里要用 data[i].src、data[i].title、data[i].num,写错一个字母就取不到值。
循环拼接字符串
为什么需要循环?因为数组里有多个对象,要生成多个结构相同的 li。怎么想?先在循环外准备一个空字符串 let str = '',再 for 循环遍历数组,每次把一段 li 结构拼到 str 后面。怎么做?用模板字符串拼接,变量位置写成 \${data[i].src} 等。转写稿中的调试过程是:先 console.log(str) 看是否生成了多个 li,确认结构没问题后,再逐个把写死的图片、标题、人数替换成数据。易错点:str 必须声明在循环外;for 循环条件用 i < data.length;img 的 src 属性外面仍要保留双引号,变量写在双引号里面,如 src="\${data[i].src}"。
渲染到页面
为什么还要渲染?因为拼接好的 str 只是字符串,还没有进入页面。怎么想?最终容器是 ul.common,把 \${str} 放进 ul 内部,再用 document.write() 输出整体结构。怎么做?先输出外层 div.course.wrapper、标题区 .hd、内容区 .bd,在 <ul class="common"> 中写入 \${str}。易错点:模板字符串里再嵌套模板字符串时要分清 \${};ul 的类名不要写错;如果页面有小图标错误,检查 iconfont 相关 CSS 是否引入。转写稿中还验证了:在数据里多加一条,页面就会多一个卡片,说明页面由数据驱动。
完整代码
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>学成在线</title>
<link rel="stylesheet" href="./iconfont/iconfont.css">
<link rel="stylesheet" href="./css/base.css">
<link rel="stylesheet" href="./css/index.css">
</head>
<body>
<!-- 精品课程 -->
<script>
// 综合案例代码
// 1. 利用对象数组的数据渲染页面
// 综合案例代码
let data = [
{
src: './uploads/quality01.png',
title: 'JavaScript数据看板项目实战',
num: 1125
},
{
src: './uploads/quality02.png',
title: 'Vue.js实战——面经全端项目',
num: 2726
},
{
src: './uploads/quality03.png',
title: '玩转Vue全家桶,iHRM人力资源项目',
num: 9456
},
{
src: './uploads/quality04.png',
title: 'Vue.js实战医疗项目——优医问诊',
num: 7192
},
{
src: './uploads/quality05.png',
title: '小程序实战:小兔鲜电商小程序项目',
num: 2703
},
{
src: './uploads/quality06.png',
title: '前端框架Flutter开发实战',
num: 2841
},
{
src: './uploads/quality07.png',
title: '熟练使用React.js——极客园H5项目',
num: 95682
},
{
src: './uploads/quality08.png',
title: '熟练使用React.js——极客园PC端项目',
num: 904
},
{
src: './uploads/quality09.png',
title: '前端实用技术,Fetch API 实战',
num: 1516
},
{
src: './uploads/quality10.png',
title: '前端高级Node.js零基础入门教程',
num: 2766
},
{
src: './uploads/quality10.png',
title: '前端高级Node.js零基础入门教程',
num: 27669
}
]
// 2. 利用循环来做字符串拼接生成多个li标签,之后把生成的li标签放入容器 ul 里面渲染即可
let str = ''
for (let i = 0; i < data.length; i++) {
// console.log(data[i]) // 每个对象
str += `
`<li>`
<a href="#">
<div class="pic">
<img src="\${data[i].src}" alt="">
</div>
<h4>\${data[i].title}</h4>
<p><span>高级</span> · <i>\${data[i].num}</i>人在学习</p>
</a>
</li>
`
}
console.log(str)
document.write(`
<div class="course wrapper">
<div class="hd">
<h3>精品推荐</h3>
<a href="#">查看全部
<span class="iconfont icon-arrow-right-bold">
</span>
</a>
</div>
<div class="bd">
<ul class="common">
\${str}
</ul>
</div>
</div>
`)
</script>
</body>
</html>调试与易错点小结
调试时先打印 str,确认循环是否生成了正确数量的 li;再检查 src、title、num 是否被替换成数据;最后把 \${str} 放进 ul 中输出。易错点:空字符串要在循环前声明;模板字符串变量必须用 \${};img 的 src 属性双引号不要丢;CSS 资源路径要正确;新增数据后页面能自动多出卡片,说明数据驱动渲染成功。
一、栈与堆:基本类型和引用类型分别存哪里
为什么需要: 变量不只是“起个名字”,它还要在内存中占位置。先分清栈和堆,才能理解为什么基本类型访问快、引用类型容量大,也才能看懂后面数组、对象、函数参数为什么能传来传去。
怎么想: 笔记给出的结论很明确:栈访问速度快,基本数据类型存放到栈里面;堆存储容量大,引用数据类型存放到堆里面。 可以把栈想成手边的小抽屉,取用快;堆想成大仓库,能放更多东西。数组、对象这类引用类型的数据本体在堆中,我们通过引用去访问它。
怎么做: 对照下面两张图理解内存分配:


易错点: 不要把“变量名”和“数据本体”混为一谈;也不要把所有数据都认为存在栈里。基本类型直接存值,引用类型重点在“引用/地址”与堆中数据的关系。
二、变量声明:const 优先,必要时再改 let
为什么需要: 很多变量声明后根本不会再改。如果一开始就用 let,后面可能被误改,安全性差。笔记明确建议:const 优先,尽量使用 const。实际开发中,比如 React、Vue,基本用 const 声明。
怎么想: 总结成一句话:有了变量先给 const,如果发现它后面是要被修改的,再改为 let。 先判断“这个值以后会不会重新赋值”,不会就 const,会就 let。
怎么做: 例如转写稿的猜数字限定次数案例中,开关变量 flag 一开始是 true,猜对后要改成 false,所以它必须用 let;而某些只读取、不再改的配置值,就适合 const。写代码时先写 const,报错或发现要重新赋值时再改 let。
易错点: 不要因为习惯先写 let;也不要把后面明确要修改的变量硬写成 const。判断标准不是“值长不长”,而是“后面是否重新赋值”。
三、专业术语:读懂文档、报错和代码
为什么需要: 关键字、保留字、标识符、表达式、语句这些词会出现在文档、报错和面试题里。分不清它们,就看不懂“这里要写表达式”“这是语句”等说明。
怎么想: 关键字是 JavaScript 中有特殊意义的词汇;保留字是目前没意义、未来可能有特殊意义的词汇;标识(标识符)是变量名、函数名的另一种叫法;表达式可以被求值,一般配合运算符出现;语句是一段可执行代码。
怎么做: 保留笔记中的术语表:
| 术语 | 解释 | 举例 |
|---|---|---|
| 关键字 | 在JavaScript中有特殊意义的词汇 | let、var、function、if、else、switch、case、break |
| 保留字 | 在目前的JavaScript中没意义,但未来可能会具有特殊意义的词汇 | int、short、long、char |
| 标识(标识符) | 变量名、函数名的另一种叫法 | 无 |
| 表达式 | 可以被求值的代码,一般配合运算符出现 | 10 + 3、age >= 18 |
| 语句 | 一段可执行的代码 | if ()、for() |
易错点: 10 + 3 是表达式,if () {} 是语句;不要把“关键字”和“保留字”混用;变量名不能随便用关键字。
四、把数据存储落到案例:随机索引、循环与函数参数
为什么需要: 只背“栈、堆、const”不够,案例能检验我们是否真的会用变量存数据、用索引取数据、用循环处理一批数据。
怎么想: 转写稿里的“随机显示姓名”核心是:先生成数组索引号,再用 数组[索引] 取元素。转写稿里的“缩引号”应纠正为“索引号”。索引范围是 0 到 arr.length - 1,所以公式是:
js
let index = Math.floor(Math.random() * arr.length);
document.write(arr[index]);调试时先打印随机数,确认最小是 0、最大不超过 arr.length - 1,再取数组元素。猜数字案例则先随机生成 1 到 10 的整数,再用 while (true) 不断让用户输入;用户输入用 prompt 得到的是字符串,比较前要转成数字。限定三次时,把无限循环改成 for (let i = 1; i <= 3; i++),并用 flag 控制“次数用完”的提示:
js
let flag = true;
for (let i = 1; i <= 3; i++) {
let num = +prompt('请输入1到10之间的整数');
if (num === random) {
alert('恭喜你猜对了');
flag = false;
break;
} else if (num > random) {
alert('数字猜大了,继续猜');
} else {
alert('数字猜小了,继续猜');
}
}
if (flag) alert('你的次数已经用完了');随机颜色案例中,函数参数 flag = true 用默认值控制返回十六进制还是 RGB;RGB 要生成三个独立的 0 到 255 随机整数,不能共用一个;十六进制则准备包含 0 到 F 的数组,循环 6 次,用随机索引取字符并拼接到 # 后面。学成在线渲染则是对象数组配合 for 循环拼接 li 字符串,再放入 ul 容器。
易错点: 随机索引不要写成 Math.random() * (arr.length - 1);prompt 输入要转数字;猜对时 flag = false 要写在 break 前面,否则可能误弹“次数用完”;十六进制要循环 6 次,RGB 要生成三个不同随机数;拼接 src 时双引号要保留在字符串里。
课件图示








代码示例
示例一:综合案例——根据对象数组渲染精品推荐列表(学成在线)
js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>学成在线</title>
<link rel="stylesheet" href="./iconfont/iconfont.css">
<link rel="stylesheet" href="./css/base.css">
<link rel="stylesheet" href="./css/index.css">
</head>
<body>
<!-- 精品课程 -->
<script>
// 1. 利用对象数组的数据渲染页面
let data = [
{
src: './uploads/quality01.png',
title: 'JavaScript数据看板项目实战',
num: 1125
},
{
src: './uploads/quality02.png',
title: 'Vue.js实战——面经全端项目',
num: 2726
},
{
src: './uploads/quality03.png',
title: '玩转Vue全家桶,iHRM人力资源项目',
num: 9456
},
{
src: './uploads/quality04.png',
title: 'Vue.js实战医疗项目——优医问诊',
num: 7192
},
{
src: './uploads/quality05.png',
title: '小程序实战:小兔鲜电商小程序项目',
num: 2703
},
{
src: './uploads/quality06.png',
title: '前端框架Flutter开发实战',
num: 2841
},
{
src: './uploads/quality07.png',
title: '熟练使用React.js——极客园H5项目',
num: 95682
},
{
src: './uploads/quality08.png',
title: '熟练使用React.js——极客园PC端项目',
num: 904
},
{
src: './uploads/quality09.png',
title: '前端实用技术,Fetch API 实战',
num: 1516
},
{
src: './uploads/quality10.png',
title: '前端高级Node.js零基础入门教程',
num: 2766
},
{
src: './uploads/quality10.png',
title: '前端高级Node.js零基础入门教程',
num: 27669
}
]
// 2. 利用循环来做字符串拼接生成多个 li 标签,之后把生成的 li 标签放入容器 ul 里面渲染即可
let str = ''
for (let i = 0; i < data.length; i++) {
// console.log(data[i]) // 每个对象
str += `
<li>
<a href="#">
<div class="pic">
<img src="${data[i].src}" alt="">
</div>
<h4>${data[i].title}</h4>
<p><span>高级</span> · <i>${data[i].num}</i>人在学习</p>
</a>
</li>
`
}
console.log(str)
// 3. 把拼好的 li 字符串放进 ul 容器,整体输出到页面
document.write(`
<div class="course wrapper">
<div class="hd">
<h3>精品推荐</h3>
<a href="#">查看全部
<span class="iconfont icon-arrow-right-bold">
</span>
</a>
</div>
<div class="bd">
<ul class="common">
${str}
</ul>
</div>
</div>
`)
</script>
</body>
</html>要点解释 / 易错点:
data是对象数组,数组里每个对象对应页面上的一个li,页面结构只写一份,靠循环批量生成,数据变了页面自动变。let str = ''必须写在for循环外面。写在循环里面每次循环都会被重置成空串,最后只剩最后一个li。- 用模板字符串(反引号)配合
\${}取值:\${data[i].src}、\${data[i].title}、\${data[i].num},正好对应三个会变的地方(图片地址、标题、学习人数)。 src比较特殊:外面的双引号要正常写,把\${data[i].src}放进双引号里,写成src="\${data[i].src}",不要因为字符串本身带引号就省掉属性引号。- 循环条件用
i < data.length,不要写成<=,否则data[data.length]是undefined,会多渲染一个空li。 - 最后
document.write()里用\${str}把拼好的所有li塞进<ul class="common">容器中,一次性渲染。 - 字符串拼接要严谨,不要随意多敲空格(如
src=" ..."),浏览器可能容错,但代码不规范。
示例二:封装随机颜色函数(十六进制 / rgb 双格式)
js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>随机颜色</title>
</head>
<body>
<script>
// 不传参 / 传 true → 返回十六进制;传 false → 返回 rgb 格式
function getRandomColor(flag = true) {
if (flag) {
// 十六进制:# 开头 + 6 位 0~f 的字符
let arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']
let str = '#'
for (let i = 1; i <= 6; i++) {
let random = Math.floor(Math.random() * 16) // 0 ~ 15,正好是数组索引
str += arr[random]
}
return str
} else {
// rgb 格式:需要三个独立的 0~255 随机整数
let r = Math.floor(Math.random() * 256)
let g = Math.floor(Math.random() * 256)
let b = Math.floor(Math.random() * 256)
return `rgb(${r},${g},${b})`
}
}
console.log(getRandomColor()) // 不传参 → 十六进制
console.log(getRandomColor(true)) // 传 true → 十六进制
console.log(getRandomColor(false)) // 传 false → rgb
</script>
</body>
</html>要点解释 / 易错点:
- 形参默认值
flag = true:不传实参时形参就是true,自动走十六进制分支,省去了判空。 - 十六进制里含
a~f字符,Math.random()生成不了字符,所以要先准备一个 16 个字符的数组,用随机索引去取值。 Math.floor(Math.random() * 16)得到0~15,正好是数组的索引范围,取出来就是一位十六进制字符。str = '#'要声明在循环外面,循环 6 次每次str += arr[random],拼完 6 位再return str。- rgb 必须生成三个独立的随机数,不能共用一个变量,否则红绿蓝三值相同,颜色就固定了。
0~255要乘256(最大值 + 1),不是乘255,否则取不到 255。if分支里return一旦执行函数就结束了,两个return不会冲突。
示例三:从数组中随机抽取一个元素(随机显示姓名)
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>随机显示姓名</title>
</head>
<body>
<script>
// 五虎上将
let arr = ['关羽', '张飞', '赵云', '马超', '黄忠']
// 随机生成 0 ~ arr.length - 1 的整数,作为数组索引
let index = Math.floor(Math.random() * arr.length)
console.log(index)
// 用索引取出元素,显示到页面
document.write(arr[index])
</script>
</body>
</html>要点解释 / 易错点:
- 抽元素的核心是先拿到合法的索引号,再用
arr[index]取值。 - 通用公式:
Math.floor(Math.random() * arr.length),因为数组索引从 0 开始,到length - 1结束,正好用数组长度来乘,数组内容变了也不用改公式。 - 必须
Math.floor取整,Math.random()本身是小数,直接当索引会得到undefined。 - 不要写成
Math.random() * arr.length - 1或漏掉floor,那会越界或少取到最大值。 document.write()只适合这种练习页面,会直接往文档里写内容。
常见错误
1. 列表只渲染出最后一个 li
- 现象:页面上只有一条数据,或者
str打印出来只有一小段。 - 原因:字符串变量声明在循环体内(每轮被重置),或者写成
str = ...覆盖而不是str += ...。 - 修法:
let str = ''必须在循环之前声明;循环体内只用+=追加。判断技巧——变量"跨轮次累积"就一定要提到循环外面。
2. img 的 src 拼出来没有引号或多了空格
- 现象:图片不显示;或控制台 Elements 里
src值被浏览器"修正"成奇怪的东西。 - 原因:模板字符串里写成
src=\${data[i].src},属性值裸露;或src = "..."手滑多敲空格。 - 修法:属性值永远写全
src="\${data[i].src}";拼完后用 Elements 面板核对一次真实 DOM,不要只看页面"看起来对"。
3. 猜数字里"猜对了却一直让猜"或"猜对后仍弹出次数用完"
- 现象:输入正确值后弹窗继续;或者同时弹出"恭喜"和"次数已用完"。
- 原因:
break漏写;或开关变量flag = false写在了break的下面(break之后的语句不再执行)。 - 修法:顺序固定为「先改开关 → 再
break」,且break只负责跳出循环、不负责通知外部,两者缺一不可。
4. prompt 的返回值直接和数字比较
- 现象:输入正确数字却提示"猜大了/猜小了",或比较结果诡异。
- 原因:
prompt返回字符串,'5' > 10这类隐式转换与'5' === 5都为假,混用==/===时行为不一致。 - 修法:入口处立刻
let num = +prompt(...)或Number(...)转成数字,再进入比较逻辑;转换只做一次,别在分支里反复转。
5. 随机取数组元素取到 undefined
- 现象:偶尔渲染出
undefined,或抽名字时页面空白。 - 原因:区间公式少
+1,Math.floor(Math.random() * arr.length)本身是对的,但手改成* (max - min)或* 10时漏掉最大值,导致下标越界或永远取不到最后一项。 - 修法:统一记成
Math.floor(Math.random() * (max - min + 1)) + min;取数组元素时直接* arr.length,不要自己数长度。
6. 封装 getRandomColor 后不传参返回了 RGB
- 现象:
getRandomColor()期望十六进制,实际拿到rgb(...)。 - 原因:形参没给默认值,
flag是undefined,if (flag)判假直接进else。 - 修法:
function getRandomColor(flag = true);并且判断写成显式的if (flag)/else,不要依赖"空值恰好等于 false"。
7. 十六进制颜色只拼出最后一位
- 现象:返回
#f这种一位颜色,或#后只有一个字符。 - 原因:
let str = '#'写在了for循环里面,每次循环都被重置。 - 修法:累加型字符串一律在循环外初始化;同时确认循环次数是 6 次而不是数组长度 16 次。
8. 用 document.write 渲染,页面只剩列表
- 现象:点击某个按钮后再渲染,整个页面被冲掉,样式和其他模块全没了。
- 原因:
document.write在文档流关闭后调用会重写整个文档。 - 修法:真实项目一律用
容器.innerHTML = str;document.write只用于教学演示的一次性写入。
练习
练习 1|两种拼接方式对比 用 for 循环和 map().join('') 各写一遍学成在线列表渲染,把结果分别塞进两个 ul 的 innerHTML,用 Elements 面板对比生成的 DOM 是否完全一致。写一句话总结:为什么数组方法更不容易写出"覆盖式拼接"的 bug。
练习 2|猜数字·限定 5 次 + 剩余次数提示 把猜数字改为最多 5 次,猜对时提示"你用了 N 次猜对,还剩 M 次机会"。要求分别用两种写法实现: ① 开关变量 flag + break; ② 逻辑中断 flag && alert(...)。 最后故意在写法①里把 flag = false 挪到 break 后面,观察现象并解释原因。
练习 3|随机颜色函数升级 把 getRandomColor(flag) 改成接收字符串参数 getRandomColor(type = 'hex'),支持 'hex' / 'rgb',传入其他值或 undefined 时统一回退到十六进制。要求:颜色生成部分抽成两个小函数,主函数只负责分发。
练习 4|不重复抽名字 基于五虎上将数组做"抽一个少一个":每次随机抽出一个名字显示到页面,同时从数组中移除该元素;数组抽空后提示"已全部抽完"并停止。提示:注意移除后长度会变,随机公式里的长度必须是"当下"的长度。
练习 5|数据驱动验证 在学成在线数据里新增两条对象,不做任何 HTML 改动,只刷新页面确认列表变成 12 条;再故意把某条数据的 src 改成不存在的路径,观察页面表现,并说明为什么"改数据不改结构"是这个案例最大的价值。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 随机显示姓名案例 | 07:00 |
| 2 | 猜数字游戏 | 09:55 |
| 3 | 猜数字游戏限定次数 | 13:17 |
| 4 | 随机颜色案例-封装函数 | 07:41 |
| 5 | 随机颜色案例-返回rgb格式颜色 | 04:23 |
| 6 | 随机颜色案例-返回十六进制格式颜色 | 08:14 |
| 7 | 综合案例-学成在线页面渲染 | 13:24 |
小结
- 累加型变量的作用域决定了结果正确性:字符串拼接、颜色拼接、开关标记,只要"跨轮次"使用,就必须声明在循环外面,这是本组案例最高频的翻车点。
- 对象数组 + 循环 + 字符串拼接 = 数据驱动渲染的雏形:页面结构写一次、数据变多次,后续学 Vue/React 的列表渲染本质上还是这套思路的语法升级。
break与开关变量是两件事:break只终止循环,不向循环外传递"我成功了"这个信息;要靠flag之类的变量把状态带出去,且赋值必须在break之前。- 逻辑中断
flag && alert()是if的等价简化,但它只适合"条件为真就执行一句"的场景;条件分支多于两个时老老实实写if/else,可读性优先。 - 随机数公式要背成"长度思维"而不是"数字思维":数组用
* arr.length,区间用(max - min + 1) + min,后者是前者的推广,写错基本都是漏了+1。 - 函数用形参控制返回值,是"一个函数多形态"的最小实践:布尔参数能用但语义弱,字符串/枚举参数更接近真实 API 设计;默认参数保证"不传也能跑"。
- 渲染入口要选对:
document.write是演示工具,innerHTML才是工程手段;调试时看 Elements 里的真实 DOM,而不是只看页面"像不像"。 - 写这类案例的正确节奏是「先结构、再数据、最后换活数据」:先用死数据拼通结构并
console.log验证条数,再逐个替换可变字段,比一上来就全动态化更容易定位错误。