主题
2-12日期对象及倒计时案例
本页对应 第二章WebAPIs,共 7 段视频,约 55 分钟。
学习目标
- 能够使用
new Date()实例化日期对象,获取当前日期时间或指定日期时间,并说明new实例化的作用。 - 能够调用
getFullYear()、getMonth()、getDate()、getDay()、getHours()、getMinutes()、getSeconds()格式化日期时间,并正确处理月份+1、星期0–6的取值规则。 - 能够封装日期时间格式化函数,完成时分秒补零,并结合定时器将动态日期时间显示到页面指定元素中。
- 能够使用
toLocaleString()、toLocaleDateString()、toLocaleTimeString()按不同需求输出完整日期时间、日期或时间。 - 能够使用
getTime()、+new Date()、Date.now()三种方式获取时间戳,并说明推荐写法及适用场景。 - 能够利用“将来时间戳 - 当前时间戳”计算剩余时间,转换为时分秒并写入页面,结合定时器完成自动倒计时案例。
知识点
插件不是“神秘代码”,而是可复用的现成方案
为什么需要:实际开发里很多效果别人已经写好了,比如轮播、手势滑动。我们不必从零造轮子,把成熟代码接进来,能更快完成需求。笔记给的定义很直接:插件就是别人写好的一些代码,我们复制对应代码,就可以直接实现对应效果。
怎么想:先把插件当成“带使用说明的工具”,不要一上来就改源码。先判断它能不能解决当前需求,再按文档跑通最小示例,最后才放进项目。
怎么做:学习插件按笔记路线走:先看官网了解能完成什么需求,再看基本使用流程,然后写个小 demo 对照在线演示,最后应用到开发中。
易错点:不要只看效果图就直接复制一大段代码;也不要跳过 demo 直接进项目。插件版本、引入顺序、元素是否存在,都会影响结果。
学插件的固定路线:官网→流程→demo→项目
为什么需要:插件文档通常包含依赖、引入方式、初始化配置和示例。按固定路线看,能减少“代码抄了但不知道哪一步错”的情况。
怎么想:笔记给了 Swiper 的路线:官网看需求,usage 看基本使用流程,demo 找符合需求的在线演示。这个顺序适合大多数插件。
怎么做:先打开官网 https://www.swiper.com.cn/ 了解能力;再看 https://www.swiper.com.cn/usage/index.html 了解流程;然后到 https://www.swiper.com.cn/demo/index.html 找 demo;最后才放到开发中。
易错点:官网首页往往只是展示,真正要读的是“使用/文档”和“demo”。不要拿展示页代码当完整接入代码。
AlloyFinger:轻量级 Web 手势插件
为什么需要:移动端经常要识别滑动等手势。AlloyFinger 是腾讯 AlloyTeam 开源的超轻量级 Web 手势插件,可以为元素注册各种手势事件。
怎么想:先拿到库文件,再在页面引入,最后对目标元素做初始化配置。笔记给出的 GitHub 地址是 https://github.com/AlloyTeam/AlloyFinger。
怎么做:下载 alloy_finger.js,在当前文件引入;也可以使用在线地址。注意笔记里 <scriptsrc=...> 是排版笔误,实际应写成 <script src=...>:
html
<script src="alloy_finger.js"></script>
<!-- 或使用在线地址 -->
<script src="https://unpkg.com/alloyfinger@0.1.16/alloy_finger.js"></script>易错点:脚本要在使用 AlloyFinger 之前引入;路径不要写错;在线地址要能访问。元素还没渲染就初始化,也会导致绑定不到。
配置手势事件:new AlloyFinger(element, {...})
为什么需要:引入库只是准备工具,真正让某个元素响应滑动,还要告诉插件“给哪个元素、做什么事”。
怎么想:第一个参数 element 是目标元素;第二个参数是配置对象。笔记示例里用 swipe 处理滑动,用 e.direction 判断方向。
怎么做:保留笔记中的配置代码:
javascript
new AlloyFinger(element, { // element 是给哪个元素做滑动事件
swipe: function (e) {
// 滑动的时候要做的事情 e.direction 可以判断上下左右滑动 Left Right 等
}
})易错点:element 要传真实 DOM 元素,不是选择器字符串;swipe 拼写和大小写要按文档;方向值如 Left、Right 等要以文档或 demo 为准,不要凭感觉写。
接入后没效果怎么排查
为什么需要:插件接入失败时,页面往往只是“没反应”,如果不看控制台,很容易乱改代码。
怎么想:转写稿里页面时间不显示时,先右击检查、打开控制台,按报错行号定位,再读错误信息,发现变量声明后又被重新赋值,于是把 const 改成 let 后恢复。这个排查顺序同样适用于插件:先看控制台,再看引入,再看元素,最后看配置。
怎么做:按顺序查:控制台有无报错;alloy_finger.js 是否成功加载;new AlloyFinger 是否在库引入之后;element 是否获取到;配置字段是否写对。转写稿还提到“页面一打开先执行一次,避免一秒空白期”,这提醒我们:初始化时机要覆盖首次展示,不要只等后续事件。
易错点:不要一看到没效果就删代码;先读错误信息。变量该用 const 还是 let 要看是否重新赋值,转写稿中把 const 改成 let 就是典型调试过程。
小结:插件学习的关键是“读文档+最小 demo+按需改”
插件学习的核心不是背代码,而是按笔记路线:看官网确认需求,读使用流程,跑最小 demo,再放进项目。AlloyFinger 的接入可以概括为:下载或在线引入库,取得目标元素,用 new AlloyFinger(element, { swipe(e) { ... } }) 注册手势。出现问题时,用控制台定位,而不是猜。这样既保留插件原有能力,也能把调试过程变成自己的经验。
综合案例要解决什么问题
这一节的综合案例是“移动端通讯录”:把联系人数据渲染到页面中,姓名最后一个字取出来做头像,后面显示手机号;联系人可以滑动出现删除按钮,点击删除时弹出确认框,取消不删、确定才删;还可以输入姓名和手机号添加联系人。转写稿里说的“do目节点”应纠正为 DOM 节点,“GS”应纠正为 JS。案例的核心不是拼命操作 DOM,而是先把数据管好,再根据数据渲染页面。
遍历数组forEach:把数据逐条变成页面结构
为什么需要: 通讯录里有多条联系人数据,如果手写每个联系人的结构,数据一多就无法维护;新增、删除后还要重新显示列表,所以需要“逐条遍历数组,再生成对应内容”。
怎么想: forEach 的回调函数会拿到两个关键参数:element 是当前数组元素,index 是当前元素的索引号。渲染联系人时,用 element 取姓名、手机号;用 index 定位当前是第几条数据,便于后续删除等操作。
怎么做: 笔记给出的基本结构是:
JavaScript
arr.forEach(function (element, index) { /* … */ })在综合案例中,就是对联系人数组调用 forEach,每遍历一个联系人,就根据它的姓名、手机号生成一条页面内容;头像则继续用下面的字符串截取处理姓名。
易错点: 不要把 element 和 index 的顺序写反;forEach 主要用于遍历执行,不要把它当成有返回值的 map 来接收结果;索引从 0 开始;新增或删除联系人后,要重新根据数组渲染,不能只改页面不改数据。
字符串截取substring:取出姓名最后一个字做头像
为什么需要: 通讯录头像不一定要图片,转写稿里的做法是把姓名最后一个字拿出来显示在前面,作为头像文字。这就需要从姓名字符串中截取最后一个字。
怎么想: substring 接收起始索引号和可选的结束索引号。字符串索引从 0 开始,最后一个字的起始索引就是“姓名长度减一”;结束索引可以省略,表示截取到末尾。
怎么做: 笔记给出的语法是:
JavaScript
字符串.substring(起始索引号, [结束索引号])落到案例里,就是拿到联系人姓名字符串后,把起始索引设为最后一个字的位置,结束索引省略,从而得到头像文字。
易错点: 索引从 0 开始,不要误以为长度就是最后一位索引;substring 的结束索引不包含在结果里;姓名很短或为空时要考虑边界;头像取错时,先检查起始索引是不是“长度减一”。
思想转变:操作数据,再渲染视图
为什么需要: 如果增加联系人就直接往页面里塞一个 DOM 节点,删除就直接删 DOM 节点,代码会越来越散,数据和页面也容易对不上。笔记特别强调:本次案例要尽量减少 DOM 操作,采取“操作数据”的形式,为后期 Vue 做铺垫。
怎么想: 增加和删除都先针对数组操作:添加联系人就是把新联系人加入数组;删除联系人就是按索引从数组中移除。数组变了以后,再根据数组数据重新渲染页面,这就是“数据驱动视图”。
怎么做: 把联系人数组当作唯一数据源。渲染时用 forEach 遍历数组生成列表;添加时先改数组再渲染;删除时先确认,确认后改数组再渲染。页面只是数组的“投影”。
易错点: 不要一边改数组一边又手动增删 DOM,否则容易出现页面有、数据没有,或数据有、页面没有;重新渲染时要注意旧内容处理,避免列表重复追加;删除前要保留确认框,转写稿明确说取消不删除、确定才删除。
事件委托:减少注册次数,也照顾新增元素
为什么需要: 如果给每个联系人的删除按钮都注册一次事件,联系人越多注册次数越多;更麻烦的是,新增的联系人是后来出现的,之前绑在旧元素上的事件管不到它。笔记指出事件委托有两个重要作用:减少注册次数、给新增元素注册事件。
怎么想: 把事件注册在联系人列表的父容器上,利用事件冒泡,点击子元素时事件会冒泡到父容器;在父容器的事件处理里判断真正点击的是不是删除按钮,再执行删除。
怎么做: 父容器统一监听点击事件,处理时识别目标元素;删除仍然走“确认框→操作数组→重新渲染”的流程。新增元素不需要单独再绑事件,因为它也在父容器范围内。
易错点: 要判断点击目标,不能点列表空白也触发删除;删除按钮的识别方式要稳定;新增后删除没反应时,先检查是不是还采用“给每个旧元素单独绑定”的写法,改成事件委托即可覆盖新增元素。
综合案例的落地顺序与检查
落地时先准备联系人数组并渲染列表;头像用 substring 取姓名最后一个字;删除按钮点击后弹确认框,取消不删,确定后按索引操作数组并重新渲染;添加联系人时读取姓名和手机号,加入数组后重新渲染。调试时重点查三处:头像不对,查 substring 起始索引;新增后删除无效,查是否用了事件委托;列表重复或数据与页面对不上,查是否坚持“先操作数组,再根据数组渲染页面”。这样就把 forEach、substring、数据驱动视图和事件委托串成了一个完整案例。
课件图示








代码示例
示例一:页面自动显示日期和时间(日期对象 + 补零 + 定时器)
js
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>页面显示日期和时间</title>
<style>
.box {
width: 420px;
height: 60px;
line-height: 60px;
margin: 100px auto;
text-align: center;
font-size: 20px;
color: #fff;
background: #ff6700;
}
</style>
</head>
<body>
<div class="box"></div>
<script>
// 1. 封装函数,把日期对象格式化成想要的格式
function getDateTime() {
const date = new Date(); // 使用日期对象必须先实例化
const year = date.getFullYear(); // 四位数年份
const month = date.getMonth() + 1; // 月份返回 0~11,要 +1
const day = date.getDate(); // 日
// 时分秒后面要做补零,需要重新赋值,所以用 let 而不是 const
let hh = date.getHours();
let mm = date.getMinutes();
let ss = date.getSeconds();
// 2. 补零操作
hh = hh < 10 ? '0' + hh : hh;
mm = mm < 10 ? '0' + mm : mm;
ss = ss < 10 ? '0' + ss : ss;
return `${year}年${month}月${day}日 ${hh}:${mm}:${ss}`;
}
const box = document.querySelector('.box');
// 3. 页面一打开先调用一次,防止盒子出现 1 秒空白期
box.innerHTML = getDateTime();
setInterval(function () {
box.innerHTML = getDateTime();
}, 1000);
</script>
</body>
</html>关键行与易错点
new Date():日期对象必须实例化,new就是实例化;new Date()括号为空得到当前时间,括号里写'2099-12-12 08:08:08'可得到指定时间。getMonth() + 1:月份取值范围是 0~11,返回 0 表示 1 月,忘记 +1 是最常见的错误;getDay()得到星期几,取值 0~6,0 代表星期天。let hh/mm/ss:因为补零时要对变量重新赋值,用const会报Assignment to constant variable(转写稿中就是在这个位置出的错),排查时看控制台报的行号即可。'0' + hh:0 要写成字符串,否则会变成数字相加(如0 + 8 = 8,不是"08")。- 定时器外先手动调用一次
box.innerHTML = getDateTime(),否则页面刚打开时盒子是空的,要等 1 秒才出现内容。
示例二:倒计时案例(时间戳 + 换算时分秒 + 定时器)
js
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>倒计时</title>
<style>
.countdown { width: 500px; margin: 100px auto; text-align: center; }
.clock { display: flex; justify-content: center; align-items: center; font-size: 26px; }
.clock span {
width: 60px; height: 60px; line-height: 60px;
background: #333; color: #fff; border-radius: 6px;
}
.clock i { margin: 0 8px; font-style: normal; }
</style>
</head>
<body>
<div class="countdown">
<p class="now"></p>
<p>距离下课还有</p>
<div class="clock">
<span id="hour">00</span><i>:</i>
<span id="minute">00</span><i>:</i>
<span id="second">00</span><i></i>
</div>
</div>
<script>
// 顺带把今天的日期显示出来
const now = new Date();
document.querySelector('.now').innerHTML =
`${now.getFullYear()}年${now.getMonth() + 1}月${now.getDate()}日`;
function getTimer() {
// 1. 将来的时间戳 - 现在的时间戳 = 剩余毫秒数,记得除以 1000 换成秒
const end = +new Date('2099-12-12 18:30:00'); // 正号 new Date 得到时间戳
const nowTime = +new Date();
const count = (end - nowTime) / 1000; // 剩余总秒数
// 2. 总秒数 -> 时 分 秒
let h = parseInt(count / 60 / 60 % 24);
let m = parseInt(count / 60 % 60);
let s = parseInt(count % 60);
// 3. 补零,需要重新赋值,所以用 let
h = h < 10 ? '0' + h : h;
m = m < 10 ? '0' + m : m;
s = s < 10 ? '0' + s : s;
// 4. 写到对应的盒子中
document.querySelector('#hour').innerHTML = h;
document.querySelector('#minute').innerHTML = m;
document.querySelector('#second').innerHTML = s;
}
getTimer(); // 先调用一次,防止默认 00 先露出来
setInterval(getTimer, 1000); // 定时器直接写函数名,后面不加小括号
</script>
</body>
</html>关键行与易错点
+new Date('...'):正号把日期对象转成数字,即时间戳(1970 年 1 月 1 日 0 时起的毫秒数)。这是推荐写法,比date.getTime()少一步、又比Date.now()灵活(能指定日期)。(end - now) / 1000:时间戳是毫秒,必须先除以 1000 换成秒再参与计算,否则时分秒全部算错。- 换算公式:
parseInt(总秒 / 60 / 60 % 24)得小时、parseInt(总秒 / 60 % 60)得分、parseInt(总秒 % 60)得秒。 setInterval(getTimer, 1000):直接写函数名,不能写成getTimer(),否则是立即执行一次并把返回值当回调。- 定时器外的那句
getTimer()不能删,否则页面刷新时会先显示 HTML 里默认的00:00:00,要等 1 秒才变成真实数据。
示例三:AlloyFinger 手势插件——左滑出现删除按钮
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>AlloyFinger 滑动删除</title>
<style>
.list { width: 400px; margin: 50px auto; }
.item {
position: relative;
height: 60px;
line-height: 60px;
margin-bottom: 10px;
background: #f5f5f5;
overflow: hidden;
}
.item .content {
position: relative;
height: 60px;
padding-left: 20px;
background: #f5f5f5;
transition: transform .3s;
}
.item .delete {
position: absolute;
right: 0;
top: 0;
width: 80px;
height: 60px;
text-align: center;
color: #fff;
background: red;
}
</style>
</head>
<body>
<div class="list">
<div class="item">
<div class="content">张三</div>
<div class="delete">删除</div>
</div>
<div class="item">
<div class="content">李四</div>
<div class="delete">删除</div>
</div>
</div>
<!-- 1. 引入插件库(也可下载到本地引入 alloy_finger.js) -->
<script src="https://unpkg.com/alloyfinger@0.1.16/alloy_finger.js"></script>
<script>
// 2. 给每个元素注册手势事件
const items = document.querySelectorAll('.item');
items.forEach(function (item) {
new AlloyFinger(item, {
swipe: function (e) {
// e.direction 判断滑动方向:Left / Right / Top / Bottom
if (e.direction === 'Left') {
item.querySelector('.content').style.transform = 'translateX(-80px)';
} else if (e.direction === 'Right') {
item.querySelector('.content').style.transform = 'translateX(0)';
}
}
});
});
</script>
</body>
</html>关键行与易错点
- 学习插件的流程:先看官网了解能做什么 → 看基本使用流程 → 写个小 demo 跑通 → 再放进项目,不要一上来就整段复制进业务代码。
new AlloyFinger(element, { swipe: fn }):第一个参数是给哪个元素做手势,e.direction用来判断方向(Left、Right等,注意首字母大写)。- 删除按钮用
position: absolute藏在右侧、父级overflow: hidden挡住,滑动时只移动.content,这是"滑动出现删除按钮"的常用结构。 - 用
forEach遍历querySelectorAll的结果批量注册,注意querySelectorAll返回的是伪数组,没有forEach之外的数组方法;如果需要新增元素也有效果,应配合事件委托或新增后重新注册。
常见错误
- 现象:页面显示“0月”或星期对不上。→ 原因:
getMonth()返回 0–11,getDay()中 0 表示周日。→ 修法:月份+1;星期用中文映射数组。 - 现象:倒计时刷新时先出现空白或默认
00。→ 原因:定时器 1 秒后才首次执行。→ 修法:页面加载先调用一次函数,再启动setInterval。 - 现象:控制台报
Assignment to constant variable。→ 原因:补零时对const变量重新赋值。→ 修法:需要重新赋值的变量改用let,或一次性三元生成结果。 - 现象:倒计时小时数异常巨大。→ 原因:直接用毫秒时间戳换算时/分/秒。→ 修法:先
剩余时间戳 / 1000转成秒,再套换算公式。 - 现象:
setInterval只执行一次或立即执行。→ 原因:写成setInterval(getTime(), 1000),传入的是函数返回值。→ 修法:传函数引用setInterval(getTime, 1000),不加括号。 - 现象:AlloyFinger 滑动无效或新增列表项无法滑动。→ 原因:未引入/未实例化插件,或只给旧元素绑定。→ 修法:先引入库再
new AlloyFinger(element, {...});新增元素用事件委托或重新实例化。
练习
- 用日期对象输出“2023年01月27日 星期五 21:41:48”,要求月、日、时、分、秒补零,星期显示中文。
- 写一个倒计时到当天 18:30,显示时/分/秒;要求刷新页面不出现空白或默认
00,并每秒自动更新。 - 用 AlloyFinger 给通讯录列表项注册左滑事件,左滑后显示“删除”按钮;说明新增联系人后如何仍能左滑。
- 用
forEach渲染通讯录数据,点击删除时用事件委托删除对应数据并重新渲染页面,说明“数据驱动视图”的流程。 - 分别用三种方式获取指定日期的时间戳,并说明倒计时案例中为什么推荐先转成秒再计算。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 今日内容 | 02:19 |
| 2 | 日期对象的使用 | 14:02 |
| 3 | 页面显示日期和时间案例 | 11:14 |
| 4 | 格式化日期的另外方法 | 05:03 |
| 5 | 三种方式获得时间戳 | 07:24 |
| 6 | 倒计时案例上 | 11:50 |
| 7 | 倒计时案例下 | 03:41 |
小结
- 插件学习路径应固定为:看官网需求 → 看基本流程 → 跑最小 demo → 接入项目。
- 移动端手势优先用成熟插件,业务代码只处理方向判断和后续状态。
- 日期对象不要直接展示给用户,必须按需求显式格式化。
- 倒计时的关键不是“显示”,而是统一单位后计算剩余时间。
- 自动刷新类效果要先立即执行一次,再启动定时器,避免首屏空白。
- 默认优先
const,需要重新赋值再用let;报错先看控制台行号和错误类型。 - 列表增删应走“操作数据 → 重新渲染”,配合事件委托降低 DOM 绑定成本。