1. 从“算数”到“编程”:为什么Math和Date是你的JavaScript必修课?
刚开始学JavaScript那会儿,我总觉得Math和Date这两个对象有点“鸡肋”。不就是算个数、取个时间吗?浏览器控制台里敲敲不就行了?直到后来,我接手了一个电商项目,需要实时计算商品折扣后的价格(涉及四舍五入和向上取整),还要在订单页面上精确显示下单时间(精确到毫秒,并且要格式化成年月日),我才彻底明白,这两个对象根本不是课本里的练习题,而是你每天写代码都绕不开的“瑞士军刀”。
简单来说,Math对象就是JavaScript给你准备好的一个“数学工具箱”。你想做任何数学运算——不管是基础的加减乘除,还是复杂的三角函数、指数对数,甚至生成随机数——都不用自己从头写算法,直接从这个工具箱里拿现成的、高度优化过的工具来用就行。而Date对象,则是你的“时间管家”。我们生活在一个由时间构成的世界里,程序也一样:用户什么时候注册的?订单还有多久超时?这个活动持续到哪一天?所有这些问题的答案,都需要Date对象来创建、解析、计算和展示。
对于正在educoder头歌实训平台上学习Web课程的同学来说,搞懂Math和Date,意义远不止于完成几个关卡任务。它们是你从“写静态页面”迈向“开发动态交互应用”的关键一步。想想看,一个没有动态计算能力的页面,和一个能根据时间自动切换活动横幅、能根据用户输入实时计算结果的页面,体验上的差距有多大?这份实战能力,正是你接下来学习更高级的JavaScript功能,乃至整个前端框架的基石。
所以,别把它们当成枯燥的API来背。接下来,我会带你像拆解乐高一样,看看Math和Date这两个“盒子”里到底有哪些好玩的“积木块”,并且用最贴近真实开发的例子,手把手教你把它们拼装成有用的功能。你会发现,原来编程里的数学和时间,可以这么直观和有趣。
2. Math对象:你的随身计算器,远比你想象的强大
很多新手一看到Math,就想到Math.PI(圆周率)和Math.sqrt()(开平方),觉得这玩意也就考试用用。其实大错特错。在实际开发中,Math对象出场频率高得惊人,而且用法非常“接地气”。
2.1 四舍五入“三兄弟”:弄不清它们,价格计算准出错
这是最容易踩坑的地方。Math对象提供了三个方法来处理数字的取整:Math.round()、Math.ceil()和Math.floor()。听起来简单,但用混了直接导致财务计算错误。
Math.round(x):经典的四舍五入。它遵循我们小学学的规则:看小数点后第一位,大于等于5就入,小于5就舍。比如Math.round(1.4)是1,Math.round(1.5)就是2。在计算商品评分(平均分显示)时,这是最常用的。Math.ceil(x):向上取整。它的逻辑是“只要有小数,就向大的方向进一”。Math.ceil(1.1)是2,Math.ceil(-1.1)是-1(注意,-1比-1.1大)。这个在分页计算时是神器。比如,你有21条数据,每页显示10条,需要几页?Math.ceil(21 / 10)等于3,非常直观。Math.floor(x):向下取整。和ceil相反,“砍掉所有小数,往小的方向走”。Math.floor(1.9)是1,Math.floor(-1.9)是-2。在做数组索引计算或者计时器倒计时(显示整数秒)时特别有用。
来看一个educoder实训风格的例子,但我会把它扩展得更实用:
function calculatePayment(price, quantity) {
// 假设单价98.5元,买3件,有会员折扣9.5折
let singlePrice = 98.5;
let count = 3;
let discount = 0.95;
// 计算总价:单价 * 数量 * 折扣
let totalRaw = singlePrice * count * discount; // 280.725
// 场景1:财务要求精确到分,分后四舍五入(用于实际扣款)
let amountForCharge = Math.round(totalRaw * 100) / 100; // 280.73
console.log(`实际扣款金额:${amountForCharge}元`);
// 场景2:在页面上展示优惠价,习惯显示到小数点后一位(向下取整,显得更优惠)
let displayPrice = Math.floor(totalRaw * 10) / 10; // 280.7
console.log(`页面展示价:${displayPrice}元`);
// 场景3:计算需要多少张50元的优惠券才能覆盖(向上取整)
let couponsNeeded = Math.ceil(totalRaw / 50); // 280.725 / 50 = 5.6145 -> 6
console.log(`需要${couponsNeeded}张50元券`);
}
calculatePayment();
通过这一个简单的价格计算,你就看到了三个方法完全不同的用途。记住口诀:“商业四舍五入用round,宁多不少用ceil,宁少不多用floor”。
2.2 最值、随机数与乘方:小功能解决大问题
除了取整,Math对象里还有几个“明星方法”。
Math.max()和Math.min():找出一组数里的最大值和最小值。你别以为这只能用来做数学题。我经常用它来做“边界限制”。比如,一个可拖拽的元素,不能超出屏幕范围:
// 假设鼠标拖拽到了一个x坐标 newX,但屏幕宽度是screenWidth,元素自身宽度是width
let restrictedX = Math.max(0, Math.min(newX, screenWidth - elementWidth));
// 这行代码保证了restrictedX永远不会小于0,也永远不会大于(屏幕宽-元素宽)
Math.random():这是最有“魔法”感的方法。它返回一个0到1之间(包括0,但不包括1)的伪随机数。它的应用场景多到数不清:随机抽奖、随机更换背景图、生成验证码、游戏中的随机事件等等。但记住,它默认只能生成0-1的小数,我们通常需要加工一下。
// 生成一个介于[min, max]之间的随机整数(经典公式,务必背下来)
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min; // 包含最大值和最小值
}
let luckyNumber = getRandomInt(1, 100); // 生成1到100之间的幸运数字
Math.pow(x, y)和Math.sqrt(x):分别计算x的y次幂和x的平方根。Math.pow(2, 10)就是计算2的10次方(1024)。Math.sqrt()在计算距离时非常有用,比如根据鼠标移动的横纵坐标差,计算实际的直线距离(勾股定理)。
// 计算两点(x1,y1)和(x2,y2)之间的直线距离
function getDistance(x1, y1, x2, y2) {
let deltaX = x2 - x1;
let deltaY = y2 - y1;
return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
}
把这些方法组合起来,Math对象就不再是孤立的函数,而是一个能解决实际问题的工具包。比如,你可以用Math.random()生成随机数,用Math.floor取整作为数组索引,再从数组里随机取一个头像来展示,一个简单的“随机头像”功能就实现了。
3. Date对象:驾驭时间,让你的应用“活”起来
如果说Math对象让程序有了“计算”能力,那么Date对象就让程序有了“时间感”。一个不会处理时间的网页,就像一块停摆的钟表,是“死”的。
3.1 创建与获取:拿到时间的“原材料”
使用Date对象的第一步,永远是创建一个日期实例。这里有几种最常用的方式:
// 1. 获取此时此刻(服务器或用户本地时间)
let now = new Date();
console.log(now); // 输出类似:Mon Dec 16 2024 14:30:00 GMT+0800 (中国标准时间)
// 2. 创建一个特定的日期时间(注意:月份是从0开始的!)
let birthday = new Date(1995, 11, 17); // 1995年12月17日,月份11代表12月
let specificTime = new Date(1995, 11, 17, 3, 24, 0); // 加上时、分、秒:凌晨3:24:00
// 3. 通过时间戳创建(最常用、最精确的方式之一)
// 时间戳是从1970年1月1日UTC时间开始的毫秒数
let timestamp = 1671091200000; // 假设这是一个从后端传来的时间戳
let dateFromTimestamp = new Date(timestamp);
// 4. 解析日期字符串(注意浏览器兼容性)
let dateFromString = new Date('2024-12-16T14:30:00'); // ISO 8601格式比较安全
创建好Date实例后,你就可以像查户口一样,从中提取出各个部分的信息。这是educoder第二关的核心内容:
let date = new Date();
let year = date.getFullYear(); // 获取四位数的年份(2024)
let month = date.getMonth(); // 获取月份,返回值是0-11(0代表一月,这是个经典坑!)
let dayOfMonth = date.getDate(); // 获取月份中的第几天(1-31)
let dayOfWeek = date.getDay(); // 获取星期几,返回值是0-6(0代表星期日)
let hours = date.getHours(); // 获取小时(0-23)
let minutes = date.getMinutes();
let seconds = date.getSeconds();
let milliseconds = date.getMilliseconds(); // 获取毫秒
// 还有一个非常重要的:获取时间戳
let timestamp = date.getTime(); // 或者 Date.now()
这里有个超级重要的细节:getMonth()返回的月份是从0开始的。所以,当你看到getMonth()返回11时,别慌,那代表的是12月。这是JavaScript早期设计的一个“坑”,无数开发者在这里栽过跟头,一定要形成肌肉记忆。
3.2 设置与计算:让时间“听话”
Date对象不仅是只读的,你还可以修改它。这在制作倒计时、计算未来日期时非常有用。
let date = new Date();
// 设置时间
date.setFullYear(2025);
date.setMonth(5); // 设置为6月
date.setDate(15);
date.setHours(14, 30, 0); // 同时设置时、分、秒
// 更实用的:时间的计算
// 计算10天后的日期
let today = new Date();
let tenDaysLater = new Date(today);
tenDaysLater.setDate(today.getDate() + 10); // 自动处理跨月跨年
// 计算两个日期之间的天数差(经典面试题)
function getDayDiff(date1, date2) {
// 先去掉时间部分,只比较日期
let d1 = new Date(date1.getFullYear(), date1.getMonth(), date1.getDate());
let d2 = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
// 计算时间戳差,再换算成天数
let timeDiff = Math.abs(d2.getTime() - d1.getTime());
return Math.ceil(timeDiff / (1000 * 60 * 60 * 24));
}
时间的计算,核心秘诀在于利用时间戳。因为无论日期格式多么复杂,转换成毫秒时间戳后,就变成了一个简单的数字,加减乘除都非常方便。最后再通过new Date(timestamp)转换回可读的日期格式。
3.3 格式化输出:把时间变成用户看得懂的样子
从Date对象获取的数据,往往是零散的数字,直接扔给用户看体验很差。2024,11,16,14,30 这是什么?我们需要把它格式化成 2024年12月16日 14:30 或者 2024-12-16。
JavaScript的Date对象没有内置的完美格式化方法(不像其他语言有strftime),所以我们需要自己拼接,或者使用Intl.DateTimeFormat这个现代API。
// 方法1:手动拼接(最灵活,也最繁琐)
function formatDate(date) {
let year = date.getFullYear();
let month = (date.getMonth() + 1).toString().padStart(2, '0'); // 月份补零
let day = date.getDate().toString().padStart(2, '0');
let hours = date.getHours().toString().padStart(2, '0');
let minutes = date.getMinutes().toString().padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`;
}
// 方法2:使用 toLocaleDateString 和 toLocaleTimeString(简单,但控制力弱)
let date = new Date();
let dateString = date.toLocaleDateString('zh-CN'); // "2024/12/16"
let timeString = date.toLocaleTimeString('zh-CN', { hour12: false }); // "14:30:00"
// 方法3:使用 Intl.DateTimeFormat(推荐,功能强大且兼容性好)
let formatter = new Intl.DateTimeFormat('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
hour12: false // 使用24小时制
});
console.log(formatter.format(date)); // "2024/12/16 14:30"
对于初学者,我建议先从方法1开始练习,它能帮你彻底理解日期各个部分的获取。在实际项目中,为了代码简洁和国际化考虑,可以逐步转向使用方法3。
4. 实战融合:Math与Date联手,打造真实应用场景
单独学完了Math和Date,是时候让它们组队打怪了。在实际的Web开发中,它们经常是同时出现的。下面我模拟两个educoder上可能出现的、但更贴近真实项目的综合关卡,带你看看它们如何协作。
4.1 实战一:生成带时间戳的随机订单号
电商系统里,每个订单都需要一个唯一的订单号。一个常见的简单方案是:时间戳 + 随机数。这样可以极大降低重复的概率。
function generateOrderNumber() {
// 1. 获取当前时间戳(Date对象出场)
let timestamp = new Date().getTime(); // 例如 1671091200000
// 2. 生成一个3位数的随机数作为后缀(Math对象出场)
// 使用Math.random()生成随机小数,再通过乘法和Math.floor变成整数
let randomSuffix = Math.floor(Math.random() * 900) + 100; // 生成100-999的随机数
// 3. 组合成订单号(时间戳 + 随机后缀)
// 时间戳很长,我们有时只取后几位,这里取后10位
let timestampPart = timestamp.toString().slice(-10);
let orderNumber = `ORD${timestampPart}${randomSuffix}`;
return orderNumber;
}
// 测试
console.log(generateOrderNumber()); // 输出类似:ORD91200000123
console.log(generateOrderNumber()); // 几毫秒后再次调用,输出类似:ORD91200015456
这个函数虽然简单,但融合了Date.getTime()获取时间戳,Math.random()生成随机数,Math.floor()进行取整,以及字符串操作。它生成的订单号,在同一毫秒内重复的概率极低(1/900),基本能满足小型应用的需求。
4.2 实战二:实现一个智能的优惠券到期倒计时
假设你正在做一个电商网站,需要在商品详情页展示用户领取的优惠券还有多久过期。这个功能需要计算时间差,并且友好地显示“剩余X天X小时X分”。
function getTimeRemaining(endTime) {
// endTime 可以是一个Date对象,也可以是一个时间戳字符串
let end = new Date(endTime);
let now = new Date();
// 计算总的时间差(毫秒)
let totalMs = end.getTime() - now.getTime();
// 如果已经过期
if (totalMs <= 0) {
return { expired: true, message: '优惠券已过期' };
}
// 将毫秒差转换成天、时、分、秒(Math.floor出场)
let oneSecond = 1000;
let oneMinute = oneSecond * 60;
let oneHour = oneMinute * 60;
let oneDay = oneHour * 24;
// 计算剩余天数(向下取整)
let days = Math.floor(totalMs / oneDay);
// 计算除去整天后,剩余的毫秒数
let hoursRemainder = totalMs % oneDay;
// 计算剩余小时数
let hours = Math.floor(hoursRemainder / oneHour);
// 以此类推...
let minutesRemainder = hoursRemainder % oneHour;
let minutes = Math.floor(minutesRemainder / oneMinute);
let secondsRemainder = minutesRemainder % oneMinute;
let seconds = Math.floor(secondsRemainder / oneSecond);
return {
expired: false,
days,
hours,
minutes,
seconds,
message: `剩余 ${days}天 ${hours}小时 ${minutes}分 ${seconds}秒`
};
}
// 假设优惠券在3天后的晚上23点59分59秒过期
let couponEndTime = new Date();
couponEndTime.setDate(couponEndTime.getDate() + 3);
couponEndTime.setHours(23, 59, 59, 999);
let result = getTimeRemaining(couponEndTime);
console.log(result.message);
// 如果现在距离过期还有2天半,可能输出:“剩余 2天 12小时 15分 30秒”
在这个例子中,Date对象负责处理时间的解析和计算(getTime()做差值),而Math.floor()则在单位换算中起到了关键作用,确保我们得到的是整数天、整数小时。你可以把这个函数返回的结果,用setInterval动态更新到网页上,就是一个生动的倒计时组件了。
4.3 实战三:模拟简易数据图表——生成过去7天的随机访问量
最后,我们来一个稍微综合点的练习。假设老板让你在后台快速画一个过去一周网站访问量的趋势草图,不需要复杂的图表库,先用数字列表展示。我们需要生成过去7天的日期,以及对应的随机访问量。
function generateWeeklyVisits() {
let visitsData = [];
let today = new Date();
for (let i = 6; i >= 0; i--) { // 从6天前循环到今天
// 1. 计算日期:用Date对象做时间减法
let targetDate = new Date(today);
targetDate.setDate(today.getDate() - i);
// 2. 格式化日期为 MM-DD 的形式
let month = targetDate.getMonth() + 1;
let day = targetDate.getDate();
let dateLabel = `${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`;
// 3. 生成一个随机的访问量(比如在200-5000之间波动)
// 使用Math.random()和Math.floor()生成随机整数
let visits = Math.floor(Math.random() * (5000 - 200 + 1)) + 200;
visitsData.push({
date: dateLabel,
visits: visits
});
}
return visitsData;
}
let data = generateWeeklyVisits();
console.table(data);
// 输出一个表格,类似:
// ┌─────────┬─────────┬────────┐
// │ (index) │ date │ visits │
// ├─────────┼─────────┼────────┤
// │ 0 │ '12-10' │ 3124 │
// │ 1 │ '12-11' │ 4589 │
// │ ... │ ... │ ... │
// └─────────┴─────────┴────────┘
这个函数里,循环中的每一天,都通过Date.setDate()进行推算,完美展示了Date对象用于日期序列生成的能力。而Math.random()和Math.floor()则负责制造模拟数据。有了这个数据数组,你既可以简单地用console.table展示,也可以尝试用HTML和CSS画一个最基础的柱状图。
通过这些实战例子,你应该能感受到,Math和Date从来不是孤立的知识点。它们是你工具箱里最趁手的两件工具,一个帮你处理“数”,一个帮你处理“时”。当你能熟练地把它们结合起来,去解决像订单号、倒计时、模拟数据这些具体问题时,你就已经跨过了“死记API”的阶段,开始真正地用编程思维来解决问题了。educoder上的关卡是很好的起点,但真正的掌握,来自于你不断地把这些知识应用到更多、更复杂的真实场景中去。

317

被折叠的 条评论
为什么被折叠?



