【Web APIs-Day4】节点操作与移动端事件

2 阅读4分钟

【Web APIs-Day4】节点操作与移动端事件

📺 对应视频:P117-P126 | 🎯 核心目标:掌握日期对象、DOM节点的增删克隆操作,了解Swiper插件使用


一、日期对象

1.1 创建日期对象

// 当前时间
let now = new Date()

// 指定时间
let d1 = new Date('2024-01-01')
let d2 = new Date('2024-01-01 12:30:00')
let d3 = new Date(2024, 0, 1)        // 注意:月份从0开始!
let d4 = new Date(1704067200000)     // 时间戳(毫秒)

1.2 日期方法

let now = new Date()

// 获取各部分
now.getFullYear()   // 年份,如 2024
now.getMonth()      // 月(0-11!使用时要 +1)
now.getDate()       // 日(1-31)
now.getDay()        // 星期(0-6,0=周日)
now.getHours()      // 时(0-23)
now.getMinutes()    // 分(0-59)
now.getSeconds()    // 秒(0-59)
now.getMilliseconds() // 毫秒

// 设置各部分
now.setFullYear(2025)
now.setMonth(11)     // 12月(注意:0-11)
now.setDate(25)

1.3 时间戳

// 时间戳:从 1970-01-01 00:00:00 UTC 至今的毫秒数
Date.now()           // 获取当前时间戳(常用)
new Date().getTime() // 也可以
+new Date()          // 隐式转换(也常用)

// 时间戳的应用
const start = Date.now()
// ... 执行一些操作
const end = Date.now()
console.log(`耗时:${end - start}ms`)

1.4 格式化日期(实战)

function formatDate(date = new Date()) {
  const year = date.getFullYear()
  const month = String(date.getMonth() + 1).padStart(2, '0')  // 补零
  const day = String(date.getDate()).padStart(2, '0')
  const hours = String(date.getHours()).padStart(2, '0')
  const minutes = String(date.getMinutes()).padStart(2, '0')
  const seconds = String(date.getSeconds()).padStart(2, '0')
  
  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
console.log(formatDate())  // '2024-01-15 09:05:30'

// 星期几
const weeks = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
console.log(weeks[new Date().getDay()])

1.5 倒计时案例

function countdown(targetDate) {
  const target = new Date(targetDate).getTime()
  
  function update() {
    const now = Date.now()
    const diff = target - now  // 剩余毫秒数
    
    if (diff <= 0) {
      clearInterval(timer)
      console.log('时间到!')
      return
    }
    
    const days = Math.floor(diff / (1000 * 60 * 60 * 24))
    const hours = Math.floor(diff % (1000 * 60 * 60 * 24) / (1000 * 60 * 60))
    const minutes = Math.floor(diff % (1000 * 60 * 60) / (1000 * 60))
    const seconds = Math.floor(diff % (1000 * 60) / 1000)
    
    document.querySelector('.days').textContent = String(days).padStart(2, '0')
    document.querySelector('.hours').textContent = String(hours).padStart(2, '0')
    document.querySelector('.minutes').textContent = String(minutes).padStart(2, '0')
    document.querySelector('.seconds').textContent = String(seconds).padStart(2, '0')
  }
  
  update()
  const timer = setInterval(update, 1000)
}

countdown('2025-01-01 00:00:00')

二、DOM 节点操作

2.1 节点类型与关系

const parent = document.querySelector('.parent')
const child = document.querySelector('.child')

// 父节点
child.parentNode          // 直接父节点(含文本节点)
child.parentElement       // 直接父元素节点(推荐)

// 子节点
parent.childNodes         // 所有子节点(含文本、注释节点)
parent.children           // 所有子元素节点(推荐,HTMLCollection)
parent.firstChild         // 第一个子节点(可能是文本节点)
parent.firstElementChild  // 第一个子元素节点(推荐)
parent.lastElementChild   // 最后一个子元素节点

// 兄弟节点
child.previousSibling          // 前一个兄弟节点
child.previousElementSibling   // 前一个兄弟元素节点(推荐)
child.nextSibling               // 后一个兄弟节点
child.nextElementSibling        // 后一个兄弟元素节点(推荐)

2.2 创建节点

// 创建元素节点
const div = document.createElement('div')
const li = document.createElement('li')
const img = document.createElement('img')

// 创建文本节点
const text = document.createTextNode('Hello World')

// 创建文档片段(批量操作用,减少回流)
const fragment = document.createDocumentFragment()
for (let i = 0; i < 100; i++) {
  const li = document.createElement('li')
  li.textContent = `第${i}项`
  fragment.appendChild(li)
}
document.querySelector('ul').appendChild(fragment)  // 只触发一次渲染

2.3 插入节点

const parent = document.querySelector('.parent')
const newEl = document.createElement('div')
const refEl = document.querySelector('.reference')

// appendChild:追加到末尾
parent.appendChild(newEl)

// insertBefore:插入到参考元素之前
parent.insertBefore(newEl, refEl)  // 在 refEl 前面插入

// insertAdjacentElement(更灵活,ES6+)
refEl.insertAdjacentElement('beforebegin', newEl)  // refEl 之前
refEl.insertAdjacentElement('afterbegin', newEl)   // refEl 内部开头
refEl.insertAdjacentElement('beforeend', newEl)    // refEl 内部末尾
refEl.insertAdjacentElement('afterend', newEl)     // refEl 之后

// insertAdjacentHTML(直接插入HTML字符串)
refEl.insertAdjacentHTML('afterend', '<p>新段落</p>')

2.4 克隆节点

const original = document.querySelector('.card')

// cloneNode(false):浅克隆,只克隆元素本身(不含子节点)
const shallow = original.cloneNode(false)

// cloneNode(true):深克隆,克隆元素及所有子节点(推荐)
const deep = original.cloneNode(true)

// 克隆后需要插入到文档中才能显示
document.querySelector('.container').appendChild(deep)

// ⚠️ 注意:克隆不会复制事件监听器(addEventListener绑定的)

2.5 删除节点

const el = document.querySelector('.target')

// 现代方式(推荐)
el.remove()

// 旧方式(通过父节点删除)
el.parentNode.removeChild(el)

// 清空所有子节点
parent.innerHTML = ''     // 简单但可能有内存泄漏(事件监听残留)
while (parent.firstChild) {
  parent.removeChild(parent.firstChild)  // 逐个删除(干净)
}

三、Swiper 插件

3.1 什么是 Swiper?

Swiper 是一个成熟的移动端滑动插件,支持触摸滑动、轮播、无限循环等效果,无需手写复杂逻辑。

CDN 引入:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css"/>
<script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>

3.2 基本使用

<!-- HTML 结构(固定格式) -->
<div class="swiper">
  <div class="swiper-wrapper">
    <div class="swiper-slide">幻灯片1</div>
    <div class="swiper-slide">幻灯片2</div>
    <div class="swiper-slide">幻灯片3</div>
  </div>
  <!-- 分页器 -->
  <div class="swiper-pagination"></div>
  <!-- 导航按钮 -->
  <div class="swiper-button-prev"></div>
  <div class="swiper-button-next"></div>
</div>
// JS 初始化
const swiper = new Swiper('.swiper', {
  // 基础配置
  loop: true,          // 无限循环
  autoplay: {
    delay: 3000,       // 自动播放,3秒切换
    disableOnInteraction: false  // 用户操作后继续自动播放
  },
  
  // 分页器
  pagination: {
    el: '.swiper-pagination',
    clickable: true    // 点击分页器可跳转
  },
  
  // 导航按钮
  navigation: {
    prevEl: '.swiper-button-prev',
    nextEl: '.swiper-button-next'
  },
  
  // 效果
  effect: 'fade',     // 'slide'(默认)| 'fade' | 'cube' | 'coverflow'
  
  // 回调
  on: {
    slideChange() {
      console.log('当前索引:', this.activeIndex)
    }
  }
})

// 操作方法
swiper.slideNext()          // 下一张
swiper.slidePrev()          // 上一张
swiper.slideTo(2)           // 跳转到索引2
swiper.autoplay.stop()      // 停止自动播放
swiper.autoplay.start()     // 开始自动播放

四、移动端事件

4.1 触摸事件

const el = document.querySelector('.touch-area')

el.addEventListener('touchstart', e => {
  console.log('手指按下')
  console.log(e.touches)          // 所有触摸点
  console.log(e.changedTouches)   // 本次变化的触摸点
  const touch = e.changedTouches[0]
  console.log(touch.clientX, touch.clientY)  // 触摸位置
})

el.addEventListener('touchmove', e => {
  e.preventDefault()  // 阻止默认滚动
  console.log('手指移动')
})

el.addEventListener('touchend', e => {
  console.log('手指抬起')
})

4.2 手势识别(滑动方向判断)

let startX, startY

el.addEventListener('touchstart', e => {
  startX = e.touches[0].clientX
  startY = e.touches[0].clientY
})

el.addEventListener('touchend', e => {
  const endX = e.changedTouches[0].clientX
  const endY = e.changedTouches[0].clientY
  
  const deltaX = endX - startX
  const deltaY = endY - startY
  
  if (Math.abs(deltaX) > Math.abs(deltaY)) {
    // 水平滑动
    if (deltaX > 50) console.log('向右滑')
    else if (deltaX < -50) console.log('向左滑')
  } else {
    // 垂直滑动
    if (deltaY > 50) console.log('向下滑')
    else if (deltaY < -50) console.log('向上滑')
  }
})

五、知识图谱

节点操作与移动端事件
├── 日期对象
│   ├── new Date(),getFullYear/Month/Date/Day...
│   ├── 时间戳:Date.now()
│   └── 应用:格式化、倒计时
├── DOM 节点操作
│   ├── 节点关系:parentElement / children / nextElementSibling
│   ├── 创建:createElement / createDocumentFragment
│   ├── 插入:appendChild / insertBefore / insertAdjacentElement
│   ├── 克隆:cloneNode(true/false)
│   └── 删除:el.remove() / removeChild
├── Swiper 插件
│   ├── 引入 CSS + JS
│   ├── HTML 结构(swiper/wrapper/slide)
│   └── JS 配置(loop/autoplay/pagination/navigation)
└── 移动端事件
    ├── touchstart / touchmove / touchend
    ├── e.touches / e.changedTouches
    └── 手势判断(deltaX/deltaY)

六、高频面试题

Q1: childNodes children 的区别?

childNodes 返回所有子节点(含文本节点、注释节点),是 NodeList;children 只返回子元素节点,是 HTMLCollection。日常开发用 children 更方便。

Q2:如何高效地批量插入 DOM?

使用 document.createDocumentFragment() 创建文档片段,把所有元素先加入片段,最后一次性插入 DOM,只触发一次重排/重绘,性能更好。

Q3: cloneNode 会克隆事件监听吗?

不会。通过 addEventListener 绑定的事件监听器不会被克隆(inline onclick 属性会被克隆,但不推荐)。克隆后需要重新绑定事件。


⬅️ 上一篇Web APIs Day3 - 事件进阶 ➡️ 下一篇Web APIs Day5 - BOM与本地存储