☰
用 JavaScript 动态创建日历表格:createCalendar 实战解析(zh.javascript.info 经典任务)
2026/10/8 1:33:27 网站建设 项目流程
  • 文档
  • 教程
  • 前端

【免费下载链接】zh.javascript.info

现代 JavaScript 教程(The Modern JavaScript Tutorial),以最新的 ECMAScript 规范为基准,通过简单但足够详细的内容,为你讲解从基础到高阶的 JavaScript 相关知识。

项目地址:https://gitcode.com/gh_mirrors/zh/zh.javascript.info
点击查看免费下载

本篇文章以 zh.javascript.info 中「修改文档(document)」章节的经典任务「创建一个日历」为核心,完整讲解createCalendar(elem, year, month)函数的实现思路、JavaScript 日期对象的正确用法,以及通过innerHTML字符串拼接动态生成<table>的完整方案。读完本文,你将掌握:如何利用Date对象处理「月份从 0 开始」「每周从星期一开始」等日历排版的经典陷阱,以及如何用一条 HTML 字符串干净地生成整张月历表格。

任务背景:它来自哪个章节

该任务位于教程「修改文档(document)」小节(07-modifying-document/article.md)的练习区。在阅读本任务前,你已经掌握了该章节的核心知识:

  • 用document.createElement(tag)创建元素节点、用document.createTextNode(text)创建文本节点;
  • 用node.append / prepend / before / after / replaceWith / remove等现代方法插入和移除节点;
  • 用elem.insertAdjacentHTML(where, html)把 HTML 字符串按四个位置(beforebegin、afterbegin、beforeend、afterend)插入;
  • 用elem.innerHTML一次性写入整段 HTML。

「创建日历」这个任务,本质上是把这些 DOM 修改能力与 JavaScript 日期计算结合起来的综合练习:它不要求任何点击交互,只要求根据给定的year和month在页面元素中生成一张结构正确的月历表格。

任务要求原文

编写一个函数createCalendar(elem, year, month):

  • 对该函数的调用,应该使用给定的year/month创建一个日历,并将创建的日历放入elem中;
  • 创建的日历应该是一个表格(<table>),其中每一周用<tr>表示,每一天用<td>表示;
  • 表格顶部应该是带有星期名的<th>,第一天应该是 Monday(星期一),依此类推直到 Sunday(星期日);
  • 例如,createCalendar(cal, 2012, 9)应该在元素cal中生成对应的 2012 年 9 月日历;
  • 附加说明:本任务只要求生成日历,不需要点击交互功能。

任务对应的起始模板位于 source.view/index.html,其中已经提供了<div id="calendar"></div>占位容器和样式,以及一个待你填充的函数骨架:

<div id="calendar"></div> <script> function createCalendar(elem, year, month) { // ...your code that generates the calendar in elem... } createCalendar(calendar, 2012, 9); </script>

解决方案:用一条 HTML 字符串构建整张表格

官方解答(见 solution.md 与 solution.view/index.html)采用了一个非常简洁的策略:

我们将表格创建为字符串:"<table>...</table>",然后将其赋值给innerHTML。

也就是说,不逐格调用createElement/append来搭建 DOM 树,而是先在内存中把整张表格的 HTML 拼成一条字符串,最后一次性赋值给elem.innerHTML。对于这种结构规整、内容以日期数字为主的表格,字符串拼接方式代码更短、可读性更好,也省去了大量重复的 DOM 操作调用。

核心算法五步

官方解答将整个算法拆解为 5 步:

  1. 使用<th>创建带有星期名的表头;
  2. 创建日期对象d = new Date(year, month-1)。它是month的第一天(考虑到 JavaScript 中的月份从0开始,而不是从1开始);
  3. 直到月份的第一天d.getDay(),前面的几个单元格是空的。用<td></td>填充它们;
  4. 天数增长d:d.setDate(d.getDate()+1)。如果d.getMonth()还没到下一个月,就将新的单元格<td>添加到日历中;如果那天是星期日,就添加新行</tr><tr>;
  5. 如果该月结束,但表格的行尚未填满,就用空的<td>补齐。

完整参考实现

function createCalendar(elem, year, month) { let mon = month - 1; // JS 中月份是 0..11,而不是 1..12 let d = new Date(year, mon); let table = '<table><tr><th>MO</th><th>TU</th><th>WE</th><th>TH</th><th>FR</th><th>SA</th><th>SU</th></tr><tr>'; // 第一行的空格 // 从星期一 到 这个月的第一天 // * * * 1 2 3 4 for (let i = 0; i < getDay(d); i++) { table += '<td></td>'; } // 填充有实际日期的 <td> while (d.getMonth() == mon) { table += '<td>' + d.getDate() + '</td>'; if (getDay(d) % 7 == 6) { // 星期日,一周最后一天 - 换行 table += '</tr><tr>'; } d.setDate(d.getDate() + 1); } // 补上月末之后剩余的空格,填满最后一行 // 29 30 31 * * * * if (getDay(d) != 0) { for (let i = getDay(d); i < 7; i++) { table += '<td></td>'; } } // 关闭表格 table += '</tr></table>'; elem.innerHTML = table; } function getDay(date) { // 获取星期编号,从 0(星期一)到 6(星期日) let day = date.getDay(); if (day == 0) day = 7; // 让星期日(0)变成最后一天 return day - 1; } createCalendar(calendar, 2012, 9);

以createCalendar(calendar, 2012, 9)为例,这段代码生成的 2012 年 9 月日历布局如下(该月 1 号是星期六,因此第一行以两个空格开头;30 号是星期日,因此最后一行恰好以 30 收尾,无需补空格):

MO TU WE TH FR SA SU 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

配套样式

渲染出的表格是否美观取决于 CSS。仓库给出的 solution.view/index.html 中提供了配套样式,可以直接复用:

table { border-collapse: collapse; } td, th { border: 1px solid black; padding: 3px; text-align: center; } th { font-weight: bold; background-color: #E6E6E6; }

其中border-collapse: collapse让相邻单元格的边框合并为一条线,th用灰色背景加粗区分表头,td居中对齐日期数字。

关键知识点拆解

为什么month要减 1

JavaScript 的Date对象中,月份是从0开始计数的:0代表一月,11代表十二月。因此,当用户传入的month是「1~12」的自然月编号时,必须执行mon = month - 1再传给new Date(year, mon)。这是本任务最容易踩的第一个坑,官方解答中专门用注释强调了这一点。

关于Date对象的构造与常用方法,可以复习教程 日期和时间(Date) 章节。

为什么每周从星期一开始需要重写 getDay

原生Date.prototype.getDay()返回0(星期日)到6(星期六),也就是一周从星期日开始。而本任务要求日历以星期一开始,星期日在最后。

官方的getDay(date)辅助函数完成了一次坐标转换:

function getDay(date) { // 从 0(星期一)到 6(星期日) let day = date.getDay(); if (day == 0) day = 7; // 星期日(0)变成 7,即最后一天 return day - 1; // 整体左移一位 }

转换结果:星期一 →0,星期二 →1,……,星期六 →5,星期日 →6。这样一来:

  • 月初第一天是星期几,就决定了第一行前面要放几个空格单元格(for (let i = 0; i < getDay(d); i++));
  • 星期日的编号是6,所以getDay(d) % 7 == 6可以判断「是否到了该换行的时候」。这里的% 7写法让判断在逻辑上更通用(等价于getDay(d) == 6)。

遍历整个月:setDate 自增与 getMonth 边界判断

生成日期单元格的核心是一个while循环:

while (d.getMonth() == mon) { table += '<td>' + d.getDate() + '</td>'; if (getDay(d) % 7 == 6) { table += '</tr><tr>'; } d.setDate(d.getDate() + 1); }

d.setDate(d.getDate() + 1)是 JavaScript 中「日期加一天」的标准写法。它的巧妙之处在于:当日期越过当月边界时(例如 9 月 30 日 +1),Date对象会自动进位到下个月的 1 号,因此d.getMonth()会变成mon + 1,循环条件d.getMonth() == mon自然失效,循环随之终止——不需要手动计算每月有多少天。

循环结束后,d恰好指向下个月的 1 号,此时getDay(d)的值恰好等于「本月最后一天之后剩余的空格数」,因此最后一行补空格的逻辑可以直接复用:

if (getDay(d) != 0) { for (let i = getDay(d); i < 7; i++) { table += '<td></td>'; } }

这个设计非常精妙:不需要额外保存任何状态,循环退出时d本身携带的星期信息就足够补齐最后一行了。

为什么用 innerHTML 而不是 DOM 方法逐个插入

在 07-modifying-document/article.md 章节中你学到了多种插入节点的方法(append、prepend、insertAdjacentHTML等)。本任务选择innerHTML而非逐个createElement+append,理由很实际:

  • 代码量更少:整张表格只需一条字符串拼接循环,不需要为每个<td>单独创建节点;
  • 结构一目了然:字符串中的<tr>/<td>层级关系直接映射最终表格结构,便于阅读和调试;
  • 性能足够:对一张月历这样的小型静态结构,一次性赋值innerHTML比几十次 DOM 插入调用更高效。

当然,如果你希望代码风格更贴近「DOM 操作」教学点,也可以用document.createElement('table')配合append逐行逐格构建,两种方式都能通过本任务,只是官方解答选择了更简洁的字符串方案。

验证与运行方式

仓库为本题提供了两个可直接运行的页面:

  • source.view/index.html:任务起始模板(函数体为空);
  • solution.view/index.html:完整解答,打开即可看到 2012 年 9 月的日历渲染结果。

验证建议:将createCalendar(calendar, 2012, 9)中的参数替换为其他年份/月份组合,重点检查三类边界情况:

  1. 月初是星期日(如 2012 年 1 月):第一行不应出现空格单元格,直接从 1 号开始;
  2. 月末不落在星期日(如 2012 年 9 月之外的其他月份):最后一行应有正确的空格补齐,保证整张表格行数完整;
  3. 二月平年/闰年(如 2011 年 2 月 28 天、2012 年 2 月 29 天):验证while循环边界判断是否正确终止。

总结

「创建日历」是 DOM 修改章节的综合性练习,它把三个关键能力串联在一起:

  1. 日期计算:new Date(year, month - 1)处理月份偏移,setDate(getDate() + 1)自动跨月,getMonth()判断月界;
  2. 星期坐标转换:用getDay辅助函数把原生「星期日开头」的编号改造成「星期一开头」,并据此计算行首空格与换行时机;
  3. 表格字符串生成:用一条<table>...</table>字符串配合innerHTML一次性写入,兼顾简洁与性能。

掌握这套模式后,你可以轻松扩展出带周数、节假日高亮、前后月切换等交互功能的日历组件——但正如任务所说,先让一张静态月历正确渲染出来,是一切扩展的地基。

  • 文档
  • 教程
  • 前端

【免费下载链接】zh.javascript.info

现代 JavaScript 教程(The Modern JavaScript Tutorial),以最新的 ECMAScript 规范为基准,通过简单但足够详细的内容,为你讲解从基础到高阶的 JavaScript 相关知识。

项目地址:https://gitcode.com/gh_mirrors/zh/zh.javascript.info
点击查看免费下载

相关推荐

上一篇:ARIS 专利撰写系列:USPTO 美国专利申请格式指南(Claims / Specification / Drawings / IDS 全解析)
下一篇:kkFileView CAD图纸参数提取技术架构与系统设计深度解析

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询