Skip to content

第一章练习答案 ​

事件传播与默认行为 ​

html
<div id="outer"><button id="inner">点击</button></div>
<script>
  const outer = document.querySelector('#outer')
  const inner = document.querySelector('#inner')
  const log = []
  outer.addEventListener('click', () => log.push('outer-capture'), true)
  inner.addEventListener('click', (event) => log.push(`target:${event.target.id}/${event.currentTarget.id}`))
  outer.addEventListener('click', () => log.push('outer-bubble'))
  inner.click()
  console.log(log)
</script>

预期日志是 outer-capture、target:inner/inner、outer-bubble。target 是真正被点击的按钮,currentTarget 随当前监听器改变;把中间监听器改成 stopPropagation() 后,父级冒泡日志消失,但按钮自身默认行为仍由浏览器决定。

DOM 树、节点身份与遍历 ​

js
const list = document.querySelector('#list')
const first = list.firstElementChild
console.log(first === list.querySelector('li'), first.parentElement === list)
console.log([...list.children].map(node => node.dataset.id))

预期第一个比较为 true,父节点比较也为 true。使用 cloneNode() 后身份比较为 false,但复制出的属性和值仍可相同;练习的关键是区分节点身份和结构相等。

target、currentTarget 与 composedPath ​

html
<div id="host"><button id="child">保存</button></div>
<script>
const host = document.querySelector('#host')
host.addEventListener('click', (event) => {
  console.log(event.target.id, event.currentTarget.id, event.composedPath().map(node => node.id || node.tagName))
})
document.querySelector('#child').click()
</script>

直接子节点点击时 target 是子节点、currentTarget 是 host;事件经过 Shadow DOM 时,target 可能被重定向为宿主,而 composedPath() 才能显示跨边界的路径。将监听器改为 { composed: false } 的自定义事件后,事件不会穿过 ShadowRoot。

默认行为、取消与用户手势 ​

链接监听器中调用 preventDefault() 后,地址不再跳转;改为 stopPropagation() 后,父级监听器可能不再收到事件,但链接仍会导航。表单使用 type="button" 时根本不会触发表单提交,这是改变 HTML 默认类型,不是脚本取消提交。键盘 Enter 提交与鼠标提交都应进入同一个 submit 监听器。

用心记录每一步成长