主题
综合案例:天气预报
本页对应 1-10综合案例-天气预报,共 5 段视频,约 54 分钟。
学习目标
- 能够封装可复用的天气查询函数,接收城市编码作为参数并调用接口获取对应城市天气数据。
- 能够根据接口返回数据,使用模板字符串、
map和join完成当前天气、今日天气及 7 日天气预报的页面渲染。 - 能够为搜索框注册
input事件,并结合防抖处理实现按输入关键字查询城市列表。 - 能够将城市列表数据渲染为列表项,并通过
data-*自定义属性保存城市编码。 - 能够使用事件委托为动态生成的城市列表绑定点击事件,读取
dataset中的城市编码并复用查询函数更新页面天气。
知识点
项目目标:先让北京天气默认显示

这个案例的完整需求有三步:先获取北京市天气预报并展示;再根据搜索框查询城市列表并展示;最后点击城市,切换显示对应天气数据。本节先完成“默认数据”这条主线:封装查询函数、查询北京天气,并把拿到的数据设置到页面上。转写稿里也明确说,本节先做前两步,渲染逻辑紧接着完成;后面城市查询和点击切换,都是在这个查询函数上继续复用。
为什么需要:一打开页面就要看到北京天气,用户不必先搜索;同时还能检验前面封装的 myAxios 是否好用。
怎么想:查北京和查其他城市的逻辑几乎一样,只是城市编码不同,所以先把“查天气”封装成函数,页面初始化时调用一次。
怎么做:定义 searchWeather(city),内部调用 myAxios,按接口文档传 url、method: 'get'、params: { city }。
易错点:city 不能写死;北京传的是城市编码 110100,不是“北京”两个字;编码要按文档用字符串。
js
function searchWeather(city) {
return myAxios({
url: '天气预报接口地址',
method: 'get',
params: { city }
})
}
searchWeather('110100').then(res => {
console.log(res)
})调用函数查询北京默认数据
为什么需要:页面初始化时必须主动发起一次请求,否则页面不会自动出现北京天气。
怎么想:函数已经封装好,默认查询北京只需要在函数定义后调用,并传入北京编码。
怎么做:在控制台先打印 res,展开后看 data,确认里面有北京市、天气、温度等信息。转写稿中的调试过程就是:保存后切到页面,控制台出现“查询天气成功”,再展开 res.data 找到北京相关数据。
易错点:不要直接把整个 res 当天气数据用;真正要渲染的是 res.data。接口文档里城市参数名是 city,位置是 query,所以要放在 params 里。
js
searchWeather('110100').then(res => {
const WD = res.data
console.log(WD)
})把数据渲染到页面:类名与属性名一致
为什么需要:只查到数据还不够,用户要看的是页面上的城市名、日期、温度、天气、风向风力等。
怎么想:页面结构复杂时不能着急,要“一个一个来,写一个验证一个”。转写稿里特别强调:本案例提前做了准备——要使用数据的标签类名,和接口返回的属性名一致,因此选到标签后可以直接用同名属性替换。
怎么做:先用 document.querySelector 找到标签;文本用 innerText,带标签的结构用 innerHTML。例如城市名先选 .area,再赋值为 WD.area。日期区域可以先复制内部结构作为模板字符串,先改一个 1234 验证页面会变,再把 WD.date 等数据填进去。
易错点:不要复制最外层盒子,只复制内部结构;innerText 和 innerHTML 不要混用;模板字符串里替换数据时注意属性名和层级,别把 WD 写成别的对象。
js
const WD = res.data
document.querySelector('.area').innerText = WD.area
const dateStr = `页面日期区域的内部结构,用 ${WD.date} 等数据替换`
document.querySelector('.title').innerHTML = dateStr七日预报:数组用 map + join
为什么需要:下半部分是未来七天,每天结构相同,不能手写七遍。
怎么想:接口里七日天气是数组,数组生成多个 HTML 结构,应该用 map 映射,再用 join('') 拼成一个字符串。转写稿中先确认 data.dayForecast 里正好 7 条数据,再复制一个 li 的结构作为模板。
怎么做:WD.dayForecast.map(v => 模板字符串).join(''),最后把结果赋给对应 ul 的 innerHTML。替换时用 v.dateFormat、v.weather、温度、风向风力等字段。
易错点:数据层级别取错,是 WD.dayForecast;join 要传空字符串,避免多余符号;复制模板时不要多带外层 ul;先 console.log 拼好的字符串,确认无误再渲染。
js
const weekStr = WD.dayForecast.map(v => {
return `<li>用 v.dateFormat、v.weather 等字段替换后的结构</li>`
}).join('')
document.querySelector('.week').innerHTML = weekStr小结与易错点
本节的核心不是“背代码”,而是建立默认数据的处理顺序:封装查询函数 → 调用函数拿北京数据 → 从 res.data 取数据 → 按页面结构逐块渲染 → 七日预报用 map + join。转写稿中的调试方法很值得保留:每替换一块就看一次页面,控制台先打印数据再渲染;复杂结构不要一次性写完。常见同音字也要纠正:myAxios 不是“黑马ex”,innerHTML 不是“in the mail”,innerText 不是“in the test”,lodash 不是“low大学”。本节先把北京默认天气跑通,后面城市查询和点击切换才有稳定基础。
本节要做什么
这一节完成天气预报案例中的“搜索城市列表”:在输入框里输入关键字后,根据关键字请求城市信息,并把匹配的城市渲染成列表。效果如下:

笔记里把步骤概括为两步:绑定 input 事件获取关键字;获取并展示城市列表数据。转写稿补出了中间关键推导:input 会随着每次输入触发,不能每次都直接请求接口,否则浪费性能,所以要先做函数防抖,再请求、再渲染。
为什么监听 input 事件
为什么需要:用户输入或删除内容时都要重新查询城市,input 事件正好能在输入框内容变化时触发。
怎么想:先拿到输入框元素,注册 input,在事件处理函数里通过 this.value 取关键字。这里用普通函数而不是箭头函数,是为了让 this 指向输入框。
怎么做:
js
document.querySelector('.search-input').addEventListener('input', function () {
console.log(this.value)
})易错点:不要写成箭头函数后还期待 this 是输入框;调试时先在控制台确认每次输入都能打印值,再继续往下写。
为什么先防抖再请求
为什么需要:转写稿里明确提醒,用户连续打字时 input 会一直触发,如果里面直接调接口,会造成性能浪费。
怎么想:用户停止输入后稍等一会儿再查,这就是函数防抖。课程里用 lodash 的 _.debounce 实现(转写稿里的“楼大学”是 lodash,“抵bs”是 debounce)。
怎么做:把真正要执行的函数包成防抖函数,再绑定给 input:
js
const searchCity = _.debounce(function () {
// 这里再请求城市接口
console.log(this.value)
}, 1000)
document.querySelector('.search-input').addEventListener('input', searchCity)易错点:防抖后的返回值要重新用于 addEventListener;延迟时间不要设得太长,转写稿中一开始设 2000 毫秒,后来调成 1000 毫秒更合适;防抖函数内部如果要用 this.value,仍要注意 this 的指向。
怎么请求并渲染城市列表
为什么需要:关键字只是输入,真正要展示的是服务器返回的城市数组。
怎么想:调用课程封装好的请求函数,传查询参数 city,拿到 res.data 后,用 map 生成多个 li,再用 join('') 拼成字符串,最后通过 innerHTML 放到列表容器里。转写稿调试时输入“北”,控制台能看到北京、河北、北辰、北戴河;网络面板也能确认请求参数是 city=北。
怎么做:核心结构如下(请求函数名按你课程里实际封装的名称替换):
js
const res = await hmAxios({
url: '城市信息接口地址',
params: {
city: this.value
}
})
const str = res.data.map(v => {
return `<li class="city-item" data-code="${v.code}">${v.name}</li>`
}).join('')
document.querySelector('.search-list').innerHTML = str易错点:数据在 res.data 里,不要直接拿整个响应;渲染容器是 ul.search-list,不是输入框;列表默认可能被样式隐藏,调试时可在元素面板临时取消 display: none 观察;每个 li 要顺手存 data-code,因为后面点击城市查天气时要用城市编码,而不是城市名字。
调试顺序与易错点
转写稿的调试过程很值得照做:先只打印 this.value,确认 input 绑定成功;再加防抖,确认连续输入时只在停止后打印一次;再请求接口,确认参数和返回数据;最后渲染,先在控制台打印拼好的字符串,再赋值给 innerHTML。常见易错点还有:把 dataset 写成 data-set;判断点击目标时用 e.target.classList.contains('city-item'),而不是直接比较整个 className;自定义属性读取用 e.target.dataset.code。本节先把搜索列表跑通,后面点击城市查天气时,直接取这里存下的 code 即可。
小结
监听输入框实时改变的事件是 input 事件。实现“搜索城市列表”时,推荐顺序是:绑定 input 获取关键字 → 用 _.debounce 做防抖 → 按关键字请求城市数据 → 用 map + join 渲染列表,并保存城市 code 供后续使用。
本节要完成的效果
本节要完成的是:在搜索框输入城市名后,页面会展示城市列表;点击列表中的某个城市,就切换为该城市的天气数据。效果示意如下:

从转写稿的演示看,默认页面先展示北京天气;在右上角搜索框输入内容后,会出现城市候选列表;点击“广州”“武汉”等城市项,页面天气随之切换。核心步骤只有两步:检测搜索列表的点击事件并获取城市 code,然后复用前面已经封装好的查询并展示城市天气的函数。
知识点一:给动态城市列表绑定点击事件
为什么需要:城市列表不是页面一开始就写死的,而是根据搜索关键字动态生成的。动态生成的 li 如果逐个绑定点击事件,代码麻烦,而且列表重新渲染后还要重新绑定。因此更适合用事件委托:把点击事件绑在始终存在的父容器 ul 上,再判断真正点击的是不是城市项。
怎么想:点击“谁”,就查“谁”的天气。父容器 ul 一直存在,所以事件绑在 ul 上;事件触发后,通过事件对象 e.target 找到实际触发元素,再判断它是否包含城市项的类名。
怎么做:在转写稿中,先找到搜索列表的 ul,注册 click 事件,并在回调里判断 e.target 的类名:
js
document.querySelector('搜索列表类名').addEventListener('click', (e) => {
if (e.target.classList.contains('城市项类名')) {
console.log('点击了 li 标签')
}
})调试时,转写稿先输入“北”得到城市列表,点击城市项,控制台能打印;再点击列表外区域,不打印,说明事件范围限制正确。
易错点:不要给动态生成的每个 li 直接绑事件;判断类名时不要漏掉 classList.contains;如果城市项内部还有子元素,e.target 可能不是 li 本身,需要按实际结构确认判断目标。
知识点二:从被点击项取出城市 code
为什么需要:查询天气时,后端要求传递的不一定是城市名字,而可能是城市编码。转写稿中查询北京天气传的是 110100 这样的数字字符串;上一节渲染城市列表时,已经把每个城市的 code 存进了 li 的自定义属性 data-code。所以点击后要把这个 code 取出来。
怎么想:点击事件里已经能拿到被点击的 li,而 li 上有 data-code,就可以通过 dataset 读取。转写稿中强调:上一节存了 data-code,这一节才能拿到;不存就拿不到。
怎么做:在点击判断内部解构出 code:
js
const { code } = e.target.dataset
console.log(code)调试时,点击“上海”会打印一个对象,里面有 code 属性;再点其他城市,code 值会变化,说明取到的是对应城市编码。
易错点:data-code 对应的是 dataset.code,不要写成 dataset.dataCode;要确认上一节渲染列表时确实写入了 data-code;不要误以为传城市中文名一定可以,笔记小结明确说:看后端要求传递什么,就传什么。
知识点三:复用查询天气函数切换天气
为什么需要:查询北京天气和查询其他城市天气,逻辑基本一致:调接口、拿数据、渲染页面。前面已经封装了 searchWeather,这里不应重新写一套查询逻辑,否则重复且难维护。
怎么想:点击城市项拿到 code 后,直接调用已有函数,把 code 传进去。这样“点谁查谁”,简单高效。
怎么做:
js
const { code } = e.target.dataset
searchWeather(code)转写稿中验证:搜索“广州”并点击,广州天气被查出;再搜索“武汉”并点击,武汉天气也切换成功。说明复用函数有效。
易错点:不要传城市名字代替 code;不要忘记调用函数;要保证 searchWeather 的参数就是后端要求的城市编码;点击列表项后如果页面没变,先检查 code 是否取到、函数是否被调用。
小结
本节的关键是“事件委托 + 自定义属性 + 复用函数”。动态列表用事件委托绑定点击;点击后通过 dataset.code 取出上一节存下的城市编码;最后调用已封装的 searchWeather(code) 切换天气。至于传给后端的是城市名字还是 code,以接口文档和后端要求为准。
课件图示



代码示例
示例 1:封装 searchWeather 并默认查询北京天气
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>天气预报 - 默认查询北京</title>
<style>
body { font-family: system-ui, sans-serif; padding: 24px; }
.weather-box span { display: inline-block; margin-right: 16px; padding: 6px 10px;
background: #f2f6ff; border-radius: 6px; }
</style>
</head>
<body>
<h3>今日天气</h3>
<div class="weather-box">
<span class="area">--</span> <!-- 城市名 -->
<span class="date">--</span> <!-- 日期 -->
<span class="temperature">--</span> <!-- 温度 -->
<span class="weather">--</span> <!-- 天气 -->
<span class="pm25">--</span> <!-- PM2.5 -->
</div>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
/* 1. 自己封装的 myAxios:返回后端返回的整个对象 { code, message, data } */
async function myAxios(config) {
const res = await axios(config); // axios 的 res.data 才是后端数据
return res.data;
}
/* 2. 接口地址(以接口文档为准) */
const WEATHER_URL = 'https://hmajax.itheima.net/api/weather';
/* 3. 封装查询天气函数:city 是形参,不写死,方便后面复用 */
async function searchWeather(city) {
const res = await myAxios({
url: WEATHER_URL,
method: 'get', // get 可以省略
params: { city } // 查询参数叫 city,值是城市编码
});
console.log(res);
const wData = res.data; // 真正要用的数据在 res.data 里
console.log(wData);
/* 4. 渲染:类名与属性名一一对应,直接取值填充 */
const fill = (selector, value) => {
const el = document.querySelector(selector);
if (el) el.innerHTML = value ?? '--';
};
fill('.area', wData.city);
fill('.date', wData.date);
fill('.temperature', wData.temperature);
fill('.weather', wData.weather);
fill('.pm25', wData.pm25);
}
/* 5. 页面一打开就查北京:传的是城市编码 110100,不是"北京"两个字 */
searchWeather('110100');
</script>
</body>
</html>关键行与易错点
myAxios封装返回的是res.data(后端的整个响应体),所以函数里还要再取一层res.data才是业务数据,两层data别混。searchWeather(city)里city必须是形参,不能写死成'110100',否则后面城市切换时无法复用。- 请求参数走
params(查询参数),不是data(请求体),这是本项目最容易写错的地方。 - 北京要传的是城市 code
110100(字符串),不是城市名;具体传名字还是 code 由接口文档决定,其他城市的 code 后面搜索列表接口会返回。 - 先
console.log打印确认数据结构,再写渲染;字段含义以接口文档为准。
示例 2:输入关键字搜索城市列表(input + 防抖 + map/join 渲染)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>天气预报 - 城市搜索列表</title>
<style>
body { font-family: system-ui, sans-serif; padding: 24px; }
.search { width: 260px; padding: 8px 10px; border: 1px solid #ccc; border-radius: 6px; }
.search-list { list-style: none; padding: 0; width: 260px; margin: 8px 0 0;
border: 1px solid #ddd; border-radius: 6px; overflow: hidden; }
.search-list .item { padding: 8px 10px; cursor: pointer; }
.search-list .item:hover { background: #f2f6ff; }
</style>
</head>
<body>
<input type="text" class="search" placeholder="请输入城市名,如:北" />
<ul class="search-list"></ul>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash/lodash.min.js"></script>
<script>
async function myAxios(config) {
const res = await axios(config);
return res.data;
}
const CITY_URL = 'https://hmajax.itheima.net/api/city'; // 以接口文档为准
const searchInput = document.querySelector('.search');
const searchList = document.querySelector('.search-list');
/* 1. 真正的业务逻辑:查城市 + 渲染列表 */
async function searchCity() {
const res = await myAxios({
url: CITY_URL,
method: 'get',
params: { city: this.value } // this 是 input 元素,所以下面用普通函数
});
console.log(res);
/* 2. 数组 → HTML 字符串:map 生成 + join 拼接 */
const str = res.data.map((v) => {
return `<li class="item" data-code="${v.code}">${v.name}</li>`;
}).join('');
searchList.innerHTML = str;
}
/* 3. 用 lodash 的 debounce 做函数防抖,避免边打字边发请求 */
const debounceSearch = _.debounce(searchCity, 1000);
/* 4. 监听 input 事件(输入、删除内容都会触发) */
searchInput.addEventListener('input', debounceSearch);
</script>
</body>
</html>关键行与易错点
- 监听的是
input事件,不是change——change要等失焦才触发,做不到实时搜索。 - 必须用普通函数
function () {}而不是箭头函数,否则this.value拿不到输入框的值(lodash 的debounce会保留调用时的this)。 - 防抖必须包在
addEventListener外面:_.debounce(fn, 1000)返回的是新函数,addEventListener('input', _.debounce(...))每次也是新函数,虽然能跑但语义混乱,且无法复用。 - 每个
<li>上要偷偷存一份data-code="${v.code}",用户看不见,但点击切换城市时全靠它。 map只负责生成数组,必须join('')才能拼成字符串赋给innerHTML,否则中间会出现逗号。- 数据里的属性名(
name/code)以打印结果或接口文档为准,别凭印象写。
示例 3:点击城市列表切换天气(事件委托 + 复用查询函数)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>天气预报 - 点击切换城市</title>
<style>
body { font-family: system-ui, sans-serif; padding: 24px; }
.search { width: 260px; padding: 8px 10px; border: 1px solid #ccc; border-radius: 6px; }
.search-list { list-style: none; padding: 0; width: 260px; margin: 8px 0 0;
border: 1px solid #ddd; border-radius: 6px; overflow: hidden; }
.search-list .item { padding: 8px 10px; cursor: pointer; }
.search-list .item:hover { background: #f2f6ff; }
.weather-box span { display: inline-block; margin: 12px 16px 0 0; padding: 6px 10px;
background: #f2f6ff; border-radius: 6px; }
</style>
</head>
<body>
<input type="text" class="search" placeholder="请输入城市名,如:广州" />
<ul class="search-list"></ul>
<div class="weather-box">
<span class="area">--</span>
<span class="temperature">--</span>
<span class="weather">--</span>
</div>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash/lodash.min.js"></script>
<script>
async function myAxios(config) {
const res = await axios(config);
return res.data;
}
const WEATHER_URL = 'https://hmajax.itheima.net/api/weather';
const CITY_URL = 'https://hmajax.itheima.net/api/city';
const searchInput = document.querySelector('.search');
const searchList = document.querySelector('.search-list');
/* ===== 1. 查询并渲染天气(示例 1 封装的函数,这里直接复用) ===== */
async function searchWeather(city) {
const res = await myAxios({ url: WEATHER_URL, method: 'get', params: { city } });
const wData = res.data;
const fill = (s, v) => {
const el = document.querySelector(s);
if (el) el.innerHTML = v ?? '--';
};
fill('.area', wData.city);
fill('.temperature', wData.temperature);
fill('.weather', wData.weather);
}
/* ===== 2. 查询城市列表 ===== */
async function searchCity() {
const res = await myAxios({ url: CITY_URL, method: 'get', params: { city: this.value } });
searchList.innerHTML = res.data
.map((v) => `<li class="item" data-code="${v.code}">${v.name}</li>`)
.join('');
}
searchInput.addEventListener('input', _.debounce(searchCity, 1000));
/* ===== 3. 事件委托:给 ul 绑点击,动态生成的 li 也能响应 ===== */
searchList.addEventListener('click', (e) => {
if (e.target.classList.contains('item')) { // 限定只有点 li 才触发
const { code } = e.target.dataset; // 取出 data-code
searchWeather(code); // 复用第一步的函数
searchList.innerHTML = ''; // 选完收起列表(可选)
}
});
/* ===== 4. 页面打开先展示北京 ===== */
searchWeather('110100');
</script>
</body>
</html>关键行与易错点
li是动态渲染出来的,直接给它绑事件会失效或绑不到,必须用事件委托:绑在始终存在的ul上。- 一定要写
e.target.classList.contains('item')做判断,否则点ul的空白区域、或点到别的元素也会触发查询。 e.target.dataset.code读取的就是<li data-code="440100">里存的编码,前提是示例 2 里真的存进去了,没存就取到undefined。- 拿到
code后直接调用searchWeather(code),这就是前面"封装函数方便复用"的回报,不要再复制一遍请求逻辑。 - 传进去的是 code 编码而不是城市名——和示例 1 保持一致;具体传什么由接口文档决定。
常见错误
1. 请求成功,页面却全是 undefined
- 现象:Network 里 200,控制台打印数据正常,但城市名、温度位置空白或显示 undefined。
- 原因:模板字符串里的属性名写错(大小写、拼错),或者把「类名 = 属性名」的巧合当成规律,直接抄了类名却抄错层级。
- 修法:先在控制台把
res.data逐层展开,确认字段真实路径(如data.todayWeather.temDay);再对照接口文档确认含义;每替换一个字段就保存看一次页面,别整段写完再调。
2. this.value 打印出 undefined
- 现象:input 事件能触发,但拿不到输入框内容。
- 原因:回调写成了箭头函数,
this指向 window 而非 input 元素。 - 修法:回调改回普通
function,或干脆用e.target.value,不依赖 this。
3. 城市列表点了没反应,或点空白处也触发
- 现象:列表渲染正常,点击无响应;或者点到 ul 的缝隙也执行了查询。
- 原因:把 click 直接绑在动态生成的 li 上(绑定时元素还不存在);或没有做目标元素判断。
- 修法:事件委托绑在始终存在的 ul 上,回调里用
e.target.classList.contains('li类名')过滤后再执行。
4. 点击城市后查到的还是北京 / 报错
- 现象:点上海,页面仍是北京天气;或
searchWeather(undefined)。 - 原因:渲染列表时漏写
data-code自定义属性;或读取时写成e.target.code、dataset.Code(大小写敏感)。 - 修法:生成时写
data-code="${v.code}",读取用e.target.dataset.code;先console.log(e.target.dataset)确认真的存进去了。
5. 输入「北京」发了十几次请求
- 现象:Network 面板请求刷屏,接口偶发限流或报错。
- 原因:没做防抖,input 每敲一个字符触发一次。
- 修法:用
_.debounce(fn, 300~1000)包一层,注意把返回值赋给新变量再注册,不能直接调原函数。
6. 拼接后列表项之间多出逗号 / 标签变成纯文本
- 现象:页面上出现
</li>,<li>的逗号;或整段<li>被当文字显示。 - 原因:
map返回数组没join('')就赋值;或用了innerText而不是innerHTML。 - 修法:
.map(...).join('');需要解析标签一律用innerHTML。
7. 渲染后页面结构错乱、盒子套盒子
- 现象:样式全崩,控制台元素里出现重复的外层容器。
- 原因:复制模板时把最外层容器标签也一起复制进去了。
- 修法:只复制容器内部结构,外层用
querySelector找到后整体替换innerHTML。
练习
- 换默认城市:把页面打开时默认查询的城市从北京改成上海。要求先说清楚上海的 code 从哪里来,再动手改,并解释为什么不能直接写「上海」两个字。
- 验证防抖:把搜索防抖延迟设为 500ms,在 Network 面板连续快速输入「北京市」三个字,数一数实际发出了几次请求;再把延迟改成 0 对比一次,记录差异。
- 点击后收起列表:点击某个城市查出天气后,自动清空搜索框并让城市列表隐藏,同时保证再点输入框时列表能重新出现。
- 只渲染三天:7 日天气预报改为只显示前 3 天,其余不动。思考改哪一行最省事。
- 兜底显示:假设接口某天
weather字段返回空字符串,页面上该显示--而不是空白。请在不改动数据结构的前提下实现。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 案例-天气预报-查询北京天气 | 08:39 |
| 2 | 案例-天气预报-渲染北京01 | 11:07 |
| 3 | 案例-天气预报-渲染北京02 | 11:24 |
| 4 | 案例-天气预报-城市查询 | 14:20 |
| 5 | 案例-天气预报-点击查询城市 | 08:51 |
小结
- 查询天气的逻辑一定要先封装成带参数的函数,后面「点谁查谁」才能一行调用,否则就是复制粘贴两遍代码。
- 传城市名还是传 code,完全由接口文档决定,不要凭直觉;code 不用背,查城市列表时服务器会一并返回。
- 渲染复杂页面没有捷径:一个字段一个字段来,改一处验一处,比一口气写完再调试快得多。
- 「类名 = 属性名」只是本案例特意做的便利设置,真实项目里往往对不上,对不上就以接口文档为准。
- 凡是动态生成的元素,事件一律用委托绑在始终存在的父级上,这是通用做法而不是本例的特殊技巧。
- 点击时才需要的数据(如 code),用
data-*属性藏在 DOM 上,用户看不见、代码随时取。 - 实时搜索必须防抖,这不是优化项而是必做项;延迟时间取 300~1000ms 之间比较符合手感。
- 渲染列表的固定套路是「模板字符串 + map + join + innerHTML」,四步缺一不可。