Skip to content

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/[email protected]/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、数据驱动视图和事件委托串成了一个完整案例。

课件图示 ​

课件第38页:4.插件-swiper l 小demo的实现步骤 1. 下载相关js文件和css

课件第40页:插件- AlloyFinger l AlloyFinger 是腾讯 AlloyT

课件第5页:1. 日期对象 Date l 日期对象:用来表示日期和时间的对象 l 作用:可以

课件第6页:1.1 实例化 l 日期对象使用必须先实例化:创建一个日期对象并获取时间 l 在

课件第7页:2.2 日期对象方法 - 格式化日期对象 使用场景:因为日期对象返回的数据我们不

课件第8页:总结 总结 1. 实例化日期对象怎么写? Ø new Date() 2. 日期对

课件第10页:2.2 日期对象方法 格式化日期对象另外一种方法 方法 作用 说明 toLoca

课件第12页:2.3 时间戳 l 什么是时间戳: Ø 是指1970年01月01日00时00分0

代码示例 ​

示例一:页面自动显示日期和时间(日期对象 + 补零 + 定时器) ​

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/[email protected]/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 之外的数组方法;如果需要新增元素也有效果,应配合事件委托或新增后重新注册。

常见错误 ​

  1. 现象:页面显示“0月”或星期对不上。→ 原因:getMonth() 返回 0–11,getDay() 中 0 表示周日。→ 修法:月份 +1;星期用中文映射数组。
  2. 现象:倒计时刷新时先出现空白或默认 00。→ 原因:定时器 1 秒后才首次执行。→ 修法:页面加载先调用一次函数,再启动 setInterval。
  3. 现象:控制台报 Assignment to constant variable。→ 原因:补零时对 const 变量重新赋值。→ 修法:需要重新赋值的变量改用 let,或一次性三元生成结果。
  4. 现象:倒计时小时数异常巨大。→ 原因:直接用毫秒时间戳换算时/分/秒。→ 修法:先 剩余时间戳 / 1000 转成秒,再套换算公式。
  5. 现象:setInterval 只执行一次或立即执行。→ 原因:写成 setInterval(getTime(), 1000),传入的是函数返回值。→ 修法:传函数引用 setInterval(getTime, 1000),不加括号。
  6. 现象:AlloyFinger 滑动无效或新增列表项无法滑动。→ 原因:未引入/未实例化插件,或只给旧元素绑定。→ 修法:先引入库再 new AlloyFinger(element, {...});新增元素用事件委托或重新实例化。

练习 ​

  1. 用日期对象输出“2023年01月27日 星期五 21:41:48”,要求月、日、时、分、秒补零,星期显示中文。
  2. 写一个倒计时到当天 18:30,显示时/分/秒;要求刷新页面不出现空白或默认 00,并每秒自动更新。
  3. 用 AlloyFinger 给通讯录列表项注册左滑事件,左滑后显示“删除”按钮;说明新增联系人后如何仍能左滑。
  4. 用 forEach 渲染通讯录数据,点击删除时用事件委托删除对应数据并重新渲染页面,说明“数据驱动视图”的流程。
  5. 分别用三种方式获取指定日期的时间戳,并说明倒计时案例中为什么推荐先转成秒再计算。

原视频对照 ​

#视频时长
1今日内容02:19
2日期对象的使用14:02
3页面显示日期和时间案例11:14
4格式化日期的另外方法05:03
5三种方式获得时间戳07:24
6倒计时案例上11:50
7倒计时案例下03:41

小结 ​

  1. 插件学习路径应固定为:看官网需求 → 看基本流程 → 跑最小 demo → 接入项目。
  2. 移动端手势优先用成熟插件,业务代码只处理方向判断和后续状态。
  3. 日期对象不要直接展示给用户,必须按需求显式格式化。
  4. 倒计时的关键不是“显示”,而是统一单位后计算剩余时间。
  5. 自动刷新类效果要先立即执行一次,再启动定时器,避免首屏空白。
  6. 默认优先 const,需要重新赋值再用 let;报错先看控制台行号和错误类型。
  7. 列表增删应走“操作数据 → 重新渲染”,配合事件委托降低 DOM 绑定成本。