面试官:考你几个简单的事件问题吧。 几小时后的你:虽然面试官考的很简单,但是就是没有答上来。
事件处理是JavaScript中非常重要的概念,我们使用的客户端软件往往都是事件驱动的,所以面试官特别喜欢问一些事件相关的知识,这里记录几个常见的问题,供大家学习。
事件流的三个阶段:事件捕获阶段、目标阶段和事件冒泡阶段(这个问题大多数同学都知道,很容易被问懵)。
事件处理函数(事件侦听器):响应的某个事件函数。
onclick
属性,值使用JS的字符串来表示要执行的事件。
<input type="button" id="btn" value="按钮" onclick="handleClick()"> <script type="text/javascript"> function handleClick(){ console.log("按钮被点击了"); } </script> onclick
方法,如下
var btn = document.getElementById("btn"); btn.onclick = function (){ console.log("按钮被点击了"); }; addEventListener
(IE使用attachEvent
)来添加方法,如下
var btn = document.getElementById("btn"); btn.addEventListener("click",function (){ console.log("按钮被点击了"); }); onclick
是DOM0级事件处理方式,而addEventListener
是DOM2级事件处理方式,所以兼容性onclick
会更好一些(虽然现在addEventListener
也不会有什么问题)。addEventListener
可以添加多个方法,而onclick
只能添加一个。addEventListener
可以添加第三个参数,表示是捕获还是冒泡阶段调用,如果为true的时候表示捕获阶段调用,如果是false的话表示冒泡阶段调用,默认是冒泡阶段调用(undefined相当于是false)。
btn.addEventListener("click",function (){ console.log("按钮被点击了"); },true); document.body.addEventListener("click",function (){ console.log("body被点击了"); },true); // 点击按钮的时候会先打印"body被点击了" 后 打印 "按钮被点击了" // 因为第三个参数是true的时候表示捕获阶段调用 // 如果第三个参数都是false的时候 那么先打印 "按钮被点击了" 后打印 "body被点击了" // 如果一个是false一个是true那么先打印为true的 因为捕获阶段先于冒泡阶段 addEventListener
可以使用removeEventListener
来删除事件处理程序,而onclick
最多只有一个事件处理程序,所以只要btn.onclick = null;
就可以了。
addEventListener
适用于正常的W3C浏览器,而attachEvent
适用于IE浏览器(注:Edge都不支持这玩意)。attachEvent
第一个参数,需要带”on”。比如添加click事件那么attachEvent的第一个参数是”onclick”。addEventListener
中的this指向DOM元素,而attachEvent
中的this指向window。attachEvent
只支持冒泡不支持捕获,所以也就没有第三个参数。attachEvent
如果添加多个事件处理程序那么先执行后添加的后执行先添加的,这与addEventListener恰好相反(IE9和IE10修改了执行顺序和addEventListener一样了,先添加的先执行)。通常情况下事件处理程序的第一个参数就是event对象,如下:
btn.addEventListener("click",function (event){
// event 就是事件对象 通常的习惯 也可以命名为e
});
但是有一种情况下例外,就是IE的DOM0级事件onclick
的情况下,使用的是window.event
来获取(没错attachEvent
也是通过事件处理函数的第一个参数来获取):
btn.onclick = function (event){
event = event || window.event;// 兼容性处理
console.log(event);
}
普通浏览器使用event.preventDefault()
来阻止默认行为,IE使用event.retureValue = false;
(注意:并不是函数return一个false)来阻止:
btn.onclick = function (event){
event = event || window.event;// 兼容性处理
if(event.preventDefault){// 阻止默认行为
event.preventDefault();
} else {
event.retureValue = false;// IE
}
}
普通浏览器使用event.stopPropagation();
来阻止事件冒泡,IE使用event.cancelBubble = true;
:
btn.onclick = function (event){
event = event || window.event;// 兼容性处理
if(event.stopPropagation){// 阻止事件冒泡
event.stopPropagation();
} else {
event.cancelBubble = true;// IE
}
}
body(或者window对象),img,script(IE9+),link(IE和Opera支持)。
mousedown > mouseup > click。
mousedown > mouseup > click > mousedown > mouseup > click > dbclick。
keydown > keypress > keyup(注意这里与click的区别)。
keydown > keypress > keydown > keypress …
touchstart > touchend > mouseover > mousemove(触发一次) > mousedown > mouseup > click。
监听window
对象上的beforeunload
事件就可以了,可以设置event.returnValue
的值等于一个提示语,也有浏览器是根据返回的字符串来提示的:
function addEvent(element,type,handler){//通用事件添加函数
if (element.addEventListener) {
element.addEventListener(type,handler);
} else if (element.attachEvent) {
element.attachEvent("on" + type,handler);
} else {
element["on" + type] = handler;
}
}
addEvent(window,"beforeunload",function (event){
event = event || window.event;
var msg = "官人,你先别走啊,你填的东西还没有保存呢!请不要弃我而去啊...";
event.returnValue = msg;// IE的处理
return msg;//普通浏览器的处理
});
这里需要注意一点虽然我们给了特定的字符串并不是所有浏览器都会显示这个字符串的,Chrome就会给定特定的提示语而不是使用我们给定的字符串,但是只有给定字符串不为空(隐式转化为true)它才会给出提示。
// 这里btn还是上面的button元素。
btn.addEventListener("click",function (){
console.log("冒泡事件1")
},false);
btn.addEventListener("click",function (){
console.log("冒泡事件2")
},false);
document.body.addEventListener("click",function (){
console.log("document.body冒泡事件")
},false);
btn.addEventListener("click",function (){
console.log("捕获事件1")
},true);
btn.addEventListener("click",function (){
console.log("捕获事件2")
},true);
document.body.addEventListener("click",function (){
console.log("document.body捕获事件")
},true);
执行的结果是:
document.body捕获事件 冒泡事件1 冒泡事件2 捕获事件1 捕获事件2 document.body冒泡事件
事件是先捕获后冒泡的,所以第一个和最后一个是没有问题的。中间4个事件的执行,都是处于目标阶段,目标阶段会按照事件的添加顺序来执行,而不会管你是否是捕获还是冒泡。
浏览器都是先捕获后冒泡的(如果支持捕获的时候),并不支持先冒泡后捕获,我们可以改造一下捕获的函数,让他在冒泡结束后再执行,就可以达到类似的效果。如上面最后一个document.body
的事件可以像下面这样改造一下,那么document.body捕获事件
将会在最后打印。
document.body.addEventListener("click",function (){
setTimeout(() => {
console.log("document.body捕获事件")
}, 0);
},true);