5大DOM/BOM核心考点:从入门到精通,让面试官眼前一亮

前言

上周面试了一位3年经验的前端,简历上写着"精通DOM操作"。我问他事件委托的原理,他说"就是给父元素绑定事件"。我追问"为什么用事件委托?性能提升多少?",他支支吾吾答不上来。

这就是典型的'表面理解':知道概念名词,不懂底层原理和性能影响。

今天这5道DOM与BOM操作题目,每道题我都会告诉你:面试官为什么问这个、标准答案怎么说、什么回答会让你直接出局。每题都配"速记公式",面试前一晚看这篇就够了。

1. 什么是DOM?DOM操作有哪些方法?

速记公式:DOM文档树,查询增删改,性能要关注

  • DOM本质:文档对象模型,HTML的树形结构表示
  • 核心操作:查询、创建、插入、删除、修改、样式操作
  • 性能关键:减少重排重绘,批量操作

标准答案

DOM(Document Object Model) 是浏览器将HTML文档解析成的树形数据结构,每个HTML标签都是树中的一个节点。通过DOM API,JavaScript可以访问和操作页面内容、结构和样式。

DOM节点类型:

  • 元素节点(如div、p)
  • 文本节点(元素内的文本内容)
  • 属性节点(元素的属性)
  • 注释节点

常用DOM操作方法:

查询操作:

javascript 复制代码
// 基本查询
document.getElementById('id')           // 按id查询
document.getElementsByClassName('class') // 按class查询(返回HTMLCollection)
document.getElementsByTagName('div')    // 按标签名查询
document.querySelector('.class')        // CSS选择器查询单个
document.querySelectorAll('div.class')  // CSS选择器查询所有(返回NodeList)

// 关系查询
parentNode.children                    // 子元素
element.parentNode                     // 父元素
element.previousElementSibling         // 前一个兄弟元素
element.nextElementSibling             // 后一个兄弟元素

创建和修改:

javascript 复制代码
// 创建节点
const newDiv = document.createElement('div')
const newText = document.createTextNode('Hello')

// 修改内容
element.innerHTML = '<span>内容</span>'  // 设置HTML
element.textContent = '文本内容'         // 设置文本
element.setAttribute('data-id', '123')  // 设置属性
element.classList.add('active')         // 添加类名

// 样式操作
element.style.color = 'red'             // 设置样式
element.style.display = 'none'          // 隐藏元素

插入和删除:

javascript 复制代码
// 插入节点
parent.appendChild(newNode)             // 末尾插入
parent.insertBefore(newNode, refNode)   // 指定位置插入

// 删除节点
parent.removeChild(childNode)           // 删除子节点
element.remove()                        // 删除自身

// 替换节点
parent.replaceChild(newNode, oldNode)   // 替换节点

面试官真正想听什么

这题考察你对DOM操作的系统性理解和性能意识。

很多人只会用querySelector,不知道其他方法,更不知道不同查询方法的性能差异。面试官想看你是否关注操作效率。

加分回答

"在实际项目中,我特别注意DOM操作的性能。比如:

查询优化:

  • 优先使用getElementById,速度最快
  • 复杂查询用querySelector,但避免在循环中频繁使用
  • 缓存查询结果,避免重复查询

批量操作减少重排:

javascript 复制代码
// ❌ 不好的做法 - 多次重排
for (let i = 0; i < 100; i++) {
  const div = document.createElement('div')
  document.body.appendChild(div) // 每次append都会重排
}

// ✅ 好的做法 - 一次重排
const fragment = document.createDocumentFragment()
for (let i = 0; i < 100; i++) {
  const div = document.createElement('div')
  fragment.appendChild(div)
}
document.body.appendChild(fragment) // 只重排一次

class操作优于style操作:

javascript 复制代码
// ❌ 直接修改多个样式
element.style.width = '100px'
element.style.height = '100px'
element.style.backgroundColor = 'red'

// ✅ 使用class批量修改
element.classList.add('active-box')

理解DOM性能让我在开发大型列表、动画等场景时避免了很多卡顿问题。"

减分回答

❌ "DOM就是HTML标签"(理解太表面)

❌ "只用querySelector就行"(不知道性能差异)

❌ 不知道重排重绘的概念(缺乏性能意识)

2. JavaScript中的事件流是怎样的?事件对象有哪些属性?

速记公式:事件三阶段,捕获目标冒泡,对象含信息

  • 事件流三阶段:捕获 → 目标 → 冒泡
  • 事件对象属性:target、currentTarget、type、preventDefault()、stopPropagation()
  • 事件类型:鼠标、键盘、表单、触摸事件

标准答案

事件流(Event Flow) 描述的是事件在DOM结构中传播的顺序,分为三个阶段:

1. 捕获阶段(Capturing Phase) 事件从window对象向下传播到目标元素的父元素。在这个阶段,使用addEventListener的第三个参数为true可以监听捕获阶段。

2. 目标阶段(Target Phase) 事件到达目标元素本身。

3. 冒泡阶段(Bubbling Phase) 事件从目标元素向上冒泡回window对象。大多数事件默认在冒泡阶段处理。

html 复制代码
<div id="parent">
  <button id="child">点击我</button>
</div>

<script>
document.getElementById('parent').addEventListener('click', function() {
  console.log('父元素捕获阶段') // 先执行
}, true)

document.getElementById('parent').addEventListener('click', function() {
  console.log('父元素冒泡阶段') // 后执行
}, false)

document.getElementById('child').addEventListener('click', function(e) {
  console.log('目标元素') // 中间执行
})
// 点击按钮输出顺序:父元素捕获阶段 → 目标元素 → 父元素冒泡阶段
</script>

事件对象常用属性:

javascript 复制代码
element.addEventListener('click', function(event) {
  // 事件目标
  console.log(event.target)        // 实际触发事件的元素
  console.log(event.currentTarget) // 绑定事件处理程序的元素(this指向这个)
  
  // 事件信息
  console.log(event.type)          // 事件类型 'click'
  console.log(event.timeStamp)     // 事件发生时间戳
  
  // 鼠标事件
  console.log(event.clientX, event.clientY) // 相对视口的坐标
  console.log(event.pageX, event.pageY)     // 相对文档的坐标
  console.log(event.button)                 // 鼠标按钮
  
  // 键盘事件
  console.log(event.key)           // 按键值 'a'
  console.log(event.keyCode)       // 按键码 65
  console.log(event.ctrlKey)       // 是否按下Ctrl
  
  // 方法
  event.preventDefault()           // 阻止默认行为
  event.stopPropagation()          // 阻止事件传播
})

面试官真正想听什么

这题考察你对事件机制的理解深度和实际调试能力。

事件流是前端开发中的重要概念,能清晰解释说明你理解事件传播机制,这在复杂组件开发中很重要。

加分回答

在开发弹窗组件时,事件流机制帮了大忙。点击弹窗外部关闭弹窗的功能:

javascript 复制代码
// 点击弹窗外部关闭
document.addEventListener('click', function(event) {
  const modal = document.getElementById('modal')
  if (!modal.contains(event.target)) {
    closeModal() // 点击弹窗外部,关闭弹窗
  }
})

// 阻止弹窗内部点击事件冒泡到document
modal.addEventListener('click', function(event) {
  event.stopPropagation() // 防止点击弹窗内部触发外部点击事件
})

事件委托就是利用冒泡机制:

javascript 复制代码
// 给父元素绑定事件,处理动态添加的子元素
list.addEventListener('click', function(event) {
  if (event.target.classList.contains('delete-btn')) {
    deleteItem(event.target.dataset.id)
  }
})

理解事件对象让我能处理复杂交互。比如拖拽功能要用到clientX/clientY,键盘快捷键要用keyCode,表单验证要用preventDefault()阻止提交。

减分回答

❌ "事件就是点击触发函数"(理解太简单)

❌ "target和currentTarget一样"(概念混淆)

❌ 不知道事件流三个阶段(基础不扎实)

3. 事件委托是什么?如何实现?有什么优势?

速记公式:委托父元素,利用冒泡,动态高效

  • 原理:利用事件冒泡,在父元素处理子元素事件
  • 实现:event.target判断实际触发元素
  • 优势:减少内存、动态元素、代码简洁

标准答案

事件委托(Event Delegation) 是一种利用事件冒泡机制的技术,将子元素的事件处理程序绑定到父元素上,通过判断event.target来识别实际触发事件的子元素。

基本实现:

javascript 复制代码
// ❌ 传统做法 - 给每个子元素绑定事件
const items = document.querySelectorAll('.item')
items.forEach(item => {
  item.addEventListener('click', function() {
    console.log('点击了项目:', this.textContent)
  })
})

// ✅ 事件委托 - 只需一个事件监听
const list = document.getElementById('list')
list.addEventListener('click', function(event) {
  // 检查点击的是否是.item元素
  if (event.target.classList.contains('item')) {
    console.log('点击了项目:', event.target.textContent)
  }
})

处理动态添加的元素:

javascript 复制代码
// 动态添加按钮
function addNewItem(text) {
  const item = document.createElement('div')
  item.className = 'item'
  item.textContent = text
  document.getElementById('list').appendChild(item)
  // 无需为新元素单独绑定事件
}

// 事件委托自动处理所有现有和未来的.item元素
document.getElementById('list').addEventListener('click', function(event) {
  if (event.target.classList.contains('item')) {
    console.log('点击:', event.target.textContent)
  }
})

// 添加新项目,自动具有点击事件
addNewItem('新项目1')
addNewItem('新项目2')

事件委托的优势:

  1. 减少内存占用:只需一个事件处理程序,而不是N个
  2. 动态元素支持:新添加的元素自动具有事件处理
  3. 代码简洁:避免循环绑定和内存泄漏风险
  4. 性能更好:事件处理程序更少,初始化更快

面试官真正想听什么

这题考察你对性能优化和代码设计的理解。

事件委托是前端性能优化的重要技巧,能熟练使用说明你有良好的编程习惯和性能意识。

加分回答

在大型数据表格中,事件委托的优势特别明显。我之前做项目管理后台,表格有几百行数据,每行都有编辑、删除按钮:

传统做法的问题:

javascript 复制代码
// 初始化时绑定几百个事件
const rows = document.querySelectorAll('tr')
rows.forEach(row => {
  row.querySelector('.edit-btn').addEventListener('click', handleEdit)
  row.querySelector('.delete-btn').addEventListener('click', handleDelete)
})
// 内存占用大,初始化慢

改用事件委托:

javascript 复制代码
// 只需一个事件监听
document.getElementById('table').addEventListener('click', function(event) {
  const target = event.target
  
  if (target.classList.contains('edit-btn')) {
    const row = target.closest('tr')
    handleEdit(row.dataset.id)
  }
  
  if (target.classList.contains('delete-btn')) {
    const row = target.closest('tr')
    handleDelete(row.dataset.id)
  }
})

性能提升明显,而且新增行自动具有事件处理。

注意点: 要用closest()向上查找,因为可能点击的是按钮内的图标或文本。

减分回答

❌ "事件委托就是给父元素加事件"(理解不准确)

❌ "所有事件都用委托"(不区分场景)

❌ 不知道event.target和closest的用法(实践经验不足)

4. 如何阻止事件冒泡和默认行为?

速记公式:stop冒泡,prevent默认,return全阻

  • 阻止冒泡:event.stopPropagation()
  • 阻止默认:event.preventDefault()
  • 同时阻止:return false(jQuery中)

标准答案

阻止事件冒泡:

事件冒泡是指事件从目标元素向上级元素传播的过程。使用event.stopPropagation()可以阻止事件继续传播。

javascript 复制代码
document.getElementById('child').addEventListener('click', function(event) {
  console.log('子元素点击')
  event.stopPropagation() // 阻止事件冒泡到父元素
})

document.getElementById('parent').addEventListener('click', function() {
  console.log('父元素点击') // 不会执行
})

阻止默认行为:

默认行为是浏览器对特定事件的预设反应,如点击链接跳转、提交表单刷新页面等。使用event.preventDefault()可以阻止这些默认行为。

javascript 复制代码
// 阻止链接跳转
document.getElementById('link').addEventListener('click', function(event) {
  event.preventDefault() // 阻止跳转
  console.log('点击了链接但不跳转')
})

// 阻止表单提交
document.getElementById('form').addEventListener('submit', function(event) {
  event.preventDefault() // 阻止表单提交刷新
  // 执行自定义提交逻辑
  submitForm()
})

return false的行为:

在jQuery事件处理程序中,return false会同时调用event.preventDefault()event.stopPropagation()。但在原生JavaScript中,return false只阻止默认行为,不阻止冒泡。

javascript 复制代码
// jQuery中
$('#link').click(function() {
  return false // 等同于 event.preventDefault() + event.stopPropagation()
})

// 原生JavaScript中  
document.getElementById('link').addEventListener('click', function(event) {
  return false // 只阻止默认行为,不阻止冒泡
})

面试官真正想听什么

这题考察你对事件控制的理解和实际应用经验。

阻止冒泡和默认行为是事件处理中的常见需求,能准确使用说明你对事件机制有扎实理解。

加分回答

在开发下拉菜单时,阻止冒泡很关键:

javascript 复制代码
// 点击按钮显示菜单
toggleBtn.addEventListener('click', function(event) {
  event.stopPropagation() // 阻止冒泡,避免触发document点击事件
  menu.style.display = 'block'
})

// 点击页面其他地方隐藏菜单
document.addEventListener('click', function() {
  menu.style.display = 'none'
})

阻止默认行为的实用场景:

  1. 自定义表单验证
javascript 复制代码
form.addEventListener('submit', function(event) {
  if (!validateForm()) {
    event.preventDefault() // 验证不通过,阻止提交
    showError('请填写完整信息')
  }
})
  1. 右键菜单自定义
javascript 复制代码
document.addEventListener('contextmenu', function(event) {
  event.preventDefault() // 阻止浏览器默认右键菜单
  showCustomMenu(event.clientX, event.clientY) // 显示自定义菜单
})
  1. 拖拽文件上传
javascript 复制代码
dropZone.addEventListener('dragover', function(event) {
  event.preventDefault() // 必须阻止默认行为才能触发drop
})

dropZone.addEventListener('drop', function(event) {
  event.preventDefault() // 阻止浏览器打开文件
  const files = event.dataTransfer.files
  handleFiles(files)
})

理解这些方法让我能精确控制事件行为。

减分回答

❌ "stopPropagation和preventDefault一样"(概念混淆)

❌ "return false什么都能阻止"(不了解jQuery和原生的区别)

❌ 说不出具体使用场景(缺乏实践经验)

5. 浏览器的存储方式有哪些?localStorage和sessionStorage的区别?

速记公式:存储四方式,local永久,session会话,cookie小量

  • 存储方式:Cookie、localStorage、sessionStorage、IndexedDB
  • 核心区别:生命周期、容量、与服务端通信
  • 选择标准:根据数据大小、持久性需求选择

标准答案

浏览器主要存储方式:

存储方式 容量 生命周期 通信/特点 主要用途
Cookie 约4KB 可设置过期时间 每次请求自动携带到服务端 用户认证、会话管理
localStorage 约5MB 永久存储,除非手动删除 不自动发送到服务端 本地缓存、用户偏好设置
sessionStorage 约5MB 会话期间有效,关闭即清除 不自动发送到服务端 表单数据暂存、单次会话状态
IndexedDB 理论无限(几百MB) 永久存储 支持事务、索引的NoSQL数据库 大量结构化数据、离线应用

基本使用方法:

javascript 复制代码
// localStorage
localStorage.setItem('key', 'value')           // 存储
const value = localStorage.getItem('key')      // 读取
localStorage.removeItem('key')                 // 删除单个
localStorage.clear()                           // 清空所有

// sessionStorage  
sessionStorage.setItem('sessionKey', 'data')
const sessionData = sessionStorage.getItem('sessionKey')

// 存储对象需要JSON序列化
const user = {name: 'Tom', age: 25}
localStorage.setItem('user', JSON.stringify(user))
const savedUser = JSON.parse(localStorage.getItem('user'))

面试官真正想听什么

这题考察你对客户端存储的理解和合理选择能力。

不同存储方式有不同适用场景,能根据需求合理选择说明你有架构思维。

加分回答

在实际项目中,我根据数据特性选择存储方式:

用户登录状态用Cookie:因为需要每次请求自动发送到服务端验证。

用户主题偏好用localStorage

javascript 复制代码
// 保存主题设置
function saveTheme(theme) {
  localStorage.setItem('theme', theme)
}

// 应用保存的主题
function applySavedTheme() {
  const savedTheme = localStorage.getItem('theme')
  if (savedTheme) {
    document.body.className = savedTheme
  }
}

表单草稿用sessionStorage

javascript 复制代码
// 自动保存表单草稿
form.addEventListener('input', function() {
  const formData = new FormData(form)
  sessionStorage.setItem('formDraft', JSON.stringify(Object.fromEntries(formData)))
})

// 页面加载时恢复草稿
window.addEventListener('load', function() {
  const draft = sessionStorage.getItem('formDraft')
  if (draft) {
    // 填充表单数据
  }
})

大量数据用IndexedDB:比如离线笔记应用、图片缓存等。

安全注意: 敏感信息不要存在localStorage,因为容易通过XSS攻击窃取。

减分回答

❌ "localStorage和sessionStorage差不多"(不理解核心区别)

❌ "所有数据都存localStorage"(不考虑安全性和场景)

❌ 不知道存储容量限制(可能造成存储失败)

总结

这5道DOM与BOM操作题目,是前端面试的基础关。能清晰回答这些问题,说明你的基础扎实;答不上来,再花哨的框架经验也难让人信服。

每道题的核心不是死记硬背,而是理解:

  • 浏览器为什么这样设计(设计理念)
  • 你在项目中怎么应用的(实战经验)
  • 性能影响和安全性考虑(工程思维)

学习建议:

  1. 理解机制原理:搞懂事件流、存储生命周期等核心概念
  2. 动手实践验证:在控制台测试不确定的特性
  3. 关注性能安全:养成性能优化和安全防护的意识

留言区互动: 这5题里,你在实际项目中用得最多的是哪个技术?或者遇到过什么有意思的DOM操作问题?

在评论区告诉我,点赞最高的问题,我会单独写一篇深度解析!

相关推荐
脑子慢且灵1 个月前
【Web前端】JS+DOM来实现乌龟追兔子小游戏
java·开发语言·前端·js·dom
wayhome在哪1 个月前
30KB 轻量王者!SortableJS 轻松搞定拖拽需求
javascript·设计·dom
葡萄城技术团队2 个月前
告别 DOM 的旧时代:从零重塑 Web 渲染的未来
dom
水冗水孚2 个月前
从一个动画需求,来学习js中animation动画事件的具体应用
javascript·css·dom
xingba2 个月前
学习 TreeWalker api 并与普通遍历 DOM 方式进行比较
javascript·api·dom
拾光拾趣录2 个月前
如何高效判断DOM元素是否进入可视区域
前端·性能优化·dom
拾光拾趣录3 个月前
DocumentFragment:高性能DOM操作
前端·dom
今禾3 个月前
不再为多余的DOM元素烦恼:React Fragment与原生DocumentFragment深度解析
前端·react.js·dom
今禾3 个月前
别再刷新了!让我们一起走进React Router的无刷新乌托邦
前端·前端框架·dom