Date 与常用工具函数
发表于:2026-07-29
字数统计:7000 字
预计阅读24分钟
涵盖 Date 对象核心方法、倒计时实现、变量提升陷阱、JS 中的常用工具函数合集。
一、Date 对象
1. 创建 Date
JavaScript
// 1. 无参数(当前时间)
const now = new Date()
console.log(now) // 当前时间
// 2. 时间戳(毫秒)
const date1 = new Date(1715000000000)
const date2 = new Date(0) // 1970-01-01T00:00:00.000Z
// 3. 日期字符串(注意 ISO 格式会按 UTC 解析)
const date3 = new Date('2024-05-15T10:30:00') // UTC
const date4 = new Date('2024-05-15') // UTC 0 点
const date5 = new Date('2024/05/15') // 本地时间
const date6 = new Date('May 15, 2024') // 本地时间
// 4. 数字参数(注意月份从 0 开始)
const date7 = new Date(2024, 4, 15, 10, 30, 0) // 2024年5月15日 10:30:00(本地)
// 月份是 0 = 一月,4 = 五月2. 获取日期信息
JavaScript
const d = new Date(2024, 4, 15, 10, 30, 45)
d.getFullYear() // 2024(年)
d.getMonth() // 4(五月,0-11)
d.getDate() // 15(月中第几天,1-31)
d.getDay() // 3(星期三,0-6,0 = 周日)
d.getHours() // 10(小时,0-23)
d.getMinutes() // 30(分钟,0-59)
d.getSeconds() // 45(秒,0-59)
d.getMilliseconds() // 0(毫秒,0-999)
// UTC 版本(注意大小写是 UTC,方法名是大写 UTC)
d.getUTCFullYear() // 2024
d.getUTCMonth() // 4(按 UTC 时区算)
// 时间戳
d.getTime() // 1715753445000(毫秒时间戳)
Date.now() // 当前时间戳3. 设置日期信息
JavaScript
const d = new Date()
d.setFullYear(2025)
d.setMonth(0) // 设置为一月
d.setDate(1) // 设置为一号
d.setHours(0, 0, 0, 0) // 设为 0 点 0 分 0 秒
// 链式设置(因为 set 方法返回时间戳)
d.setFullYear(2025).setMonth(0).setDate(1)4. 日期格式化
JavaScript
// 1. 内置方法
const d = new Date()
d.toString() // 'Wed May 15 2024 10:30:45 GMT+0800 (中国标准时间)'
d.toDateString() // 'Wed May 15 2024'
d.toTimeString() // '10:30:45 GMT+0800 (中国标准时间)'
d.toISOString() // '2024-05-15T02:30:45.000Z'(UTC)
d.toJSON() // 等同于 toISOString
d.toLocaleString() // '2024/5/15 10:30:45'(按本地区)
d.toLocaleDateString() // '2024/5/15'
d.toLocaleTimeString() // '10:30:45'
// 2. 自定义格式化(YYYY-MM-DD HH:mm:ss)
function formatDate(date, fmt = 'YYYY-MM-DD HH:mm:ss') {
if (!(date instanceof Date)) date = new Date(date)
const map = {
YYYY: date.getFullYear(),
MM: String(date.getMonth() + 1).padStart(2, '0'),
DD: String(date.getDate()).padStart(2, '0'),
HH: String(date.getHours()).padStart(2, '0'),
mm: String(date.getMinutes()).padStart(2, '0'),
ss: String(date.getSeconds()).padStart(2, '0')
}
return fmt.replace(/YYYY|MM|DD|HH|mm|ss/g, key => map[key])
}
formatDate(new Date()) // '2024-05-15 10:30:45'
formatDate(new Date(), 'YYYY/MM/DD') // '2024/05/15'
// 3. 高级格式化推荐方案:使用 dayjs、date-fns
// 不要重复造轮子,复杂的日期逻辑交给专业库5. Date 的常见陷阱
JavaScript
// 1. 月份从 0 开始
new Date(2024, 5, 15) // 6 月 15 日(不是 5 月)
// 2. 时间戳是毫秒,不是秒
Date.now() // 1715753445000
Math.floor(Date.now() / 1000) // 秒级时间戳
// 3. Date 解析的时区问题
new Date('2024-05-15') // UTC 0 点
new Date('2024-05-15T00:00:00') // UTC 0 点
new Date(2024, 4, 15) // 本地时间 0 点
// 4. getTime 在 -1900 之前的整数会被视为年份
new Date(99, 0) // 1900 + 99 年 = 1999
new Date(2024, 0) // 2024 年二、倒计时实现
1. 基础倒计时
JavaScript
function countdown(targetTime) {
const now = Date.now()
const diff = targetTime - now // 毫秒差
if (diff <= 0) return { days: 0, hours: 0, minutes: 0, seconds: 0, expired: true }
const days = Math.floor(diff / (1000 * 60 * 60 * 24))
const hours = Math.floor(diff / (1000 * 60 * 60) % 24)
const minutes = Math.floor(diff / (1000 * 60) % 60)
const seconds = Math.floor(diff / 1000 % 60)
return { days, hours, minutes, seconds, expired: false }
}
// 用 setInterval 每秒刷新
const target = new Date('2025-01-01T00:00:00').getTime()
setInterval(() => {
const { days, hours, minutes, seconds } = countdown(target)
console.log(`距离 2025 年还有 ${days} 天 ${hours} 时 ${minutes} 分 ${seconds} 秒`)
}, 1000)2. 跨时区倒计时
JavaScript
// 同一时刻,不同时区显示不同时间
function getLocalTime(utcTime, targetTimezone) {
// UTC 时间的字符串
const utcStr = new Date(utcTime).toLocaleString('en-US', { timeZone: 'UTC' })
// 转成目标时区时间
const localDate = new Date(utcStr)
return new Intl.DateTimeFormat('zh-CN', {
timeZone: targetTimezone,
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
}).format(localDate)
}
getLocalTime(Date.now(), 'Asia/Shanghai') // '2024/05/15 10:30:45'
getLocalTime(Date.now(), 'America/New_York') // '2024/05/14 22:30:45'3. 性能更优的倒计时
JavaScript
// 倒计时 1 秒的 0-2ms 是允许的,可以不用 setInterval,用 requestAnimationFrame 或 setTimeout 链式
function startCountdown(targetTime, onTick, onComplete) {
let timer = null
function tick() {
const now = Date.now()
const diff = targetTime - now
if (diff <= 0) {
onComplete?.()
return
}
onTick?.(diff)
timer = setTimeout(tick, 1000 - (diff % 1000)) // 对齐到整数秒
}
tick()
return () => clearTimeout(timer) // 返回取消函数
}三、变量提升与函数提升
1. 经典坑题
JavaScript
// 题目 1
console.log(a) // undefined(不是 ReferenceError)
var a = 1编译阶段展开(实际执行顺序)
JavaScript
// 引擎看到 var,编译阶段把 a 提升到当前作用域顶部,赋值为 undefined
// console.log 看到的是声明提升后的默认值
// 编译后:
var a // a 已声明但未赋值
console.log(a) // undefined
a = 12. 函数提升的优先级
JavaScript
// 函数声明 > 变量声明
console.log(typeof fn)
function fn() {}
var fn = 'hello' // 运行时赋值
console.log(typeof fn)实际编译后:
JavaScript
function fn() {} // 函数声明先提升
var fn // 同名变量,提升但不会覆盖函数
console.log(typeof fn) // 'function'
fn = 'hello' // 运行时赋值,函数被覆盖
console.log(typeof fn) // 'string'3. let / const 的"暂时性死区"(TDZ)
JavaScript
console.log(a) // ❌ ReferenceError
let a = 1
console.log(b) // ❌ ReferenceError(const 同理)
const b = 2
// 关键:let/const 也会提升,但**不能访问**——从作用域开始到声明之间的区域称为 TDZ(Temporal Dead Zone)4. 函数声明 vs 函数表达式的提升
JavaScript
foo() // ✅ 'function' // 函数声明整体提升
function foo() {
console.log('function')
}JavaScript
foo() // ❌ TypeError: foo is not a function(变量已声明但未赋值)
var foo = function() {
console.log('expression')
}
// 编译提升:var foo(undefined)
// 执行到 foo() 时 foo 还是 undefined
// 或 let 的话直接 ReferenceError5. 块级作用域
JavaScript
// 在 ES5 中,var 没有块级作用域
for (var i = 0; i < 3; i++) {
// ...
}
console.log(i) // 3(泄漏到外部)
// ES6 的 let / const 有块级作用域
for (let i = 0; i < 3; i++) {
// ...
}
console.log(i) // ReferenceError
// 这是经典的"for 循环闭包问题"的解决方案6. 综合陷阱题
JavaScript
// 题目
var a = 1
function fn() {
console.log(a) // undefined(不是全局的 1!)
var a = 2
}
fn()
// 编译后:
// var a // 全局
// a = 1
// function fn() { var a; console.log(a); a = 2 }
// fn 执行时,作用域内有 a = undefined,赋值发生在 console 之后JavaScript
// 题目:循环 + 定时器
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0)
}
// 输出 3, 3, 3
// 解决方案 1:let 形成块级作用域
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0)
}
// 输出 0, 1, 2
// 解决方案 2:闭包保存
for (var i = 0; i < 3; i++) {
((j) => {
setTimeout(() => console.log(j), 0)
})(i)
}
// 输出 0, 1, 2四、JS 常用工具函数
1. 类型判断
JavaScript
// 通用 typeof
typeof 123 // 'number'
typeof 'string' // 'string'
typeof null // 'object'(经典坑)
typeof undefined // 'undefined'
typeof [] // 'object'
typeof {} // 'object'
// 精确类型判断
function getType(val) {
if (val === null) return 'null'
return Object.prototype.toString.call(val).slice(8, -1).toLowerCase()
}
getType([]) // 'array'
getType({}) // 'object'
getType(new Date()) // 'date'
getType(/foo/) // 'regexp'
getType(null) // 'null'
getType(undefined) // 'undefined'
getType(new Map()) // 'map'
// 简洁版
const isArr = Array.isArray
const isObj = v => typeof v === 'object' && v !== null2. 深拷贝(手写版)
JavaScript
function deepClone(obj, hash = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj
if (obj instanceof Date) return new Date(obj)
if (obj instanceof RegExp) return new RegExp(obj)
if (hash.has(obj)) return hash.get(obj)
const cloneObj = Array.isArray(obj) ? [] : {}
hash.set(obj, cloneObj)
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
cloneObj[key] = deepClone(obj[key], hash)
}
}
return cloneObj
}3. 防抖
JavaScript
function debounce(fn, delay) {
let timer = null
return function(...args) {
clearTimeout(timer)
timer = setTimeout(() => fn.apply(this, args), delay)
}
}
// 立即执行版(第一次触发立刻执行,之后等 delay)
function debounceImmediate(fn, delay) {
let timer = null
return function(...args) {
const callNow = !timer
clearTimeout(timer)
timer = setTimeout(() => {
timer = null
}, delay)
if (callNow) fn.apply(this, args)
}
}4. 节流
JavaScript
// 时间戳版(首次立刻执行,结束后不执行)
function throttle(fn, delay) {
let lastTime = 0
return function(...args) {
const now = Date.now()
if (now - lastTime >= delay) {
lastTime = now
fn.apply(this, args)
}
}
}
// 定时器版(首次延迟执行,结束后再执行一次)
function throttleTimer(fn, delay) {
let timer = null
return function(...args) {
if (!timer) {
timer = setTimeout(() => {
timer = null
fn.apply(this, args)
}, delay)
}
}
}5. 数组去重
JavaScript
// 方法1:Set
const unique = arr => [...new Set(arr)]
// 方法2:filter
const unique2 = arr => arr.filter((v, i) => arr.indexOf(v) === i)
// 方法3:reduce
const unique3 = arr => arr.reduce((acc, v) => acc.includes(v) ? acc : [...acc, v], [])
// 对象数组去重(按某个字段)
const uniqueBy = (arr, key) => {
const seen = new Set()
return arr.filter(item => {
const k = item[key]
return seen.has(k) ? false : seen.add(k)
})
}6. 数组扁平化
JavaScript
// 方法1:flat(ES2019)
arr.flat(Infinity)
// 方法2:reduce + 递归
function flatten(arr) {
return arr.reduce((acc, v) => acc.concat(Array.isArray(v) ? flatten(v) : v), [])
}
// 方法3:toString(仅适用于基本类型)
arr.toString().split(',') // 注意会改变类型,且对 null/undefined 异常7. URL 参数解析
JavaScript
// URLSearchParams(推荐)
function getQueryParams(url = window.location.search) {
const params = new URLSearchParams(url)
const result = {}
for (const [key, value] of params) {
result[key] = value
}
return result
}
// 手写解析
function parseQuery(url = window.location.search) {
const query = url.slice(1) // 去掉 '?'
const obj = {}
query.split('&').forEach(item => {
if (!item) return
const [key, value = ''] = item.split('=')
obj[decodeURIComponent(key)] = decodeURIComponent(value)
})
return obj
}8. 千分位
JavaScript
function thousands(num) {
const parts = String(num).split('.')
parts[0] = parts[0].replace(/\B(?=(\d{3})+$)/g, ',')
return parts.join('.')
}
thousands(1234567) // '1,234,567'
thousands(1234567.89) // '1,234,567.89'
thousands(1234567.891234) // '1,234,567.891234'
// 或用 Intl
new Intl.NumberFormat().format(1234567) // '1,234,567'(取决于 locale)9. 随机 ID
JavaScript
function randomId(length = 8) {
return Math.random().toString(36).slice(2, 2 + length)
}
randomId() // 'k3j1h2g4'
randomId(16) // 'k3j1h2g4a9b0c1d2'
// 更高质量:crypto.randomUUID(现代浏览器和 Node 14+)
crypto.randomUUID() // '550e8400-e29b-41d4-a716-446655440000'10. 对象深比较
JavaScript
function isEqual(a, b) {
if (a === b) return true
if (typeof a !== typeof b) return false
if (typeof a !== 'object' || a === null || b === null) return false
const aKeys = Object.keys(a)
const bKeys = Object.keys(b)
if (aKeys.length !== bKeys.length) return false
return aKeys.every(key => isEqual(a[key], b[key]))
}五、面试高频问答
Q1: 如何实现一个倒计时?
答:
- 计算目标时间与当前时间的差值(毫秒)
- 把毫秒换算成天 / 时 / 分 / 秒
- 用
setInterval或对齐到秒的setTimeout链式调用,每秒更新 - 注意页面隐藏时 setInterval 会节流优化(最小 1s),但不影响倒计时精度
- 组件销毁记得清理定时器,防止内存泄漏
Q2: 解释一下 JS 的变量提升?
答:JS 引擎在编译阶段把 var 声明和 function 声明提升到当前作用域顶部,但赋值留在原地。let / const 也会"提升",但从作用域开始到声明之间的区域称为暂时性死区(TDZ),访问会抛 ReferenceError。
Q3: 为什么变量提升对闭包问题有影响?
答:for 循环用 var 声明变量时,所有循环共享同一个变量引用,定时器回调执行时,循环早已结束,i 已经是最终值。用 let 声明变量会形成独立的块级作用域,每次循环都创建一个新的绑定。详见 闭包与作用域。