想象你是一家餐厅的店长。网页的每个角落——标题、段落、按钮、输入框——都画在一张“布局蓝图”上,这张蓝图就叫 DOM(文档对象模型)。蓝图上的每个图形都是一个“节点”,节点层层嵌套,组成一棵“文档树”。你想让餐厅真正运营起来,就得让服务员看懂这张蓝图:客人喊“3 号桌”,他能立刻定位到对应餐桌(节点查询);翻新菜单时,他能搬走旧桌椅、摆上新家具(节点增删);给 VIP 区换上金色装饰(属性样式);听到客人按铃就上前服务(事件绑定);准确记下客人点单(表单操作)。最后,你还得优化服务员动线,避免高峰期来回跑断腿(性能优化)。这篇教程就把这些本领一个个教给你,学完你就能当好一名网页世界的“店长”。

岗位关联

对 AI 从业者而言,DOM 操作是网页自动化、数据采集与前端性能分析的地基,学透它,你的 AI 助手就能像人一样“看得见、摸得着”网页。

目录
  1. 可视化演示
  2. 1. 节点查询:服务员认路的本事
  3. 2. 节点增删:餐厅里的家具搬运
  4. 3. 属性样式:给家具贴标签和刷油漆
  5. 4. 事件交互:服务员耳听八方
  6. 5. 表单操作:点菜单的读与写
  7. 6. 性能优化:餐厅动线设计
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

本节配有交互动效(进入视口后自动加载,约 3KB)。

DOM操作全流程:查询、增删与优化 节点查询支持getElementById精确匹配和querySelector CSS选择器,后者更灵活但略慢 createElement创建节点后通过appendChild插入DOM树,每次操作触发重排重绘,频繁操作需用DocumentFragment批量处理 事件委托利用冒泡机制在父元素统一监听,减少监听器数量,提升性能和可维护性 重排次数 ∝ 直接DOM操作次数,DocumentFragment将N次重排降为1次 DOM操作的核心是减少重排:批量操作、事件委托、防抖节流

1. 节点查询:服务员认路的本事

新来的服务员面对满堂客人,第一件事就是认路。餐桌、吧台、包厢各有编号,他会先记住“桌号”(id),再记住“区域与特色”(class、标签名)。客人喊一句“3 号桌两位”,他就能在几十张桌子中迅速锁定目标,而不是满场乱转。

DOM 把网页组织成一颗“文档树”,每个标签是树上的一个节点。节点身上挂着 id、class、标签名等身份信息,相当于餐厅里每样物品的编号和品类。想找到某个节点,本质就是让浏览器替你做一次“寻人”。

getElementById 是最高效的精确查找——输入一个 id,浏览器全局扫一遍就返回唯一元素,就像按桌号精准定位。querySelector 接受 CSS 选择器,比如 '#menu' 或 '.dish-card',它沿着树向下找,返回第一个匹配就停。querySelectorAll 则更彻底,把所有匹配的节点打包成一个 NodeList,也就是一份“符合条件清单”。

有了这套查询本领,服务员再也不用满场瞎转。选中一个节点就像在餐厅里“锁定一个目标”,后续的增删改全都建立在“先找到它”这个前提上。

findAll(s)={n∈DOM∣matches(n,s)}\text{findAll}(s) = \{ n \in \text{DOM} \mid \text{matches}(n, s) \}
这条公式说的是:查询就是走遍整棵文档树,把满足选择器条件的节点全部装进口袋。
  1. findAll(s)(查询函数)把选择器 s 当作一套筛选条件,在整棵树范围内执行查找。
    findAll(s)\text{findAll}(s)
  2. n ∈ DOM(遍历范围)n 代表树上的每一个节点,查询会逐个经过它们。
    n∈DOMn \in \text{DOM}
  3. matches(n, s)(匹配判断)检查节点 n 的 id、类名、标签名是否满足 s 的描述。
    matches(n,s)\text{matches}(n, s)
  4. 集合结果所有满足条件的节点被收集起来,形成 NodeList 清单。
    {⋯ }⇒NodeList\{ \cdots \} \Rightarrow \text{NodeList}

2. 节点增删:餐厅里的家具搬运

餐厅要翻新,店长说:“把角落那张旧桌子搬走,再在旁边加一张四人桌。”服务员手里有两件趁手工具:一件是“制造工具”,能凭空捏出全新的家具;一件是“搬运工具”,能把家具放到指定位置、或从现场撤走。节点增删就是这套“家具工程”。

createElement('div') 相当于吩咐服务员“按 div 的图纸捏一个新节点”。新节点刚造出来时是“无主孤魂”,还没进文档树,就像还没摆进餐厅的家具。appendChild(parent) 把它挂到父节点之下,它才正式成为餐厅的一员。removeChild 把子节点从父节点身上摘下,餐厅少了一件家具。replaceChild 更高级——旧家具不用先搬走再搬新,直接原地替换,一气呵成。

增删节点不是凭空变魔术,而是“先制造、再挂载;先定位、再移除”。餐厅的布局就在这一次次搬运中逐渐变成你想要的样子。

nodenew=createElement(tag);parent.appendChild(nodenew)⇒nodenew∈DOM\text{node}_{\text{new}} = \text{createElement}(\text{tag});\quad\text{parent.appendChild}(\text{node}_{\text{new}}) \Rightarrow \text{node}_{\text{new}} \in \text{DOM}
公式展示了“捏造一个节点,再挂进树里”的两步走:先创造,再入树,缺一不可。
  1. createElement(tag)(捏造新节点)用标签名创建一个游离在文档树之外的新元素。
    nodenew=createElement(tag)\text{node}_{\text{new}} = \text{createElement}(\text{tag})
  2. appendChild(parent)(挂载入树)把新节点作为子节点挂到父节点身上。
    parent.appendChild(nodenew)\text{parent.appendChild}(\text{node}_{\text{new}})
  3. node ∈ DOM(正式入树)挂载完成后,新节点才真正成为文档树的一部分。
    nodenew∈DOM\text{node}_{\text{new}} \in \text{DOM}

3. 属性样式:给家具贴标签和刷油漆

餐厅的每张桌子上都有桌号牌,VIP 区的椅子套着金色椅套,普通区的椅子是原木色。这些“标签”和“油漆”就是 DOM 节点上的属性和样式。服务员要会读标签、改写标签,还要会重新刷漆。

getAttribute('data-seat') 就像服务员蹲下看桌底贴的编号;setAttribute('data-seat', 'A12') 则是撕下旧标签贴上新的。classList 是管理“多把钥匙”的工具——一件家具可以同时属于多个分组(active、vip、disabled),classList.add 给它挂上钥匙,classList.remove 摘掉钥匙,classList.toggle 更是“有就摘、没有就挂”的一键开关。style 则直接修改内联样式,相当于现场刷油漆,改完立刻见效。

属性和样式的本质,就是给节点“挂信息、换外观”。无论是切换主题色、标记选中状态,还是给 AI 爬虫标注数据,都是在靠这些 API 往节点身上写“标签”。

toggle(c)={add(c),c∉classesremove(c),c∈classes\text{toggle}(c) =\begin{cases}\text{add}(c), & c \notin \text{classes} \\\text{remove}(c), & c \in \text{classes}\end{cases}
这段公式描述的是 classList.toggle 的开关逻辑:类名不存在就加上,存在就移除,永远只留一个确定状态。
  1. toggle(c)(切换类名)把传入的类名 c 当作一个“当前状态”的开关。
    toggle(c)\text{toggle}(c)
  2. c ∉ classes(没有该类)检查当前节点身上是否没有这个类。
    c∉classes⇒add(c)c \notin \text{classes} \Rightarrow \text{add}(c)
  3. c ∈ classes(已有该类)如果类名已经存在,则执行移除。
    c∈classes⇒remove(c)c \in \text{classes} \Rightarrow \text{remove}(c)

4. 事件交互:服务员耳听八方

客人不会一直在座位上安静坐着——他们按铃叫服务、举手加菜、起身离席。服务员必须“竖起耳朵”,听到铃声就行动,听到点单就记录。网页交互也一样,用户在页面上点击、输入、滚动,都需要程序“听到”并回应。

addEventListener('click', handler) 就是在节点上放了一只耳朵,一旦目标节点发生 click 事件,handler 就会被唤醒。removeEventListener 则是把耳朵摘掉,从此不再听这个事件。事件有一个有趣的特性叫“冒泡”:当客人点击最深处的按钮,事件不会只停在按钮上,而会一层层往上“浮”,经过父节点、祖父节点,一直传到文档根部。

正因为事件会冒泡,才有了“事件委托”这门省力功夫:与其给一百个按钮各绑一只耳朵,不如在它们的共同父节点上绑一只耳朵,然后用 event.target 判断具体是谁触发的。就像服务员站在大厅中央,谁举手他都能一眼看到,不必站在每张桌子旁边。

Path(event)=target→parentNode→⋯→document\text{Path}(\text{event}) = \text{target} \to \text{parentNode} \to \cdots \to \text{document}
公式表示事件从目标节点出发,沿父链逐级向上传播的完整路径。
  1. Path(event)(事件路径)一次事件从发生到结束所经过的所有节点。
    Path(event)\text{Path}(\text{event})
  2. target(事件源头)用户真正点击、触碰的那个最深层节点。
    target\text{target}
  3. parentNode → document(向上逐级)事件沿父节点链一直冒泡到文档根。
    target→parentNode→⋯→document\text{target} \to \text{parentNode} \to \cdots \to \text{document}

5. 表单操作:点菜单的读与写

客人点菜时,服务员要递上菜单,客人勾选、填写,服务员收回后要能读懂每一项内容,还要把“加辣”“少盐”之类的备注传给后厨。网页里的表单就是这样一张“电子点菜单”,而表单操作就是读取和填写这张单子。

input.value 是最常用的读取方式——拿到输入框里当前文本,就像看客人写的备注。checkbox.checked 判断复选框是否勾选,等于确认“这个菜要还是不要”。select 下拉框则要读 selectedIndex 或 select.value,了解客人在多个选项中挑了哪一样。表单提交时,submit 事件会触发,程序可以在这一刻拦截、校验、再决定是否真正“送往后厨”。

表单操作是网页收集信息的主通道。无论是用户登录、搜索关键词还是 AI 自动填写表单,最终都要落在这套“读单、改单、交单”的流程上。

FormData(form)={(namei,valuei)∣i∈inputs}\text{FormData}(\text{form}) = \{ (\text{name}_i, \text{value}_i) \mid i \in \text{inputs} \}
这份“数据快照”把表单里所有输入项拍成一组组“名字-值”对,方便统一读取和发送。
  1. FormData(form)(表单快照)把整个表单转换成结构化数据集合。
    FormData(form)\text{FormData}(\text{form})
  2. (name_i, value_i)(键值对)每个表单项对应一条“名字-值”记录。
    (namei,valuei)(\text{name}_i, \text{value}_i)
  3. i ∈ inputs(遍历输入项)遍历表单内所有输入控件,逐个提取。
    i∈inputsi \in \text{inputs}

6. 性能优化:餐厅动线设计

高峰时段,服务员如果为了上一道菜来回穿越整个大厅,很快就把腿跑断,客人也等得不耐烦。聪明的店长会设计动线:把顺路的菜品一起送、把高频物品放在吧台最近处。网页性能优化也是同样的道理,核心目标只有一个——少跑路、批量送、控频率。

DocumentFragment 就像餐厅的“传菜托盘”:先把五、六道菜都摆到托盘上,一次送到桌边,而不是端一盘跑一趟。对应到 DOM,就是把一批新建节点先放进“离线片段”,最后一次性插入页面,只需触发一次重排。防抖(debounce)则像“等客人不再按铃才过去”——连续滚动时不断重置计时器,等停下 200 毫秒再执行。节流(throttle)则像“每隔 30 秒巡场一次”——无论客人按多少次铃,服务台都按固定频率响应。

性能优化没有玄学,就是让每一趟“跑腿”都物有所值。文档片段管批量,防抖管“最后一击”,节流管“均匀节奏”。动线设计好了,餐厅再忙也不会乱套。

debounce: t−tlast<Δt⇒重置计时;throttle: t−tlast≥Δt⇒执行\text{debounce:}\ t - t_{\text{last}} < \Delta t \Rightarrow \text{重置计时}; \qquad\text{throttle:}\ t - t_{\text{last}} \ge \Delta t \Rightarrow \text{执行}
防抖和节流是一对兄弟:防抖追求“最后一下”,节流保证“固定节奏”。
  1. t - t_last(时间差)当前时刻与上次触发之间的间隔。
    t−tlastt - t_{\text{last}}
  2. Δt(间隔阈值)程序设定的时间窗口,相当于“多久算一次”的标尺。
    Δt\Delta t
  3. 防抖分支间隔小于阈值就重置计时,直到平静后再执行。
    debounce: t−tlast<Δt⇒重置计时\text{debounce:}\ t - t_{\text{last}} < \Delta t \Rightarrow \text{重置计时}
  4. 节流分支间隔达到阈值就立即执行,然后更新上次执行时间。
    throttle: t−tlast≥Δt⇒执行\text{throttle:}\ t - t_{\text{last}} \ge \Delta t \Rightarrow \text{执行}

AI 应用场景

场景一:AI 网页自动化测试助手

阶段:感知与交互

解决的问题:传统测试要人工逐个点击页面按钮、填写表单,效率低且易漏。AI 测试助手需要像人一样“看”页面和“操作”页面,而它的眼睛和手,正是 DOM 查询与事件绑定——先 querySelector 找到登录按钮,再触发 click,再读取表单的 value 做断言。

具体案例:某电商平台的 AI 回归测试 Agent 每晚自动遍历商品列表页,通过 querySelectorAll 收集所有商品卡片,逐个校验图片是否有 alt 属性、价格节点是否存在。发现异常时自动调用 setAttribute 给节点打上“问题”标记,并把 DOM 快照传给后端模型做进一步分析。

场景二:无障碍扫描机器人

阶段:数据采集与模型判断

解决的问题:人工依据 WCAG 标准检查网站可访问性非常耗时,关键问题如同图片缺替代文本、按钮缺可访问名称,往往藏在大量节点中。AI 扫描器要预先遍历 DOM 树,收集每个节点的标签名、属性列表、文本内容,再送入模型判断是否符合无障碍标准。

具体案例:一款浏览器插件每天扫描企业门户网站,用 getElementById 定位主体内容区,遍历所有 img 节点读取 alt 属性,并用 classList 分析隐藏元素的可见性,最终生成一份带 DOM 路径的修复清单。

场景三:前端异常预警系统

阶段:异常检测与归因

解决的问题:页面卡顿往往由频繁 DOM 操作、事件监听器泄漏引起,人工排查成本高。AI 监控平台需要实时读取页面上的 DOM 节点总数、监听器数量、MutationObserver 触发频率,这些数据都来自 DOM API 的探测。

具体案例:监控 SDK 用 MutationObserver 监听节点增删并计数,配合防抖后每 10 秒上报一次指标。AI 模型根据节点数量曲线和事件触发频率,自动识别“DOM 膨胀”“事件泄漏”等异常模式,并定位到具体 JS 文件。

Python 演示

python88 行
import time  class DOMNode:    """简单 DOM 节点模拟器"""     def __init__(self, tag, parent=None):        self.tag = tag        self.parent = parent        self.children = []        self.attributes = {}     def append_child(self, node):        """把新节点挂到自己的 children 列表"""        node.parent = self        self.children.append(node)        return node     def remove_child(self, node):        """从 children 里移除节点并断开 parent 指向"""        if node in self.children:            self.children.remove(node)            node.parent = None        return node     def query_selector_all(self, tag):        """递归遍历整棵树, 收集所有 tag 匹配的节点"""        results = []         def walk(node):            for child in node.children:                if child.tag == tag:                    results.append(child)                walk(child)         walk(self)        return results  def debounce_decorator(interval):    """简单的防抖: interval 秒内只让最后一次调用真正执行"""    state = {"last": 0.0}     def decorator(fn):        def wrapped(*args, **kwargs):            now = time.time()            if now - state["last"] > interval:                fn(*args, **kwargs)            state["last"] = now         return wrapped     return decorator  def main():    # 搭建一棵 html > body > div/p 的 DOM 树    root = DOMNode("html")    body = root.append_child(DOMNode("body"))    div = body.append_child(DOMNode("div"))    div.append_child(DOMNode("p"))    div.append_child(DOMNode("p"))    body.append_child(DOMNode("p"))     # 查询所有 p 标签节点    paras = root.query_selector_all("p")    print("找到 {} 个 <p> 节点".format(len(paras)))     # 删除 div 下的第一个 p    div.remove_child(paras[0])    print("删除后 <p> 数量:", len(root.query_selector_all("p")))     # 防抖模拟: 连续调用只执行一次    calls = {"n": 0}     @debounce_decorator(interval=0.05)    def report():        calls["n"] += 1     for _ in range(100):        report()        time.sleep(0.001)     print("100 次连续调用, 防抖后实际执行:", calls["n"], "次")  if __name__ == "__main__":    main()
目的:用 Python 模拟一棵小型 DOM 树,演示节点查询、节点删除,以及防抖在连续高频调用中的“最后一击”效果。
  1. class DOMNode:用类模拟浏览器里的 DOM 节点,每个节点持有 tag、parent、children 和属性表。
  2. node.parent = self让新节点“认父”,挂到当前节点名下,相当于 appendChild 的底层操作。
  3. results.append(child)把匹配标签名的节点加入结果清单,正是 querySelectorAll 的遍历逻辑。
  4. if child.tag == tag:对比节点的标签名与目标标签是否一致,是查询的匹配判据。
  5. if node in self.children:删除前检查子节点是否真的存在,避免误删报错。
  6. def walk(node):深度优先遍历的递归函数,从根节点层层下探,是 DOM 查询的心脏。
  7. if now - state["last"] > interval:防抖判断:距离上次触发已超过间隔才真正执行。
  8. state["last"] = now每次调用都刷新“上一次时间”,让高频触发不断重置倒计时。
  9. root.query_selector_all("p")从整个文档根节点出发,全树查找所有 p 标签。
  10. div.remove_child(paras[0])按索引找到第一个匹配节点并移除,对应 DOM 的 removeChild 流程。

常见误区

误区“页面上有多个 id 相同的元素,`getElementById` 应该都返回,所以我可以用下标去取第一个。”

正解getElementById 是“按桌号找唯一座位”,id 在规范上就必须唯一,方法返回的是元素本身,不是数组。如果 HTML 里出现重复 id,浏览器只会返回第一个匹配,这是非规范状态下的兜底行为。要拿多个元素,应该用 querySelectorAll 这类“清单式”方法。

为何常见:很多初学者刚接触 document.querySelectorAll 时见过 [0] 的用法,误以为所有查询方法都返回集合,忽略了“唯一查找”和“列表查找”是两类不同 API。

误区“反正它能用下标访问,也能 `forEach`,那它就是一个 JavaScript 数组。”

正解它返回的是 NodeList,一种类数组对象。虽然现代浏览器里的 NodeList 也有 forEach,但它没有 map、filter、reduce 等数组方法。想转换成真正的数组,可以用 Array.from(nodeList) 或 [...nodeList],这样才能调用全套数组方法。

为何常见:NodeList 看起来能遍历、有 length、有下标,和数组太像了。只有在调用 map 突然报错时,人们才会意识到“它不是数组”。

误区“为了加十个节点,我循环十次 `appendChild`,页面也照样正常,性能没啥区别。”

正解每一次 DOM 修改都可能触发浏览器重排和重绘,就像服务员为了十道菜来回跑十趟。十个节点不痛不痒,但若在滚动或大列表场景里,这种“频繁跑腿”会直接造成卡顿。正确做法是先用 DocumentFragment 或字符串积累,再一次性插入,让“十趟跑腿”变成“一趟送齐”。

为何常见:小页面、小数据量下体感差异为零,导致初学者误以为性能优化只是空谈。等到页面复杂度上来,性能债会集中爆发。

误区“事件委托?那不就是给目标元素一个个加上 `addEventListener` 吗?”

正解恰恰相反,事件委托的核心是把监听器绑在公共祖先上,利用冒泡机制“以逸待劳”。就像服务员站在大厅中央,而不是守在每张桌子旁。真实场景中,动态新增的子元素无需重新绑定,因为事件会冒泡到祖先节点的监听器上,再通过 event.target 判断来源即可。

为何常见:初学者最先学会的是“选中元素→绑定事件”这个线性流程,很难主动想到“让祖先代听”这种间接方案。只有当子元素动态变化导致绑定失效时,才体会到委托的价值。

误区“`innerHTML` 能一次性插入大段 HTML,又简单又高效,怎么用都行。”

正解innerHTML 好用,但要慎用。它会把字符串交给浏览器重新解析,如果字符串里混入用户输入,就可能造成 XSS 攻击;同时重设 innerHTML 会销毁原有子节点的事件监听器。用它做“批量静态结构”可以,但涉及用户数据时,优先使用 textContent 或 createElement + appendChild 的安全流程。

为何常见:因为它写起来太方便了,初学者容易忽略背后的字符串解析、安全风险和监听器销毁问题,直到出现安全漏洞或“事件突然失效”才追悔莫及。

面试题

Q1. 要在页面中按 id 快速找到唯一元素,应使用哪个方法?

单选题 · easy

  • A.getElementById 精确查找唯一← 正确答案
  • B.querySelector 返回首个匹配
  • C.createElement 新建一个元素
  • D.removeChild 删除某个子节点

答案:A。getElementById 就像按“桌号”找餐桌,全局唯一、查找极快。B 是按选择器找第一个匹配;C 是创建节点;D 是删除节点,三者都不是“按 id 找唯一元素”的正确答案。

Q2. querySelectorAll('li') 返回的结果是什么类型?

单选题 · easy

  • A.一个普通 JavaScript 数组
  • B.一个 NodeList 节点清单← 正确答案
  • C.一个字符串类型的选择器
  • D.一个元素节点的引用

答案:B。querySelectorAll 返回的是 NodeList 类数组对象,拥有 length 和 forEach,但不是真正的 Array,不能直接用 map、filter。A 错在把类数组当普通数组,C、D 则完全混淆了返回类型。

Q3. 想用标签名在内存中新建一个元素,应该调用哪个 API?

单选题 · mid

  • A.document.createElement('div')← 正确答案
  • B.document.querySelector('div')
  • C.document.createTextNode('div')
  • D.document.getElementById('div')

答案:A。createElement('div') 在内存中捏出一个新的 div 节点,尚未入树。B 是查找已有元素;C 是创建文本节点,参数会被当作文本而非标签;D 是按 id 查找,与“新建”无关。

Q4. 要移除一个子节点,下列哪个前提条件是必需的?

单选题 · mid

  • A.同时拿到父节点与子节点的引用← 正确答案
  • B.拿到子节点下的全部后代节点
  • C.拿到页面根节点 html 的引用
  • D.拿到子节点的 CSS 类名列表

答案:A。removeChild 是父节点的方法,必须通过父节点调用,并传入待删除的子节点引用,所以两样缺一不可。B、C、D 都只是干扰项,删除节点不需要触达后代、根节点或类名。

Q5. classList 上专门实现“有就删、没就加”的方法是什么?

单选题 · mid

  • A.add 与 remove 结合操作
  • B.toggle 一键切换类名← 正确答案
  • C.contains 判断是否包含
  • D.replace 替换原有类名

答案:B。classList.toggle(c) 是一个原子开关:类名不存在就 add,存在就 remove。A 虽然也能实现但需要手动判断;C 只判断不改变;D 是替换操作,都不符合“一键切换”的描述。

Q6. 下列哪些说法符合“事件委托”的正确做法?

多选题 · mid

  • A.把监听器绑定在公共祖先节点上← 正确答案
  • B.利用事件冒泡机制统一处理← 正确答案
  • C.每个子元素都要单独绑定监听
  • D.使用 event.target 判断触发来源← 正确答案

答案:ABD。事件委托像服务员站在大厅中央“代听”,核心是利用冒泡(B)把监听器绑在祖先上(A),再通过 event.target 判断具体来自哪个子元素(D)。C 恰恰是委托要避免的低效做法,需要逐个绑定。

Q7. 防抖与节流的本质区别是什么?

单选题 · hard

  • A.防抖只执行第一次,节流执行最后一次
  • B.防抖等停止后执行,节流按频率执行← 正确答案
  • C.防抖与节流完全等价可以互换
  • D.防抖用于延时操作,节流用于同步操作

答案:B。防抖是“等客人不再按铃才过去”,只要间隔小于阈值就重置计时,适合搜索框输入;节流是“每 30 秒巡场一次”,保证固定频率执行,适合滚动监听。A 说反了,C 说两者等价,D 混淆了应用场景。

Q8. 要阻止表单提交触发的默认刷新行为,应调用哪个方法?

单选题 · easy

  • A.event.preventDefault() 阻止默认← 正确答案
  • B.event.stopPropagation() 停止冒泡
  • C.event.stopImmediatePropagation() 停止传递
  • D.event.returnValue 设置为 true

答案:A。preventDefault 用于取消浏览器默认动作,比如表单提交跳转、链接跳转。B、C 控制的是事件传播路径,不阻止默认行为;D 是 IE 旧写法,且设置为 true 表示允许默认,方向正好相反。

Q9. 下列哪些措施能有效减少频繁 DOM 操作带来的性能问题?

多选题 · hard

  • A.使用 DocumentFragment 批量插入节点← 正确答案
  • B.对滚动事件使用防抖或节流← 正确答案
  • C.所有内容一律改用 innerHTML 写入
  • D.合并多次样式修改后批量处理← 正确答案

答案:ABD。DocumentFragment 把多次插入合并为一次(A),防抖节流降低高频事件触发次数(B),合并样式修改减少重排次数(D)。C 是误区:大量 innerHTML 会触发字符串重解析、破坏事件监听,反而加剧性能问题并引入安全风险。

Q10. 事件冒泡路径的正确传播顺序是下列哪一项?

单选题 · extreme

  • A.target 到 document 再到 window← 正确答案
  • B.document 到 target 再到 window
  • C.window 到 document 再到 target
  • D.target 直接跳到 window 结束

答案:A。完整冒泡路径从最深层目标节点开始,逐级经过祖先节点到 document,再到 window。B、C 描述的是捕获阶段的顺序,方向反了;D 跳过了中间祖先节点,不符合逐级冒泡的机制。