JavaScript与HTML之间的交互是通过事件实现的。时间就是文档或浏览器窗口中发生的一些特定的交互瞬间。可以使用侦听器来预订事件,以便事件发生时执行相应的代码。即观察员模式。支持页面的行为(js代码)与页面外观(HTML和CSS代码)之间的松散耦合。
问题:页面的哪一部分会拥有某个特定的事件?如果单击了某个按钮,单击事件不仅发生在按钮上,同时还发生在按钮的容器元素,甚至整个页面。
事件流描述的是从页面中接收事件的顺序。IE的事件流是事件冒泡流,而Netscape Communicator的事件流是事件捕获流。(事件触发先后顺序不同)
IE的事件流叫做事件冒泡(event bubbling),即事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。
如下图:当我们点击div的时候,click事件沿DOM树向上传播。在每级节点上都会发生,直至传播到document对象。
IE9,Firefox,Chrome,Safari将事件一直冒泡到window对象。
2.事件捕获
事件捕获的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。用意在于在事件到达预定目标之前捕获它。
document对象首先接收到click事件,然后事件沿DOM树一次向下,一直传播到事件的实际目标,即<DIV>元素
优先使用事件冒泡,在有特殊需要时再使用事件捕获。
3.DOM事件流
"DOM2级事件"规定的事件流包括三个阶段:事件捕获阶段,处于目标阶段和事件冒泡阶段。
事件捕获阶段:为截获事件提供了机会。
实际目标接收到事件
冒泡阶段:可以在这个阶段对事件做出响应。
事件就是用户或浏览器自身执行的某种动作。如:click,load和mouseover,都是事件的名字。而响应某个事件的函数就叫做 事件处理程序(事件侦听器)。事件处理程序的名字以"on"开头,因此click事件的事件处理程序就是onclick,load就是onload。1.HTML事件处理程序
<html> <head> <title>事件</title> </head> <body> <input type="button" onclick="buttonClick()" value="点我"> </body> <script> function buttonClick(){ alert("hello world!"); }
事件处理程序中的代码在执行过程中,有权访问全局作用域中的任何代码。
<input type="button" onclick="alert(event.type)" value="点我">通过event变量可以直接访问事件对象
<input type="button" onclick="alert(this.value)" value="点我">
在函数内部,this值等于事件的目标元素
可直接访问value属性,因为函数内部使用with扩展了作用域
<input type="button" onclick="alert(value)" value="点我">扩展如下:
function(){ with(document){ with(this){ //直接访问元素属性 console.log(value); } } }如果是表单元素,扩展如下:可以直接访问表单元素
function(){ with(document){ with(this.form){ with(this){ //直接访问元素属性 console.log(value); } } } }事件处理程序直接访问表单字段
<form method="post"> <input type="text" name="username" value="zhangsan"> <input type="button" onclick="alert(username.value)" value="点我"> </form>缺点一:如果用户在解析前面的buttonClick()函数之前就点击了按钮,就会报错(此时函数并不存在)。
缺点二:这样扩展作用域链在不同的浏览器会导致不同的结果。
通过HTML指定事件处理程序的最后一个缺点就是HTML与JavaScript紧密耦合
2.DOM0级事件处理程序
每个元素(包括window和document)都有自己的事件处理程序属性,这些属性通常全部小写,如:onclick
通过给这种属性值设置函数来指定事件处理程序,该函数被认为是元素的方法。因此,这时候的事件处理程序是在元素的作用域中运行。也就是说程序中的this引用当前元素。
var btn = document.getElementById("heheda"); btn.onclick = function(){//作为元素的方法 alert(this.id);//this指向的是点击的button元素 }这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。
删除事件: btn.onclick = null
3.DOM2级事件处理程序
addEventListener():给元素添加事件removeEventListener():给元素删除事件都接收3个参数: 1.要处理的事件名 2.事件处理函数 3,布尔值,true:捕获阶段调用;false:冒泡阶段调用var div = document.getElementById("mydiv"); var btn = document.getElementById("mybtn"); div.addEventListener("click",function(){ alert("div"); },false); btn.addEventListener("click",function(){ alert("btn"); },false); btn.addEventListener("click",function(){ alert("btn2"); },false); btn.removeEventListener("click",function(){ //没有用,需要与添加时传入的函数参数一致才行。(添加时使用的匿名函数,无法删除) },false);添加和删除中使用相同的函数参数
var div = document.getElementById("mydiv"); var btn = document.getElementById("mybtn"); btn.addEventListener("click", handler,false); btn.removeEventListener("click",handler,false); function handler(){ alert(this.id); }注意:大多数情况下,都是将事件处理程序添加到事件流的冒泡阶段,这样可以最大限度地兼容各种浏览器。最好只在需要在事件到达目标之前截获它的时候将事件处理程序添加到捕获阶段。如果不是特别需要,我们不建议在事件捕获阶段注册事件处理程序。
EventUtil:var btn = document.getElementById("mybtn"); var EventUtil = { addHandler:function(element,type,handler){ //能力检测 if(element.addEventListener){ element.addEventListener(type,handler,false); }else if(element.attachEvent){ element.attachEvent("on"+type,handler);//事件处理程序在全局作用域运行,this等于window }else{ element["on"+type] = handler; } }, removeHandler:function(element,type,handler){ if(element.removeEventListener){ element.removeEventListener(type,handler,false); }else if(element.detachEvent){ element.detachEvent("on"+type,handler); }else{ element["on"+type] = null; } } } EventUtil.addHandler(btn,"click",function(){ alert(this.id); });默认采用DOM0级方法。
DOM0级对每个事件只支持一个事件处理程序。
在触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含着所有与事件有关的信息。包括导致事件的元素,事件的类型以及其他与特定事件相关的信息。如:鼠标导致的事件对象中,会包含鼠标位置的信息,键盘操作导致的事件对象中,会包含与按下的键有关的信息。所有浏览器都支持event对象。1.DOM中的事件对象
无论指定事件处理程序时使用什么方法(DOM0级或DOM2级),都会传入event对象。var div = document.getElementById("mydiv"); var btn = document.getElementById("mybtn"); //DOM0 btn.onclick = function(event){ alert(event.type); console.log(event.currentTarget === this); //true console.log(event.target === this); //true } //DOM2 div.addEventListener("click",function(event){ alert(event.type); console.log(event.currentTarget === this); //true 当前执行这个事件的元素 console.log(event.target === this); // fasle console.log(event.target === btn); //true 点击的目标元素 },false);
单击按钮时,
target :事件的目标(实际点击的元素btn)currentTarget:事件处理程序当前正在处理事件的那个元素(btn没有点击处理事件,冒泡到div)在事件处理程序内部,对象this始终指向currentTarget
一个函数处理多个事件:通过event.type属性来区分事件类型var btn = document.getElementById("mybtn"); var handler = function(event){ switch(event.type){ case "click": alert("Clicked"); break; case "mouseover": event.target.style.backgroundColor = "red"; break; case "mouseout": event.target.style.backgroundColor = ""; break; } } btn.onclick = handler; btn.onmouseover = handler; btn.onmouseout = handler;preventDefault():阻止特定事件默认行为
var a = document.getElementById("aLink"); a.onclick = function(event){ event.preventDefault(); //阻止链接的默认事件 }cancelable属性为true的事件才能用上述方法取消默认事件。
stopPropagation():立即停止事件在DOM层次中的传播,取消进一步的事件捕获或冒泡。var div = document.getElementById("mydiv"); var btn = document.getElementById("mybtn"); var a = document.getElementById("aLink"); btn.onclick = function(event){ alert("hehe"); event.stopPropagation();//阻止事件冒泡 } div.onclick = function(){ alert("haha"); }eventPhase:调用事件处理程序的阶段:1.捕获阶段,2.表示"处于目标",3.冒泡阶段
var div = document.getElementById("div"); var btn = document.getElementById("btn"); btn.onclick = function(event){//尽管"处于目标“发生在冒泡阶段 console.log(event.eventPhase);//2 } div.addEventListener("click",function(event){ console.log(event.eventPhase);// 1 },true);//事件捕获阶段 div.addEventListener("click",function(event){ console.log(event.eventPhase); //3 },false);//事件冒泡阶段注意:只有在事件处理程序执行期间,event对象才会存在,一旦事件处理程序执行完成,event对象就会被销毁
2.IE中的事件对象
与访问DOM中的event对象不同,要访问IE中的event对象有几种不同的方式,取决于指定事件处理程序的方法。DOM0级方法添加事件处理程序:event对象作为window对象的一个属性存在........3.跨浏览器的事件对象
IE中event对象的全部信息和方法DOM对象中都有,只不过实现方式不一样。
var EventUtil = { addHandler:function(element,type,handler){}, removeHandler:function(element,type,handler){}, getEvent:function(event){ return event?event:window.event; }, getTarget:function(event){ return event.target||event.srcElement; }, preventDefault:function(event){ if(event.preventDefault{ event.preventDefault(); }else{ event.returnValue = false; } }, stopPropagation:function(event){//停止冒泡 if(event.stopPropagation){ event.stopPropagation(); }else{ event.cancelBubble = true; } } }
Web浏览器中可能发生的事件有很多类型。DOM3级事件规定如下:
- UI(User Interface,用户界面)事件,用户与页面元素交互时触发;
- 焦点事件,元素获得或失去焦点时触发;
- 鼠标事件,用户通过鼠标在页面上执行操作时触发;
- 滚轮事件,使用鼠标滚轮时触发;
- 文本事件,在文档中输入文本时触发;
- 键盘事件,通过键盘操作页面时触发
- 合成事件,IME(Input Method Editor,输入法编辑器)输入字符时触发
- 变动事件(mutation),底层DOM结构发生变化时触发
- 变动名称事件,元素货属性名变动时触发(已废弃)
1.UI事件
load:页面完全加载后(图像,js,css)在window上面触发。
方式一:js代码执行window.addEventListener("load",function(event){ console.log(event); },false);方式二:给元素添加onload特性<body onload="alert(event.target)"> <div id="div"> <input type="button" id="btn" value="btn"> </div> </body>一般来说,window上面发生的任何事件都可以在<body/>元素中通过特性来指定,因为HTML中无法访问window元素。”DOM2级事件“规范,应该在document而非window上面触发load事件。但是所有浏览器都在window上面实现了该事件,以确保向后兼容。建议使用js方式
EventUtil.addHandler(window,"load",function(){ var image = document.createElement("img"); EventUtil.addHandler(image,"load",function(event){ alert(event.target.src); }); document.body.appendChild(image); image.src = "mark.png"; });首先为window指定onload事件处理程序,因为要向DOM中添加新元素,必须确定页面已经加载完毕--否则操作document.body时会报错。然后创建img元素,设置src属性。 img元素不一定要从添加到文档后才开始下载,只要设置了src属性就会开始下载。
//使用DOM0级的Image对象实现图片的加载 EventUtil.addHandler(window,"load",function(){ var image = new Image(); EventUtil.addHandler(image,"load",function(event){ console.log(event); }); image.src = "mark.png";//加载图片 });该image对象无法添加到DOM树中(有的浏览器将Image对象实现为<img>元素,就可以添加)
<script>元素也会触发load事件,以便开发人员确定动态加载的JavaScript文件是否加载完毕。只有在设置了src属性并将元素添加到文档后,才会开始下载JavaScript文件。//设置src属性并添加到document文档中后才开始加载 EventUtil.addHandler(window,"load",function(){ var script = document.createElement("script"); script.src = "test.js"; document.body.appendChild(script); EventUtil.addHandler(script,"load",function(){ console.log("script loaded"); }); }); //判断样式表是否加载 EventUtil.addHandler(window,"load",function(event){ var link = document.createElement("link"); link.type = "text/css"; link.rel = "stylesheet"; EventUtil.addHandler(link,"load",function(){ console.log("css loaded"); }); //指定href属性并添加到document文档中后才开始加载 link.href = "ceshi.css"; document.getElementsByTagName("head")[0].appendChild(link); });unload事件在文档被完全卸载后触发。只要用户从一个页面切换到另一个页面,就会发生unload事件。利用这个事件最多的情况是清楚引用,以避免内存泄漏。方式一:js代码中调用,生成的event对象在兼容DOM的浏览器中只包含target属性(值为document)EventUtil.addHandler(window,"unload",function(){ console.log("unloaded"); });方式二:<body>元素添加特性 onunload
<body onunload="console.log('hei')">unload事件是在一切都被卸载后才触发的,那么在页面加载后存在的那些对象,此时就不一定存在了。此时操作DOM节点或者元素的样式就会报错。
resize事件浏览器窗口被调整到新的高度或宽度时,触发该事件EventUtil.addHandler(window,"resize",function(){ console.log("resize"); });注意:浏览器变化1像素就会触发resize事件,然后随着变化不断重复触发。不要在这个事件处理程序中加入大量计算的代码(可能被频繁执行)。
scroll事件
在window对象上发生,实际表示页面中相应元素的变化。在混杂模式下,可以通过<body>元素的scrollLeft和scrollTop来监控这一变化。EventUtil.addHandler(window,"scroll",function(event){ // console.log(document.documentElement.scrollTop); console.log(document.body.scrollTop); });2.焦点事件
会在页面获得或失去焦点时触发。与document.hasFocus()方法及document.activeElement属性配合,可以获取页面上的行踪。
- blur:元素失去焦点时触发。事件不会冒泡;所有浏览器都支持它。
- DOMFocusIn:元素获得焦点时触发。与HTML事件focus等价,冒泡。DOM3级事件废除。
- DOMFocusOut:元素失去焦点触发。HTML事件blur的通用版本。DOM3废弃。
- focus:元素获得焦点时触发。不冒泡;所有浏览器都支持
- focusin:元素获得焦点触发。与focus等价。但是冒泡
- focusout:元素失去焦点时触发。blur通用版本。
焦点从页面中的一个元素移动到另一个元素,依次触发下列事件:
- focusout失去焦点的元素上触发
- focusin获得焦点的元素上触发
- blur失去焦点的元素上触发
- DOMFocusOut
- focus
- DOMFocusIn
检测浏览器是否支持这些事件var isSupported = document.implementation.hasFeature("FocusEvent","3.0");即使focus和blur不冒泡,也可以在捕获阶段侦听到。
a.addEventListener("focus",function(event){ console.log(event.eventPhase); console.log("a"); },true); div.addEventListener("focus",function(event){//div先捕获 console.log(event.eventPhase); console.log("div"); },true);3.鼠标滚轮事件
- click:用户单击鼠标按钮或按下回车键时触发。onclick事件处理程序可以通过键盘也可以通过鼠标执行。
- dblclick:用户双击鼠标时触发
- mousedown:按下任意鼠标按钮时触发。不能通过键盘触发这个事件。
- mouseenter:鼠标光标从元素外部首次移动到元素范围之内时触发。事件不冒泡,光标移动到后代元素上不会触发
- mouseleave:位于元素上方的鼠标光标移动到元素范围之外时触发。事件不冒泡
- mousemove:鼠标指针在元素内部移动时重复触发。不能通过键盘触发这个事件。
- mouseout:鼠标指针位于一个元素上方,指针移入到另一个元素时触发。移入的另一个元素可能位于前一个元素的外部,也可能是这个元素的子元素
- mouseover:鼠标指针位于元素外部,用户将其首次移入另一个元素边界之内时触发。不能通过键盘触发这个事件。
- mouseup:用户释放鼠标按钮时触发。不能通过键盘触发这个事件。
只有在同一个元素上相继触发mousedown和mouseup事件,才会触发click事件,触发两次click事件才会触发一次dbclick事件.检测浏览器是否支持鼠标事件
//DOM2级事件,除dbclick,mouseenter和mouseleave外 var isSupported = document.implementation.hasFeature("MouseEvents","2.0"); //DOM3级事件 var isSupported3 = document.implementation.hasFeature("MouseEvent","3.0"); console.log(isSupported); console.log(isSupported3);
1.客户区坐标位置
鼠标事件都是在浏览器视图中的特定位置上发生的。位置信息保存在事件对象clientX和clientY属性中。所有浏览器都支持这两个属性。
var div = document.getElementById("div"); EventUtil.addHandler(div,"click",function(event){ console.log("x:"+event.clientX); console.log("y:"+event.clientY); });坐标值中不包括页面滚动的距离
2.页面坐标位置
pageX,pageY:可知道事件是在页面中的什么位置发生的。表示鼠标光标在页面中的位置(坐标是从页面本身而非视图的左边和顶边计算)3.屏幕坐标位置
screenX,screenY:相对于整个电脑屏幕的位置。
var div = document.getElementById("div"); EventUtil.addHandler(div,"click",function(event){ console.log("x:"+event.clientX); console.log("y:"+event.clientY); console.log("pageX:"+event.pageX); console.log("pageY:"+event.pageY); console.log("screenX:"+event.screenX); console.log("screenY:"+event.screenY); });
4.修改键
按下鼠标时同时也按下键盘的指定的按键(如:shift,ctrl,alt,meta)
var keys = []; if(event.shiftKey){ keys.push("shift"); } if(event.ctrlKey){ keys.push("ctrl"); } if(event.altKey){ keys.push("alt"); } if(event.metaKey){ keys.push("meta"); } console.log(keys.join(","));
5.相关元素
mouseover和mouseout事件发生时会涉及多个元素。
mouseover:事件的主目标是获得光标的元素,相关元素是失去光标的元素。
通过event的relatedTarget属性来获取相关元素,该属性只有mouseover和mouseout事件才包含。
//夸浏览器获取relatedTarget属性 function getRelatedTarget(){ if(event.relatedTarget){ return event.relatedTarget; }else if(event.toElement){ return event.toElement; }else if(event.fromElement){ return event.fromElement; }else{ return null; } }
6.鼠标按钮
mousedown,mouseup事件,在其处理程序event对象中存在一个button属性,表示按下或释放的按钮。
DOM的button属性有下面3个值:0鼠标左键,1中间滚轮,2鼠标右键
btn.addEventListener("mousedown",function(event){ console.log(event.button); },false); btn.addEventListener("mouseup",function(event){ console.log("MOUSEUP:"+event.button); },false);
7.鼠标滚轮事件mousewheel
当用户通过鼠标滚轮与页面交互,在垂直方向上滚动页面时(无论向上还是向下),就会触发mousewheel事件。这个事件可以在任何元素上面触发,最终会冒泡到document或window对象。事件对应的event对象包含一个特殊的wheelDelta属性。向前滚动时,wheelDelta是120的倍数;向后滚动是-120的倍数。
//opera9.5之前值是相反的 EventUtil.addHandler(document,"mousewheel",function(event){ //兼容早期Opera版本 var delta = (client.engine.opera && client.engine.opera<9.5 ? -event.wheelDelta:event.wheelDelta); console.log(delta); });
主要遵循DOM0级。
keydown:按下键盘上的任意键时触发,按住不放会重复触发此事件
keypress:按下键盘上的字符键时触发,按住不放会重复触发
keyup:释放键盘上的键时触发。
一个文本事件:textInput。在文本插入文本框之前触发
1.键码
keydown和keyup事件触发时,event对象的keyCode属性中会包含一个代码,与键盘上特定的键对应。
EventUtil.addHandler(text,"keydown",function(event){ console.log(event.keyCode); });2.字符编码
EventUtil.addHandler(text,"keypress",function(event){ console.log(event.charCode); if(typeof event.charCode=='number'){ console.log(String.fromCharCode(event.charCode)); return event.charCode; }else{ console.log(event.keyCode); return event.keyCode; } });3.textInput事件
在可编辑区域中输入字符时,就会触发这个事件。用来替代keypress事件
与keypress区别:
1.任何可以获得焦点的元素都可以触发keypress事件,只有可编辑区域才能触发textInput事件
2.textInput事件只会在用户按下能够输入实际字符的键时才会被触发,keypress在按下能够影响文本显示的 键时就会触发(如退格键)
event.data:按键的值
EventUtil.addHandler(text,"textInput",function(event){ console.log(event.data); });5.变动事件
DOM中的某一部分发生变化时触发
- DOMSubtreeModified:DOM结构中发生任何变化时触发
- DOMNodeInserted:节点作为子节点被插入到另一个节点中时触发
- DOMNodeInsertedIntoDocument:节点被直接插入文档或通过子树间接插入文档后触发。在DOMNodeInserted后触发
- DOMNodeRemoved:节点从父节点中被移除时触发
- DOMNodeRemovedFromDocument:节点被直接移除或通过子树间接移除之前触发。DOMNodeRemoved之后触发
- DOMAttrModified:元素特性被修改后触发
- DOMCharacterDataModified:文本节点的值发生变化时触发。
1.删除节点
使用removeChild()或replaceChild()从DOM中删除节点时,首先触发DOMNodeRemoved事件。event.target是被删除的节点。event.relatedNode属性中包含着对目标节点父节点的引用。事件触发时节点还没从父节点中删除,parentNode属性仍然指向父节点(与event.relatedNode相同)。事件会冒泡,可以在DOM的任何层次上处理。
如果被移除的节点包含子节点,那么在其所有子节点以及这个被移除的节点上会相继触发DOMNodeRemovedFromDocument事件。但这个事件不会冒泡,只有直接给其中一个子节点的事件处理程序才会被调用。
var ul = document.getElementById("myList"); //DOM发生变化就 触发 EventUtil.addHandler(document,"DOMSubtreeModified",function(event){ //4 console.log(event.target); }); //事件冒泡到document处理 EventUtil.addHandler(document,"DOMNodeRemoved",function(event){//1 console.log(event.type); console.log(event.type); console.log(event.target); console.log(event.relatedNode == document.body);//true }); EventUtil.addHandler(ul,"DOMNodeRemovedFromDocument",function(){//2 console.log("ul:DOMNodeRemovedFromDocument"); }); EventUtil.addHandler(ul.firstChild,"DOMNodeRemovedFromDocument",function(event){//3 console.log(event.type); console.log(event.target); console.log("li:DOMNodeRemovedFromDocument"); }); ul.parentNode.removeChild(ul);2.插入节点
使用appendChild(),replaceChild()或insertBefore()向DOM中插入节点时
1.触发DOMNodeInserted事件。事件的目标是被插入的节点,event.relatedNode包含对父节点的引用。在触发这个事件时,节点已经被插入到了新的父节点中。事件冒泡,可在DOM的各个层次上处理。
2.在新插入的节点上触发DOMNodeInsertedIntoDocument事件。事件不冒泡,必须在插入节点前添加事件处理程序。
3.触发DOMSubtreeModified
var ul = document.getElementById("myList"); var li = document.createElement("li"); li.appendChild(document.createTextNode("Item 4")); EventUtil.addHandler(ul,"DOMNodeInserted",function(event){ console.log(1); console.log(event.type); console.log(event.target); }); EventUtil.addHandler(document,"DOMNodeInserted",function(event){//冒泡 console.log(2); console.log(event.type); console.log(event.target); }); EventUtil.addHandler(li,"DOMNodeInsertedIntoDocument",function(event){ console.log(3); console.log(event.type); console.log(event.target); }); EventUtil.addHandler(document,"DOMSubtreeModified",function(event){ console.log(4); console.log(event.type); console.log(event.target); //UL }); ul.appendChild(li);
1.contextmenu事件
用于表示何时应该显示上下文菜单。contextmenu事件冒泡,可以给document指定一个事件处理程序,用来处理页面中所有这类事件。
取消该事件:event.preventDefalut()
简单右键菜单样式:
var div = document.getElementById("myDiv"); var ul = document.getElementById("myList"); EventUtil.addHandler(div,"contextmenu",function(event){ event.preventDefault();//阻止默认的右键事件 ul.style.left = event.clientX+"px"; ul.style.top = event.clientY+"px"; ul.style.visibility = "visible"; }); EventUtil.addHandler(document,"click",function(event){ ul.style.visibility = "hidden"; });2.beforeunload事件
在页面卸载前阻止该操作。该事件会在浏览器卸载页面前触发,可通过它取消卸载并继续使用原有页面。
为显示弹出对话框,必须将event.returnValue的值设置为要显示给用户的字符串,同时作为函数的值返回。EventUtil.addHandler(window,"beforeunload",function(event){ var msg = "确定退出?"; event.returnValue = msg; return msg; });3.DOMContentLoaded事件
window的load事件是在页面中的一切都加载完后触发,但这个过程可能会因为要加载的外部资源过多而颇费周折。DOMContentLoaded事件是在DOM树加载完后就触发,忽略图像,js文件,css文件的加载。该事件支持在页面下载的早期添加事件处理程序,用户能够尽早地与页面交互。
给document或window添加相应的事件处理程序(这个事件会冒泡到window,但它的目标实际上是document)EventUtil.addHandler(window,"load",function(event){ console.log("window load"); }); EventUtil.addHandler(document,"DOMContentLoaded",function(event){ console.log(event.target); //document console.log("DOM loaded"); });对不支持DOMContentLoaded的浏览器,可以在页面加载期间设置一个超时调用,但是无法保证会早于load事件setTimeout(function(){ //添加事件处理 },0);4.readystatechange事件
提供与文档或元素的加载状态有关的信息。readyState属性:
- uninitialized(未初始化):对象存在但尚未初始化
- loading(正在加载):对象正在加载数据
- loaded(加载完毕):对象加载数据完成
- interactive(交互):可以操作对象的,但还没有完全加载
- complete(完成):对象已经加载完毕
对于document而言,值为"interactive"的readyState会在与DOMContentLoaded大致相同的时刻触发readystatechange事件。此时DOM树已经加载完毕,可以安全操作它了,因此就会进入(interactive)交互阶段。但与此同时,图像及其他外部文件不一定可用。EventUtil.addHandler(document,"readystatechange",function(event){ console.log(document.readyState); console.log(event); }); EventUtil.addHandler(window,"load",function(){ console.log("load"); });与load事件一起使用时,无法预测两个事件触发的先后顺序。外部资源多时,交互阶段更有可能早于完成阶段;页面外部资源少时,完成阶段早于交互阶段的可能性更大。同时检测交互和完成阶段。
EventUtil.addHandler(document,"readystatechange",function(event){ if(document.readyState =='interactive' || document.readyState == 'complete'){ EventUtil.removeHandler(document,"readystatechange",arguments.callee); console.log("loaded"); } });如果进入交互阶段或完成阶段,就移除相应的事件处理程序以免在其他阶段再执行。
<script>和<link>元素也可以触发readystatechange事件,可以用来确定外部的JavaScript和CSS文件是否已经加载完成。必须将元素添加到页面后才会开始下载外部资源。页面缓存在浏览器中后,当请求的页面是从缓存中取的,就不会触发load事件。EventUtil.addHandler(window,"load",function(event){ var script = document.createElement("script"); var link = document.createElement("link"); link.type = "text/css"; link.rel = "stylesheet"; EventUtil.addHandler(script,"readystatechange",function(event){//<span style="color:#ff0000;">事件没触发?</span> console.log(event); var target = event.target; console.log(target == this); if(target.readyState == "loaded"||target.readyState=="complete"){ //取消匿名的事件处理程序 EventUtil.removeHandler(target,"readystatechange",arguments.callee); } }); EventUtil.addHandler(link,"readystatechange",function(event){ console.log("css"); }); script.src = "/test.js"; document.body.appendChild(script); link.href="test.css"; document.getElementsByTagName("head")[0].appendChild(link); });无法触发readystatechange事件?
5.pageshow和pagehide事件
pageshow会在页面显示时触发(load触发后触发),无论来自缓存还是重新加载。事件的目标是document,但必须将事件处理程序添加到window。(function(){ var showCount = 0; EventUtil.addHandler(window,"load",function(){ console.log("load"); }); EventUtil.addHandler(window,"pageshow",function(){ showCount++; console.log(event.persisted);//页面是否来自缓存 console.log("pageshow"+showCount); }); })();pagehide,在浏览器卸载页面时触发,在unload事件之前触发。在document上触发,但事件处理程序必须添加到window对象。
指定了onunload事件处理程序的页面会自动排除在缓存外。即使事件处理程序是空的。因为onunload常用于撤销在onload中所执行的操作,而跳过onload后再显示页面很可能会导致页面不正常(页面被卸载,再次显示时不去load,可能导致一些初始化数据的操作不执行)。6.hashchange事件
URL参数列表(URL中#后面的所有字符串)发生变化时触发。事件处理程序必须添加给window对象。event包含 oldURL和newURL两个额外属性。EventUtil.addHandler(window,"hashchange",function(event){ console.log(event.oldURL+" "+event.newURL); console.log(location.hash); });
JavaScript中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能。每个函数都是对象,都会占用内存;内存中的对象越多,性能就越差。必须事先指定所有事件处理程序,会导致DOM访问次数增加,会延迟整个页面的交互就绪时间。
1.事件委托
解决事件处理程序过多的问题。利用事件冒泡,指定一个事件处理程序就可以管理某一类型的所有事件。使用事件委托,只需要在DOM树中尽量最高的层次上添加一个事件处理程序。<ul id="myList"> <li id="item1">Item 1</li> <li id="item2">Item 2</li> <li id="item3">Item 3</li> </ul>
var myList = document.getElementById("myList"); EventUtil.addHandler(myList,"click",function(event){ var target = event.target; if(target.id=='item1'){ console.log("item1 clicked"); }else if(target.id=='item2'){ console.log("item2 clicked"); }else if(target.id == 'item3'){ console.log("item3 clicked"); } });如果可行,可以考虑给document对象添加事件处理程序,用以处理页面上发生的某种特定类型的事件,有点如下:
- document对象很快就可以访问,而且可以在页面生命周期的任何时点上为它添加事件处理程序(无需等待DOMContentLoaded或load事件)
- 在页面中设置事件处理程序所需的时间更少。只添加一个事件处理程序所需的DOM引用少,花的时间也少
- 整个页面占用的内存空间更少,能够提升整体性能。
适合事件委托的事件包括:click,mousedown,mouseup,keydown,keyup和keypress2.移除事件处理程序
每当将事件处理程序指定给元素时,运行中的浏览器代码与支持页面交互的JavaScript代码之间就会建立一个连接。这种连接越多,页面执行起来就越慢。内存中留存的过时不用的"空事件处理程序",也是造成web应用程序内存与性能问题的主要原因。
情形一:从文档中删除带有事件处理程序的元素时。原来添加到元素的事件处理程序可能无法被当作垃圾回收。
var div = document.getElementById("myDiv"); var btn = document.getElementById("btn"); btn.onclick = function(){ btn.onclick = null; //移除事件处理程序 div.innerHTML = "hehe"; }注意:在事件处理程序中删除目标元素也能阻止事件冒泡。目标元素在文档中是事件冒泡的前提。
情形二:页面卸载(页面卸载前没有清理干净事件处理程序)。在页面卸载前,通过onunload事件处理程序移除所有事件处理程序。
只要是通过onload事件处理程序添加的东西,最后就要通过onunload事件处理程序移除。
1.DOM中的事件模拟
可以在document对象上使用createEvent()方法创建event对象。方法接收一个参数,表示要创建的事件类型的字符串。
- UIEvents:一般化的UI事件,鼠标和键盘事件都继承自UI事件
- MouseEvents:一 般化的鼠标事件。
- MutationEvents:一般化的DOM变动事件
- HTMLEvents:一般化的HTML事件
触发事件。dispatchEvent()所有支持事件的DOM节点都支持这个方法。方法接收一个参数,表示要触发事件的event对象。
1.模拟鼠标事件
initMouseEvent():初始化鼠标事件信息。15个参数
- type(字符串):要触发的事件类型,如“click”。
- bubbles(布尔值):是否冒泡,true
- cancelable(布尔值):是否可取消,true
- view:与事件关联的视图。几乎总是设置为document.defaultView
- detail(整数):与事件有关的详细信息。一般只有事件处理程序使用,通常设置为0
- screenX:相对于屏幕的坐标
- screenY:
- clientX:相对于浏览器窗口的坐标
- clientY:
- ctrlKey(布尔值):是否按下了Ctrl键。默认false
- altKey(布尔值):是否按下的Alt键。默认false
- shiftKey(布尔值):是否按下了Shift键。默认false
- metaKey(布尔值):是否按下了Meta键。默认false
- button(整数):表示按下了哪个鼠标键。默认0
- relatedTarget(对象):与事件相关的对象。只在模拟mouseover或mouseout时使用
var div = document.getElementById("myDiv"); var btn = document.getElementById("btn"); btn.onclick = function(){ btn.onclick = null; //移除事件处理程序 div.innerHTML = "hehe"; } //创建事件对象 var event = document.createEvent("MouseEvents"); //初始化事件对象 event.initMouseEvent("click",true,true,document.defaultView,0,0,0,0,0,false,false,false,false,0,null); //触发事件 btn.dispatchEvent(event);
2.模拟键盘事件
DOM3级规定,调用createEvent()并传入"KeyboardEvent"就可以创建一个键盘事件。返回的事件对象包含一个initKeyEvent()方法
- type:要触发的事件类型:如"keydown"
- bubbles:事件是否应该冒泡。true
- cancelable:事件是否可以取消。true
- view:事件关联的视图。几乎总是设置为document.defaultView
- key:按下的键的键码
- location:按键的位置。0主键盘,1左,2右,3数字键,4移动设备,5手柄
- repeat:一行中按了这个键多少次
var testbox = document.getElementById("text"); EventUtil.addHandler(testbox,"keydown",function(event){ console.log(event.type); console.log(event); }); var event = null; //DOM3级方式创建事件对象 if(document.implementation.hasFeature("KeyboardEvents","3.0")){ event = document.createEvent("KeyboardEvent"); event.initKeyboardEvent("keydown",true,true,document.defaultView,"a",0,"Shift",0); } //触发事件 testbox.dispatchEvent(event);3.模拟其他事件
变动事件 createEvent("MutationEvents")创建事件对象,包含initMutationEvent()初始化方法。接收参数如下:
type,bubbles,cancelable,relatedNode,preValue,newValue,attrName,attrChange
EventUtil.addHandler(document,"DOMNodeInserted",function(event){ console.log(event); console.log("insert"); }); var event = document.createEvent("MutationEvents"); var node = document.createElement("li"); event.initMutationEvent("DOMNodeInserted",true,false,node,"","","",0); document.dispatchEvent(event);HTML事件 createEvent("HTMLEvents")创建事件对象,initEvent()初始化
var text = document.getElementById("text"); EventUtil.addHandler(text,"focus",function(event){ console.log(event); }); var event = document.createEvent("HTMLEvents"); event.initEvent("focus",true,false); text.dispatchEvent(event);4.自定义DOM事件
createEvent("CustomEvent")创建对象,initCustomEvent()初始化对象,接收4个参数:
- type:触发事件类型,如“keydown”
- bubbles:事件是否冒泡
- cancelable:事件是否可以取消
- detail:任意值,保存在event对象的detail属性中
var div = document.getElementById("myDiv"); EventUtil.addHandler(div,"myevent",function(event){ console.log("div:"+event.detail); }); EventUtil.addHandler(document,"myevent",function(event){ console.log("document:"+event.detail); }); //模拟触发自定义事件 if(document.implementation.hasFeature("CustomEvents","3.0")){ event = document.createEvent("CustomEvent"); event.initCustomEvent("myevent",true,false,"hehe"); div.dispatchEvent(event); }