JavaScript 的 Date 对象总有你不知道的?

1,964 阅读7分钟

起源

  • 最近有个需要自己去选择日期的搜索,由于原生内置的input.type='date'样式太丑,也难以修改,所以用原生的Date对象去实现这样一个功能。
  • 最近有在做一些面试,在问JavaScript进行数据类型检测时,有这样一个问题:使用typeof检测Date对象返回的是什么,很多人都说的是'object'。当Date单独出现时,它其实是一个函数,很多人被使用时使用new操作符给影响了。
  • 其他常用的类似还有ObjectRegExpArrayBooleanStringFunctionNumberError
  • 注:Math对象使用typeof检测返回的是object。Math 对象并不像 Date 和 String 那样是对象的类,因此没有构造函数 Math(),无法使用new操作符。 JavaScript Math 对象

Date简介

  • Date 对象用于处理日期与时间。
  • DateJavaScript 的内置对象。
  • 使用 typeof 检测 Date 返回的是 "function"
  • Date在转换时,toPrimitive方法默认第二个参数是string,所以会首先调用toString方法。
  • Date的原型上的方法:

Date使用

  • 直接使用;
Date();
  • 使用new操作符。
new Date();

常用的方法

new Date().getFullYear() // 2020 获取四位的当前年份数字
new Date().getMonth() // 4 返回当前的月份数字减一,所以一般我们会在后面加一表示当前的月份 
new Date().getDate() // 14 获取今天是当前月第几天
new Date().getHours() // 15 获取现在的小时,24格式
new Date().getMinutes() // 22 获取现在的分钟
new Date().getSeconds() // 31 获取现在的秒
new Date().getMilliseconds() // 793 获取现在的毫秒
new Date().getTime() // 1589440967857 返回距 1970 年 1 月 1 日之间的毫秒数
new Date().getDay() // 4 返回今天星期几的数字

  • 还有对应的getUTCDaygetUTCFullYeargetUTCHours...,中间加了UTC,这个代表根据世界时返回。
  • 对应每个方法还有set...代表设置相应的值。

应用场景一:格式化

  • 在日常的应用中,我们需要把某个时间通过某个方法格式化为一个我们常见的日期显示格式,类似于2020-05-15 14:00:00这样。
  • 还有其他格式化的需求可以在optionObject里面继续添加。

应用场景二:获取指定日期

  • 这里只列举了一个
  • 传入的字符串需要是能被new Date(string)转化为正确时间的时间格式字符串,为空的话默认是今天。

应用场景三:实现一个日历

  • 可以通过日期的方法获取对应某年某月有多少天,进而可以实现一个简易的日历。
效果展示

  • 实现功能:
    1. 默认展示当前日期;
    2. 可点击快速切换当前月份的上下月份;
    3. 切换上下月份时,非本年本月,默认显示选中当月第一天,本年本月显示选中当前天;
    4. 可点击当前月份,显示所有月份,进行月份的快速选择;
    5. 可以点击年份,显示当前年份向前16年(2020-2005),年份快速选择;
    6. 年份可以选择上一页和下一页;
    7. 可以清除日期,清除后重新打开,默认显示选中本年本月今天;
页面布局
  • 使用了一个div做包裹层,内部分为四块:日期值显示、年份选择、月份选择、基本显示。
默认展示当前日期
  • 首先我们要知道每个月的第一天是星期几,我们可以通过new Date('5/1/2020').getDay() // 5,传入想要获取的年和月。
  • 通过getDate()方法获取到对应的月有多少天。
  • 我们需要写一个公共方法getDayHtmlDom用来渲染每个月的日历,这个方法接受五个参数,start:从那个位置开始渲染数字,end:到哪个位置数字渲染结束,dom:渲染到哪个元素内,len:渲染的总长度,current:当前渲染的日期选中哪一个。
const nowYear = new Date().getFullYear();
const nowMonth = new Date().getMonth() + 1;
const nowDay = new Date().getDate();

const currentMonthDom = document.getElementById('current-month');
const currentYearDom = document.getElementById('current-year');
// 初始化日期为今天
const initDate = function() {
  const monthDays = new Date(nowYear, nowMonth, 0).getDate();
  const start = new Date(`${nowMonth}/1/${nowYear}`).getDay();
  const end = start -1 + monthDays;
  // 注:如果第一天刚好为周天,end就为end计算,如果第一天不是周天end的长度+1
  // 如果不+1的话,当天数加start刚好为7的倍数时,后面的日期会显示不全
  const len = Math.ceil((start === 7 ? end : end + 1)/7) * 7;
  const current = nowDay + start - 1;
  getDayHtmlDom(start, end, dayDom, len, current)
  currentMonthDom.innerHTML = nowMonth;
  currentYearDom.innerHTML = nowYear
}
// 生成并渲染对应每月dom的子元素
const getDayHtmlDom = function(start, end, dom, len, current) {
  let result = ''
  for(let i = 0; i < len; i ++) {
    if(i < start || i > end) {
      result += `<li></li>`;
    } else if (i === current) {
      result += `<li class='active'>${i-start+1}</li>`;
    } else {
      result += `<li>${i-start+1}</li>`;
    }
  }
  dom.innerHTML = result;
}
initDate()
选中某一天
  • 在鼠标点击某一天后,移除之前选中的日期,并给当前点击的日期添加选中状态,点击确定后,把对应选中的日期填入输入框。
const dayDom = document.getElementById('day');
// 点击每个日期
dayDom.onclick = function(e) {
  // 这里需要判断点击的对象是不是li标签
  if(e.target.tagName === 'LI') {
    const value = e.target.innerHTML;
    const lists = dayDom.getElementsByTagName('li');
    // 判断点击的li是有值的,而不是填充位置的空白li
    if(value !== '') {
      dayDom.getElementsByClassName('active')[0].classList.remove('active');
      const listsLen = lists.length;
      for(let i = 0; i < listsLen; i ++) {
        if(lists[i].innerHTML === value) {
          lists[i].classList.add('active');
        }
      }
    }
  }
}
// 点击确定
submitDom.onclick = function() {
  const year = currentYearDom.innerHTML;
  const month = numberFirstAddZero(currentMonthDom.innerHTML);
  const day = dayDom.getElementsByClassName('active')[0].innerHTML;
  calendarInputDom.value = `${year}-${month}-${numberFirstAddZero(day)}`
  selectDom.style.display = 'none';
}
点击左右箭头快速切换上下月
  • 在点击箭头的时候,我们首先需要知道获取的上还是下,我们可以通过一个direct参数来判断,curYearcurMonth其实是为后面快速切换年和月添加的参数,当有这个参数是,默认就使用传入的年和月去渲染。
const calendarInputDom = document.getElementById('calendar');
// 切换年限和月份
const preOrNextClick = function(direct, curYear, curMonth) {
  let currentYear = Number(currentYearDom.innerHTML);
  let currentMonth = Number(currentMonthDom.innerHTML);
  // 判断箭头放向
  if(direct === 'left') {
    currentMonth -= 1;
    // 上一月已经是0就跳到上一年的12月
    if(currentMonth === 0) {
      currentMonth = 12
      currentYear -= 1
    } 
  } else {
    currentMonth += 1;
    // 上一月已经是13就跳到下一年的1月
    if(currentMonth === 13) {
      currentMonth = 1
      currentYear += 1
    } 
  }
  if(curYear) {
    currentYear = curYear
  }
  if(curMonth) {
    currentMonth = curMonth
  }
  const monthDays = new Date(currentYear, currentMonth, 0).getDate();
  const start = new Date(`${currentMonth}/1/${currentYear}`).getDay();
  const end = start -1 + monthDays;
  const len = Math.ceil((start === 7 ? end : end + 1)/7) * 7;
  const current = start;
  getDayHtmlDom(start, end, dayDom, len, current)
  currentMonthDom.innerHTML = currentMonth;
  currentYearDom.innerHTML = currentYear;
  // 如果是本年本月,并且输入框无值,选中今天
  if(currentYear === nowYear && currentMonth === nowMonth && calendarInputDom.value === '') {
    initDate()
  }
}
快速选中月份
  • 通过点击select页面上的月份数字,打开一个弹窗,弹窗内容就是1-12的月份显示,月份固定为12个月所以我们html固定写好就行了,月份选中页面默认选中页面上当前选中的月份,,点击月份后,关闭页面并更新显示为选中的月份日历。
const monthSelectDom = document.getElementById('month-select');
const monthulDom = document.getElementsByClassName('month-ul')[0];
const monthLisDom = monthulDom.getElementsByTagName('li');

// 点击日期上面的月份
currentMonthDom.onclick = function() {
  monthulDom.getElementsByClassName('active')[0].classList.remove('active')
  for(let i = 0; i < 12; i ++) {
    // 当前月份值相等就选中
    if(monthLisDom[i].innerHTML === currentMonthDom.innerHTML) {
      monthLisDom[i].classList.add('active');
    }
  }
  monthSelectDom.style.display = 'block';
  selectDom.style.display = 'none';
}

// 选择月份
monthulDom.onclick = function(e) {
  if(e.target.tagName === 'LI') {
    const value = e.target.innerHTML;
    // 重新渲染选中的月日历
    preOrNextClick('left', Number(currentYearDom.innerHTML), value);
    monthSelectDom.style.display = 'none';
    selectDom.style.display = 'block';
  }
}
年份快速选择
  • 年份的快速选择与月份基本相同,只是具体的元素内容,我们需要手动通过js代码去生成,这里我每页生成的是16个。
const yearSelectDom = document.getElementById('year-select');
const yearulDom = document.getElementsByClassName('year-ul')[0];
// 点击日期上面年份
currentYearDom.onclick = function() {
  setYearHtml(Number(currentYearDom.innerHTML))
  yearSelectDom.style.display = 'block';
  selectDom.style.display = 'none';
}

// 设置年份渲染的html
const setYearHtml = function (start) {
  let result = ''
  for(let i = 0; i < 16; i ++) {
    const num = start - i;
    if (num === Number(currentYearDom.innerHTML)) {
      result += `<li class='active'>${num}</li>`;
    } else {
      result += `<li>${num}</li>`;
    }
  }
  yearulDom.innerHTML = result;
}
// 选择年份
yearulDom.onclick = function(e) {
  if(e.target.tagName === 'LI') {
    const value = e.target.innerHTML;
    preOrNextClick('left', value);
    yearSelectDom.style.display = 'none';
    selectDom.style.display = 'block';
  }
}
年份切换上下页
  • 我们在选择年份的时候有时候会选择超过我们当前显示的年份,例如我可能选择2021年,或者1995年等等年份,上面我们只显示了162020-2005)个年份,所以是不够的,我们需要通过切换上下页来选择其他的年份。
// 点击年份的上/下一页
const yearPreOrNext = function(direct) {
  const yearLisDom = yearulDom.getElementsByTagName('li');
  const first = yearLisDom[0].innerHTML;
  const last = yearLisDom[yearLisDom.length - 1].innerHTML;
  if(direct === 'pre') {
    setYearHtml(Number(last) - 1)
  } else {
    setYearHtml(Number(first) + 16)
  }
}
  • 注:我这里没有设置边界条件,例如小到0时或者特别大的时候。
清除后默认显示当前日期
  • 其实这个很简单,当我们点击清除后,重新调用initDate方法就可以了,当然,我们还需要考虑一些窗口的关闭。
const clearDom = document.getElementById('clear');
const showDom = document.getElementById('show');
// 清除
clearDom.onclick = function() {
  if(calendarInputDom.value !== '') {
    calendarInputDom.value = ''
    initDate()
  }
  showDomFunction()
}
// 关闭年份和月份快速选择窗口,显示日期选择窗口
const showDomFunction = function() {
  yearSelectDom.style.display = 'none';
  monthSelectDom.style.display = 'none';
  selectDom.style.display = 'block';
}
// 输入框获取焦点
calendarInputDom.onfocus = function() {
  showDomFunction()
}
// 展示
showDom.onclick = function() {
  showDomFunction()
}
// 取消
cancleDom.onclick = function() {
  selectDom.style.display = 'none';
  if(calendarInputDom.value === '') {
    initDate()
  }
}
其他方法
  • 填充字符串'0',这个为了简便,没有做异常判断;
// 小于9de加'0'
const numberFirstAddZero = function(value) {
  if(value > 9) return value;
  return '0' + value;
}
其他问题
  • 首先代码中其实有些地方还是可以复用的,你可以自己实现的时候再更抽离一下;
  • 代码中有很多的常量没有定义为一个变量,你可以自己修改一下,例如月份的长度、年份的长度以及常量为1的等等;
  • 现在的功能只能说基本上有了,你可以添加一些其他的功能:
    1. 可以通过输入框输入一个年份,然后选择对应年份的第一个月的第一天;
    2. 扩展上面的还可以输入年月以及年月日;
    3. ....

其他场景...

最后

源码

  • 由于篇幅太长,就没有贴源码了,有需要源码的可以留言或者添加我QQ2586240930