20 个现代 JavaScript API,彻底告别繁琐 DOM 操作!从选择到监听,覆盖 90% 开发场景,代码量直降 50%

107 阅读4分钟

你是否还在为「遍历 DOM 节点写 3 层循环」「拼接 className 怕漏空格」「监听滚动事件卡成 PPT」而头疼?曾经我们靠 jQuery 简化 DOM 操作,但现在原生 JavaScript 已经进化出了一大批强大 API—— 它们更轻量、性能更好,还能直接替代 80% 的 jQuery DOM 场景。

今天就带你盘点 20 个「能直接落地」的现代 JS API,每个都附场景 + 代码示例 + 避坑点,看完就能抄进项目,效率直接拉满!

一、DOM 选择与遍历:比 getElementById 更灵活

1. querySelector / querySelectorAll:CSS 选择器直达目标

核心作用:用 CSS 选择器语法快速获取单个 / 多个 DOM 节点,替代getElementById+getElementsByClassName的组合拳。场景:获取嵌套节点、带条件的元素(如 “第一个有 active 类的 li”)。

javascript

// 1. 获取单个节点(返回第一个匹配项,不存在则为null)
const navActive = document.querySelector('.nav > li.active'); // 子选择器
const userInput = document.querySelector('[name="username"]'); // 属性选择器

// 2. 获取多个节点(返回NodeList,支持forEach遍历)
const listItems = document.querySelectorAll('.list li');
listItems.forEach(item => {
  item.style.color = '#333';
});

// ❌ 老写法:需要多步筛选
const oldList = document.getElementsByClassName('list')[0];
const oldItems = oldList.getElementsByTagName('li');
// 还要手动转数组才能遍历:Array.from(oldItems).forEach(...)

避坑点querySelectorAll返回的是静态集合(获取后 DOM 变化不会同步),而getElementsByXX是动态集合,根据场景选择。

2. closest:向上查找最近的祖先节点

核心作用:从当前节点向上找「第一个匹配选择器的祖先」(包括自身),替代手动循环parentNode场景:点击列表项时,找到它的父容器;表单输入错误时,定位到对应的表单组。

javascript

// 场景:点击按钮,找到它所在的卡片容器
document.addEventListener('click', (e) => {
  if (e.target.matches('.card-btn')) {
    // 向上找最近的.card容器
    const card = e.target.closest('.card');
    card.classList.add('active');
  }
});

// ❌ 老写法:需要多次判断parentNode
let parent = e.target.parentNode;
while (parent) {
  if (parent.classList.contains('card')) break;
  parent = parent.parentNode;
}

3. children / firstElementChild / lastElementChild:精准获取元素节点

核心作用:只获取「元素类型节点」(排除文本、注释节点),替代childNodes(会包含空格文本节点)。场景:遍历容器的子元素,避免空文本干扰。

javascript

const container = document.querySelector('.container');

// 1. 获取所有子元素节点(不含文本/注释)
const childElements = container.children; // HTMLCollection

// 2. 获取第一个/最后一个子元素
const firstChild = container.firstElementChild;
const lastChild = container.lastElementChild;

// ❌ 老写法:childNodes会包含空格文本节点
const oldChildren = container.childNodes;
oldChildren.forEach(node => {
  if (node.nodeType === 1) { // 还要判断是否为元素节点(nodeType=1)
    // 处理逻辑
  }
});

二、DOM 操作:增删改查效率翻倍

4. createDocumentFragment:批量创建节点,避免重排

核心作用:创建 “虚拟 DOM 容器”,批量添加节点后再插入文档,只触发 1 次重排,替代多次appendChild(触发多次重排)。场景:渲染列表(如表格、商品列表)。

javascript

// 场景:渲染100条列表数据
const data = Array(100).fill().map((_, i) => `Item ${i+1}`);
const list = document.querySelector('#list');

// 1. 创建文档片段(不在DOM树中,操作不触发重排)
const fragment = document.createDocumentFragment();

// 2. 批量添加节点到片段
data.forEach(text => {
  const li = document.createElement('li');
  li.textContent = text;
  fragment.appendChild(li);
});

// 3. 一次性插入文档(只触发1次重排)
list.appendChild(fragment);

// ❌ 老写法:每次appendChild都触发重排
data.forEach(text => {
  const li = document.createElement('li');
  li.textContent = text;
  list.appendChild(li); // 触发100次重排,性能差
});

5. replaceWith / before / after:精准插入 / 替换节点

核心作用:直接在指定节点的前后插入节点,或替换节点,替代insertBefore+removeChild的组合。场景:动态替换内容、在元素前插入提示。

javascript

const oldBtn = document.querySelector('.old-btn');
const newBtn = document.createElement('button');
newBtn.textContent = '新按钮';

// 1. 替换节点:用newBtn替换oldBtn
oldBtn.replaceWith(newBtn);

// 2. 在节点前插入:在newBtn前面插入提示文本
const tip = document.createTextNode('点击提交:');
newBtn.before(tip);

// 3. 在节点后插入:在newBtn后面插入图标
const icon = document.createElement('i');
icon.className = 'icon-submit';
newBtn.after(icon);

// ❌ 老写法:需要父节点配合
const parent = oldBtn.parentNode;
parent.insertBefore(tip, newBtn);
parent.replaceChild(newBtn, oldBtn);

6. remove:直接删除节点,不用找父节点

核心作用:节点自身调用remove()即可删除,替代parentNode.removeChild(node)场景:删除动态生成的元素(如标签、弹窗)。

javascript

// 场景:点击标签的关闭按钮,删除标签
document.addEventListener('click', (e) => {
  if (e.target.matches('.tag-close')) {
    // 直接删除标签节点(不用找父节点)
    e.target.parentElement.remove();
  }
});

// ❌ 老写法:必须先找父节点
const tag = e.target.parentElement;
tag.parentNode.removeChild(tag);

三、样式与类操作:告别字符串拼接

7. classList:增删改类名,支持批量操作

核心作用:用方法(add/remove/toggle/contains)操作类名,替代className字符串拼接(容易漏空格)。场景:切换元素状态(如 active、disabled)、批量添加类。

javascript

const box = document.querySelector('.box');

// 1. 添加类(支持多个)
box.classList.add('active', 'visible');

// 2. 删除类(支持多个)
box.classList.remove('hidden', 'disabled');

// 3. 切换类(存在则删除,不存在则添加)
box.classList.toggle('active'); // 点击切换常用

// 4. 判断是否有类
if (box.classList.contains('disabled')) {
  return alert('当前不可用');
}

// ❌ 老写法:字符串拼接,容易出错
box.className = box.className + ' active'; // 可能多空格
box.className = box.className.replace('hidden', ''); // 可能删不干净

8. style.setProperty:动态设置 CSS 变量

核心作用:操作 CSS 自定义属性(变量),替代直接修改style单个属性,适合主题切换、动态样式。场景:切换暗黑模式、根据数据调整颜色。

css

/* CSS中定义变量 */
:root {
  --primary-color: #4285f4;
  --bg-color: #fff;
}
.box {
  background: var(--bg-color);
  color: var(--primary-color);
}

javascript

// 1. 设置单个CSS变量
document.documentElement.style.setProperty('--primary-color', '#ea4335');

// 2. 切换暗黑模式(批量修改变量)
function toggleDarkMode() {
  const root = document.documentElement;
  if (root.classList.contains('dark')) {
    root.style.setProperty('--bg-color', '#fff');
    root.style.setProperty('--text-color', '#333');
  } else {
    root.style.setProperty('--bg-color', '#121212');
    root.style.setProperty('--text-color', '#fff');
  }
  root.classList.toggle('dark');
}

9. getComputedStyle:获取最终渲染样式

核心作用:获取元素「最终生效的 CSS 样式」(包括 CSS 文件、内联样式、浏览器默认样式),替代style属性(只能获取内联样式)。场景:判断元素是否隐藏、获取实际宽高。

javascript

const box = document.querySelector('.box');

// 1. 获取最终样式(返回CSSStyleDeclaration对象)
const computedStyle = getComputedStyle(box);

// 2. 获取具体属性(注意驼峰命名,如backgroundColor)
const bgColor = computedStyle.backgroundColor; // 如"rgb(255, 255, 255)"
const isHidden = computedStyle.display === 'none';

// 3. 获取实际宽高(包含padding,不含border)
const boxWidth = computedStyle.width; // 如"200px"
const boxHeight = computedStyle.height;

// ❌ 老写法:style只能获取内联样式
const inlineWidth = box.style.width; // 若CSS中定义,这里会是空字符串

四、事件与交互:更优雅的事件处理

10. addEventListener 事件委托:减少事件绑定

核心作用:将事件绑定到父容器,通过event.target判断目标元素,替代给每个子元素绑定事件(尤其动态生成的元素)。场景:列表项点击、表格行操作。

javascript

// 场景:动态生成的列表,点击项触发事件
const list = document.querySelector('#todo-list');

// 委托到父容器,只绑定1次事件
list.addEventListener('click', (e) => {
  // 判断点击的是列表项(.todo-item)
  if (e.target.matches('.todo-item')) {
    e.target.classList.toggle('completed');
  }
  // 判断点击的是删除按钮(.todo-delete)
  else if (e.target.matches('.todo-delete')) {
    e.target.closest('.todo-item').remove();
  }
});

// ❌ 老写法:每个子元素绑定事件,动态新增的项无效
const items = document.querySelectorAll('.todo-item');
items.forEach(item => {
  item.addEventListener('click', () => {
    // 处理逻辑
  });
});
// 新增项需要重新绑定:newItem.addEventListener(...)

11. passive: true:解决滚动事件卡顿

核心作用:告诉浏览器 “这个事件监听器不会调用preventDefault()”,浏览器可以提前优化滚动渲染,避免卡顿。场景scrolltouchmove事件(移动端尤其明显)。

javascript

// 优化滚动事件
window.addEventListener('scroll', () => {
  console.log('滚动中...');
}, { passive: true }); // 关键:添加passive选项

// ❌ 老写法:浏览器会等待监听器执行完再滚动,可能卡顿
window.addEventListener('scroll', () => {
  // 即使没调用preventDefault,浏览器也会等待
});

避坑点:如果监听器中需要preventDefault()(如阻止默认滚动),不能用passive: true,否则preventDefault()会失效并报警告。

12. IntersectionObserver:监听元素是否进入视口

核心作用:异步监听元素与视口(或指定容器)的交叉状态,替代scroll事件 +getBoundingClientRect()(性能差)。场景:图片懒加载、滚动加载更多、视口内元素动画。

javascript

// 场景:图片懒加载(进入视口再加载真实图片)
const lazyImages = document.querySelectorAll('img.lazy');

// 1. 创建观察者实例
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    // 元素进入视口
    if (entry.isIntersecting) {
      const img = entry.target;
      // 替换src为真实图片地址(data-src存储)
      img.src = img.dataset.src;
      // 停止监听已加载的图片
      observer.unobserve(img);
    }
  });
});

// 2. 监听所有懒加载图片
lazyImages.forEach(img => {
  observer.observe(img);
});

html

<!-- HTML:用data-src存储真实地址,src用占位图 -->
<img class="lazy" data-src="real-image.jpg" src="placeholder.jpg" alt="...">

13. MutationObserver:监听 DOM 变化

核心作用:异步监听 DOM 节点的变化(如子节点增删、属性修改、文本变化),替代定时查询 DOM 状态(如setInterval)。场景:表单动态验证(输入框内容变化)、监听组件渲染完成。

javascript

// 场景:监听输入框内容变化,实时验证
const input = document.querySelector('#username');

// 1. 创建观察者实例
const observer = new MutationObserver((mutations) => {
  mutations.forEach(mutation => {
    // 监听文本内容变化
    if (mutation.type === 'characterData') {
      const value = mutation.target.textContent;
      validateUsername(value); // 验证逻辑
    }
  });
});

// 2. 监听输入框的子节点(文本节点)变化
observer.observe(input, {
  childList: true, // 监听子节点变化
  subtree: true    // 监听子树(文本节点属于子树)
});

五、数据与存储:更安全的本地存储

14. localStorage / sessionStorage:本地存储简化版

核心作用:替代cookie(存储容量小、会随请求发送),localStorage永久存储(除非手动删除),sessionStorage会话结束后删除。场景:存储用户偏好、临时表单数据。

javascript

// 1. localStorage:永久存储
// 存数据(自动转字符串,复杂数据需JSON.stringify)
localStorage.setItem('theme', 'dark');
localStorage.setItem('user', JSON.stringify({ name: '张三', id: 1 }));

// 取数据(复杂数据需JSON.parse)
const theme = localStorage.getItem('theme');
const user = JSON.parse(localStorage.getItem('user'));

// 删数据
localStorage.removeItem('theme');
// 清空所有
localStorage.clear();

// 2. sessionStorage:会话存储(关闭标签页消失)
sessionStorage.setItem('tempForm', JSON.stringify({ username: '李四' }));

避坑点:存储容量约 5MB,且只能存字符串;敏感数据(如密码)不要存在本地存储,会被轻易读取。

15. FormData:轻松处理表单数据

核心作用:创建表单数据对象,支持动态添加字段,替代手动拼接queryString(尤其文件上传)。场景:表单提交(尤其是带文件的表单)、AJAX 发送表单数据。

javascript

// 场景1:从表单元素创建FormData
const form = document.querySelector('#user-form');
const formData = new FormData(form); // 自动收集所有表单字段

// 场景2:动态添加字段(如额外的token)
formData.append('token', 'xxx123');
formData.append('avatar', fileInput.files[0]); // 添加文件

// 场景3:AJAX提交(无需手动设置Content-Type)
fetch('/api/user', {
  method: 'POST',
  body: formData // 直接传FormData对象
})
.then(res => res.json())
.then(data => console.log('提交成功', data));

六、其他高频 API:覆盖边缘场景

16. getBoundingClientRect:获取元素位置与尺寸

核心作用:获取元素相对于视口的位置(top/right/bottom/left)和尺寸(width/height),用于定位(如弹窗跟随)。场景:弹窗定位到按钮下方、判断元素是否在屏幕内。

javascript

// 场景:点击按钮,弹窗显示在按钮下方
const btn = document.querySelector('.open-modal-btn');
const modal = document.querySelector('.modal');

btn.addEventListener('click', () => {
  // 获取按钮的位置信息
  const btnRect = btn.getBoundingClientRect();
  
  // 设置弹窗位置(在按钮下方)
  modal.style.left = `${btnRect.left}px`;
  modal.style.top = `${btnRect.bottom + 10}px`; // 10px间距
  modal.style.display = 'block';
});

17. scrollIntoView:平滑滚动到元素

核心作用:让元素滚动到视口中,支持平滑滚动,替代window.scrollTo(需要计算位置)。场景:锚点跳转、表单错误时滚动到错误字段。

javascript

// 场景1:平滑滚动到顶部
document.querySelector('#top-btn').addEventListener('click', () => {
  document.body.scrollIntoView({
    behavior: 'smooth', // 平滑滚动
    block: 'start'      // 对齐视口顶部
  });
});

// 场景2:表单提交错误,滚动到第一个错误字段
const firstError = document.querySelector('.form-error');
if (firstError) {
  firstError.scrollIntoView({ behavior: 'smooth', block: 'center' });
}

18. requestAnimationFrame:流畅动画

核心作用:让回调函数在浏览器下一次重绘前执行,确保动画流畅(60fps),替代setTimeout/setInterval(时间不准)。场景:元素移动、进度条动画、倒计时动画。

javascript

// 场景:进度条从0到100%的动画
const progressBar = document.querySelector('.progress');
let progress = 0;

function updateProgress() {
  progress += 1;
  progressBar.style.width = `${progress}%`;
  
  if (progress < 100) {
    // 继续下一帧动画
    requestAnimationFrame(updateProgress);
  }
}

// 启动动画
requestAnimationFrame(updateProgress);

19. URLSearchParams:解析 URL 参数

核心作用:轻松解析和修改 URL 查询参数,替代手动分割location.search字符串。场景:获取 URL 中的参数(如?id=123&name=test)、构建查询字符串。

javascript

// 1. 解析URL参数
const urlParams = new URLSearchParams(location.search);

// 获取参数
const id = urlParams.get('id'); // "123"
const name = urlParams.get('name'); // "test"

// 判断是否有参数
if (urlParams.has('id')) {
  console.log('存在id参数');
}

// 2. 构建查询字符串
const newParams = new URLSearchParams();
newParams.append('page', '2');
newParams.append('size', '10');
const queryString = newParams.toString(); // "page=2&size=10"
const newUrl = `/list?${queryString}`; // "/list?page=2&size=10"

20. document.activeElement:获取当前焦点元素

核心作用:获取当前页面中获得焦点的元素,替代遍历判断document.hasFocus()场景:判断用户是否在输入框中、按下 ESC 时关闭弹窗并恢复焦点。

javascript

// 场景:按下ESC关闭弹窗,并将焦点返回之前的元素
const modal = document.querySelector('.modal');
let lastFocusedElement;

// 打开弹窗时,记录当前焦点元素
function openModal() {
  lastFocusedElement = document.activeElement; // 记录焦点
  modal.style.display = 'block';
  modal.querySelector('input').focus(); // 弹窗内输入框获焦
}

// 按下ESC关闭弹窗,恢复焦点
document.addEventListener('keydown', (e) => {
  if (e.key === 'Escape' && modal.style.display === 'block') {
    modal.style.display = 'none';
    lastFocusedElement.focus(); // 恢复之前的焦点
  }
});

总结:从 “能用” 到 “好用” 的 API 选择原则

  1. 优先用现代 APIquerySelector替代getElementXXclassList替代classNameIntersectionObserver替代scroll监听 —— 性能和代码量都更优;
  2. 避免重复 DOM 操作:用createDocumentFragment批量创建,用事件委托减少绑定,用passive优化滚动;
  3. 关注浏览器兼容性:以上 API 除了IntersectionObserver(IE 不支持),其余在 Chrome/Firefox/Safari 10 + 都兼容,项目若需支持 IE,可搭配polyfill
  4. 减少第三方依赖:这些原生 API 能替代大部分 jQuery DOM 功能,没必要为了简单操作引入整个 jQuery 库。

其实前端开发的效率提升,往往就藏在这些 “小而美” 的 API 里 —— 少写一行冗余代码,多避一个性能坑,长期积累下来就是质的飞跃。