第三节:事件侦听
这可能是绑定事件侦听器最有名的方法:
myElement.onclick = function onclick(event) {
console.log(event.type + ' got fired');
};
但通常应该避免这样做。这里, .onclick 是该元素的一个属性,意味着你可以改变它,但是你不能使用它来添加额外的侦听器 — 通过指定一个新的函数,你将覆盖旧的引用。
相反,我们可以使用更强大的 .addEventListener() 方法来添加尽可能多的类型的事件。它有三个参数:事件类型(比如 click),一个无论何时元素上的事件发生会被调用的函数,一个可选的配置对象,它将会被进一步解释。
myElement.addEventListener('click', function (event) {
console.log(event.type + ' got fired')
})
myElement.addEventListener('click', function (event) {
console.log(event.type + ' got fired again')
})
在监听函数内,event.target引用事件触发的元素(就像 this,除非我们使用箭头函数)。因此,你可以轻松地访问它的属性,如下所示:
// document 的`forms`属性是一个数组
// 引用所有的form
const myForm = document.form[0];
const myInputElements = myForm.querySelectorAll('input');
Array.form(myInputElements).forEach(el => {
el.addEventListener('change', function(event) {
console.log(event.target.value);
});
});
阻止默认行为
请注意,事件在监听函数中始终可用,但在需要时将其明确地传递给它是很好的选择(当然,我们可以按照我们的喜好命名它)。没有详细说明事件接口本身,一个特别值得注意的方法是 .preventDefault() ,它将阻止浏览器的默认行为,例如 链接。另一个常见的用例是在客户端表单验证失败时有条件地阻止提交表单。
myForm.addEventListener('submit', function(event) {
const name = this.querySelector('#name')
if (name.value === 'Donald Duck') {
alert('You gotta be kidding!')
event.preventDefault()
}
})
另一个重要的事件方法是 .stopPropagation() ,它将防止事件冒泡DOM。这意味着如果我们有一个停止传播的 click 监听器,(比如说)在一个元素上,而另一个 click 监听器在它的父元素上,那么在子元素上触发的点击事件不会在父级上触发 - 否则,它会在两者上触发。
现在,.addEventListener() 将可选配置对象作为第三个参数,它可以具有以下任何布尔属性(所有这些属性都默认为 false):
capture(捕获):事件将在DOM中的任何其他元素之前被触发(事件捕获和冒泡本身就是一篇文章,更多细节请看这里);
once(一次):正如你可能猜到的那样,这表示该事件只会触发一次;
passive(被动):这意味着
event.preventDefault()将被忽略(并且通常在控制台中产生警告);
最常见的选择是 .capture;实际上,这是很常见的,有一个简写:不需要在配置对象中指定它,你可以在这里传递一个布尔值:
myElement.addEventListener(type, listener, true)
事件监听器可以使用 .removeEventListener() 来移除,它将事件类型和对回调函数的引用移除;例如,once 选项也可以实现。
myElement.addEventListener('change', function listener(event) {
console.log(event.type + ' got triggered on ' + this)
this.removeEventListener('change', listener)
})
事件委托
另一个有用的模式是事件委托(事件代理):假设我们有一个表单并且想要为其所有输入子项添加一个更改事件监听器。一种方法是使用 myForm.querySelectorAll('input') 来迭代它们,如上所示。但是,当我们可以将它添加到表单本身并检查 event.target 的内容时,这是不必要的。
myForm.addEventListener('change', function(event) {
const target = event.target
if (target.matches('input')) {
console.log(target.value)
}
})
这种模式的另一个优点是它也自动地为动态插入的子项计算帐户,而不必为每个子项绑定新的监听者。
第四节:动画
通常,执行动画的最简洁的方法是将CSS类与 transition 属性一起应用,或者使用CSS @keyframes 。但是如果你需要更多的灵活性(例如对于一款游戏),那么也可以使用JavaScript来完成。
幼稚的做法是让一个 window.setTimeout() 函数调用自己,直到完成所需的动画。
幼稚的做法是让一个 window.setTimeout() 函数调用自己,直到完成所需的动画。但是,这无效地迫使文件快速回流;而这种布局变换可能会迅速导致卡顿,尤其是在移动设备上。 相反,我们可以使用 window.requestAnimationFrame() 同步更新来安排当前对下一个浏览器重绘帧的更改。它将回调作为接收当前(高分辨率)时间戳的参数:
const start = window.performance.now();
const duration = 2000;
window.requestAnimationFrame(function fadeIn(now)) {
const progress = now - start;
myElement.style.opacity = progress / duration;
if (progress < duration) {
window.requestAnimationFrame(fadeIn);
}
}
这样我们可以实现非常流畅的动画。有关更详细的讨论,请查看Mark Brown撰写的这篇文章。
编写你自己的帮手方法
诚然,与jQuery的简洁和可链接的 $('.foo').css({color:'red'}) 语法相比,总是需要遍历元素来处理元素可能相当麻烦。那么,为什么不简单地写我们自己的速记方法呢?
const $ = function $ (selector, context = document) {
const elements = Array.from(context.querySelectorAll(selector))
return {
elements,
html (newHtml) {
this.elements.forEach(element => {
element.innerHTML = newHtml
})
return this
},
css (newCss) {
this.elements.forEach(element => {
Object.assign(element.style, newCss)
})
return this
},
on (event, handler, options) {
this.elements.forEach(element => {
element.addEventListener(event, handler, options)
})
return this
}
// etc.
}
}
因此,我们有一个超薄的DOM库,只有我们真正需要的方法,并且没有所有的向后兼容性权重。通常我们会在我们的集合的原型中使用这些方法。这里有一些(更精细)的要点,提供一些如何实现这些帮助者的想法。或者,我们可以保持它像
const $ = (selector, context = document) => context.querySelector(selector)
const $$ = (selector, context = document) => context.querySelectorAll(selector)
const html = (nodeList, newHtml) => {
Array.from(nodeList).forEach(element => {
element.innerHTML = newHtml
})
}
// And so on...
Demo
为了关闭这篇文章,下面是一个CodePen,它演示了许多上面解释的概念来实现一个简单的灯箱技术。我鼓励你花一些时间浏览源代码,如果你有任何意见或问题,请在下面的评论中告诉我。
结论
我希望我可以证明用普通的JavaScript进行DOM操作不是火箭科学,实际上,许多jQuery方法在本地DOM API中有直接的等价物。这意味着对于一些日常使用情况(例如导航菜单或模式弹出窗口),DOM库的额外开销可能不合适。
虽然本地API的某些部分确实是冗长或不方便的(例如必须始终手动迭代节点列表),但我们可以很轻松地编写自己的小帮助函数来抽象出这些重复的任务。
但现在它已经结束了。你怎么看?你是否愿意尽可能地避免使用第三方库,或者自己动手干脆不值得认知开销?请在下面的评论中告诉我。
本文由Vildan Softic和Joan Yin同行评审。感谢SitePoint的所有同行评论员,让SitePoint的内容达到最佳状态!
- 参考资料

本文介绍JavaScript中的事件绑定、监听及动画实现方法。探讨了事件侦听器的不同绑定方式及其优缺点,介绍了事件委托技巧和使用requestAnimationFrame进行平滑动画的技术。
1424

被折叠的 条评论
为什么被折叠?



