目录
2.1 beforeunload事件,unload事件,load事件,error事件,pageshow事件,pagehide事件
2.2 DOMContentLoaded事件,readystatechange事件
1 表单事件
1.1 Input事件,select事件,change事件
以下事件与表单成员的值变化有关。
1.1.1 input事件
input事件当<input>、<textarea>的值发生变化时触发。此外,打开contenteditable属性的元素,只要值发生变化,也会触发input事件。
input事件的一个特点,就是会连续触发,比如用户每次按下一次按键,就会触发一次input事件。
1.1.2 select事件
select事件当在<input>、<textarea>中选中文本时触发。
// HTML代码为
// <input id="test" type="text" value="Select me!" />
var elem = document.getElementById('test');
elem.addEventListener('select', function() {
console.log('Selection changed!');
}, false);
1.1.3 Change事件
Change事件当<input>、<select>、<textarea>的值发生变化时触发。它与input事件的最大不同,就是不会连续触发,只有当全部修改完成时才会触发,而且input事件必然会引发change事件。具体来说,分成以下几种情况。
- 激活单选框(radio)或复选框(checkbox)时触发。
- 用户提交时触发。比如,从下列列表(select)完成选择,在日期或文件输入框完成选择。
- 当文本框或textarea元素的值发生改变,并且丧失焦点时触发。
下面是一个例子。
// HTML代码为
// <select size="1" onchange="changeEventHandler(event);">
// <option>chocolate</option>
// <option>strawberry</option>
// <option>vanilla</option>
// </select>
function changeEventHandler(event) {
console.log('You like ' + event.target.value + ' ice cream.');
}
1.2 reset事件,submit事件
以下事件发生在表单对象上,而不是发生在表单的成员上。
注意:如果在onsubmit和onreset事件中调用的是自定义的函数名,那么,必须在函数名的前面加“return”语句;否则,不论在函数中返回的是true还是false,当前事件返回的值一律是true值
1.2.1 reset事件
reset事件当表单重置(所有表单成员变回默认值)时触发。
1.2.2 submit事件
submit事件当表单数据向服务器提交时触发。注意,submit事件的发生对象是form元素,而不是button元素(即使它的类型是submit),因为提交的是表单,而不是按钮。
2 文档事件
2.1 beforeunload事件,unload事件,load事件,error事件,pageshow事件,pagehide事件
以下事件与网页的加载与卸载相关。
2.1.1 beforeunload事件
beforeunload事件当窗口将要关闭,或者document和网页资源将要卸载时触发。它可以用来防止用户不当心关闭网页。
该事件的默认动作就是关闭当前窗口或文档。如果在监听函数中,调用了event.preventDefault()
,或者对事件对象的returnValue属性赋予一个非空的值,就会自动跳出一个确认框,让用户确认是否关闭网页。如果用户点击“取消”按钮,网页就不会关闭。监听函数所返回的字符串,会显示在确认对话框之中。
window.onbeforeunload = function() {
if (textarea.value != textarea.defaultValue) {
return '你确认要离开吗?';
}
};
上面代码表示,当用户关闭网页,会跳出一个确认对话框,上面显示“你确认要离开吗?”。
下面的两种写法,具有同样效果。
window.addEventListener('beforeunload', function( event ) {
event.returnValue = '你确认要离开吗?';
});
// 等同于
window.addEventListener('beforeunload', function( event ) {
event.preventDefault();
});
上面代码中,事件对象的returnValue属性的值,将会成为确认框的提示文字。
只要定义了beforeunload事件的监听函数,网页不会被浏览器缓存。
2.1.2 unload事件
unload事件在窗口关闭或者document对象将要卸载时触发,发生在window、body、frameset等对象上面。它的触发顺序排在beforeunload、pagehide事件后面。unload事件只在页面没有被浏览器缓存时才会触发,换言之,如果通过按下“前进/后退”导致页面卸载,并不会触发unload事件。
当unload事件发生时,document对象处于一个特殊状态。所有资源依然存在,但是对用户来说都不可见,UI互动(window.open、alert、confirm方法等)全部无效。这时即使抛出错误,也不能停止文档的卸载。
window.addEventListener('unload', function(event) {
console.log('文档将要卸载');
});
如果在window对象上定义了该事件,网页就不会被浏览器缓存。
2.1.3 load事件,error事件
load事件在页面加载成功时触发,error事件在页面加载失败时触发。注意,页面从浏览器缓存加载,并不会触发load事件。
这两个事件实际上属于进度事件,不仅发生在document对象,还发生在各种外部资源上面。浏览网页就是一个加载各种资源的过程,图像(image)、样式表(style sheet)、脚本(script)、视频(video)、音频(audio)、Ajax请求(XMLHttpRequest)等等。这些资源和document对象、window对象、XMLHttpRequestUpload对象,都会触发load事件和error事件。
2.1.4 pageshow事件,pagehide事件
默认情况下,浏览器会在当前会话(session)缓存页面,当用户点击“前进/后退”按钮时,浏览器就会从缓存中加载页面。
pageshow事件在页面加载时触发,包括第一次加载和从缓存加载两种情况。如果要指定页面每次加载(不管是不是从浏览器缓存)时都运行的代码,可以放在这个事件的监听函数。
第一次加载时,它的触发顺序排在load事件后面。从缓存加载时,load事件不会触发,因为网页在缓存中的样子通常是load事件的监听函数运行后的样子,所以不必重复执行。同理,如果是从缓存中加载页面,网页内初始化的JavaScript脚本(比如DOMContentLoaded事件的监听函数)也不会执行。
window.addEventListener('pageshow', function(event) {
console.log('pageshow: ', event);
});
pageshow事件有一个persisted属性,返回一个布尔值。页面第一次加载时,这个属性是false;当页面从缓存加载时,这个属性是true。
window.addEventListener('pageshow', function(event){
if (event.persisted) {
// ...
}
});
pagehide事件与pageshow事件类似,当用户通过“前进/后退”按钮,离开当前页面时触发。它与unload事件的区别在于,如果在window对象上定义unload事件的监听函数之后,页面不会保存在缓存中,而使用pagehide事件,页面会保存在缓存中。
pagehide事件的event对象有一个persisted属性,将这个属性设为true,就表示页面要保存在缓存中;设为false,表示网页不保存在缓存中,这时如果设置了unload事件的监听函数,该函数将在pagehide事件后立即运行。
如果页面包含frame或iframe元素,则frame页面的pageshow事件和pagehide事件,都会在主页面之前触发。
2.2 DOMContentLoaded事件,readystatechange事件
以下事件与文档状态相关。
2.2.1 DOMContentLoaded事件
当HTML文档下载并解析完成以后,就会在document对象上触发DOMContentLoaded事件。这时,仅仅完成了HTML文档的解析(整张页面的DOM生成),所有外部资源(样式表、脚本、iframe等等)可能还没有下载结束。也就是说,这个事件比load事件,发生时间早得多。
document.addEventListener("DOMContentLoaded", function(event) {
console.log("DOM生成");
});
注意,网页的JavaScript脚本是同步执行的,所以定义DOMContentLoaded事件的监听函数,应该放在所有脚本的最前面。否则脚本一旦发生堵塞,将推迟触发DOMContentLoaded事件。
2.2.2 readystatechange事件
readystatechange事件发生在Document对象和XMLHttpRequest对象,当它们的readyState属性发生变化时触发。
document.onreadystatechange = function () {
if (document.readyState == "interactive") {
// ...
}
}
IE8不支持DOMContentLoaded事件,但是支持这个事件。因此,可以使用readystatechange事件,在低版本的IE中代替DOMContentLoaded事件。
2.3 scroll事件,resize事件
以下事件与窗口行为有关。
2.3.1 scroll事件
scroll事件在文档或文档元素滚动时触发。
由于该事件会连续地大量触发,所以它的监听函数之中不应该有非常耗费计算的操作。推荐的做法是使用requestAnimationFrame或setTimeout控制该事件的触发频率,然后可以结合customEvent抛出一个新事件。
(function() {
var throttle = function(type, name, obj) {
var obj = obj || window;
var running = false;
var func = function() {
if (running) { return; }
running = true;
requestAnimationFrame(function() {
obj.dispatchEvent(new CustomEvent(name));
running = false;
});
};
obj.addEventListener(type, func);
};
// 将scroll事件重定义为optimizedScroll事件
throttle("scroll", "optimizedScroll");
})();
window.addEventListener("optimizedScroll", function() {
console.log("Resource conscious scroll callback!");
});
上面代码中,throttle函数用于控制事件触发频率,requestAnimationFrame方法保证每次页面重绘(每秒60次),只会触发一次scroll事件的监听函数。改用setTimeout方法,可以放置更大的时间间隔。
(function() {
window.addEventListener("scroll", scrollThrottler, false);
var scrollTimeout;
function scrollThrottler() {
if ( !scrollTimeout ) {
scrollTimeout = setTimeout(function() {
scrollTimeout = null;
actualScrollHandler();
}, 66);
}
}
function actualScrollHandler() {
// ...
}
}());
上面代码中,setTimeout指定scroll事件的监听函数,每66毫秒触发一次(每秒15次)。
2.3.2 resize事件
resize事件在改变浏览器窗口大小时触发,发生在window、body、frameset对象上面。
var resizeMethod = function(){
if (document.body.clientWidth < 768) {
console.log('移动设备');
}
};
window.addEventListener("resize", resizeMethod, true);
该事件也会连续地大量触发,所以最好像上面的scroll事件一样,通过throttle函数控制事件触发频率。
2.4 hashchange事件,popstate事件
以下事件与文档的URL变化相关。
2.4.1 hashchange事件
hashchange事件在URL的hash部分(即#号后面的部分,包括#号)发生变化时触发。如果老式浏览器不支持该属性,可以通过定期检查location.hash属性,模拟该事件,下面就是代码。
(function(window) {
if ( "onhashchange" in window.document.body ) { return; }
var location = window.location;
var oldURL = location.href;
var oldHash = location.hash;
// 每隔100毫秒检查一下URL的hash
setInterval(function() {
var newURL = location.href;
var newHash = location.hash;
if ( newHash != oldHash && typeof window.onhashchange === "function" ) {
window.onhashchange({
type: "hashchange",
oldURL: oldURL,
newURL: newURL
});
oldURL = newURL;
oldHash = newHash;
}
}, 100);
})(window);
hashchange事件对象除了继承Event对象,还有oldURL属性和newURL属性,分别表示变化前后的URL。
2.4.2 popstate事件
popstate事件在浏览器的history对象的当前记录发生显式切换时触发。注意,调用history.pushState()或history.replaceState(),并不会触发popstate事件。该事件只在用户在history记录之间显式切换时触发,比如鼠标点击“后退/前进”按钮,或者在脚本中调用history.back()、history.forward()、history.go()时触发。
该事件对象有一个state属性,保存history.pushState方法和history.replaceState方法为当前记录添加的state对象。
window.onpopstate = function(event) {
console.log("state: " + event.state);
};
history.pushState({page: 1}, "title 1", "?page=1");
history.pushState({page: 2}, "title 2", "?page=2");
history.replaceState({page: 3}, "title 3", "?page=3");
history.back(); // state: {"page":1}
history.back(); // state: null
history.go(2); // state: {"page":3}
上面代码中,pushState方法向history添加了两条记录,然后replaceState方法替换掉当前记录。因此,连续两次back方法,会让当前条目退回到原始网址,它没有附带state对象,所以事件的state属性为null,然后前进两条记录,又回到replaceState方法添加的记录。
浏览器对于页面首次加载,是否触发popstate事件,处理不一样,Firefox不触发该事件。
2.5 cut事件,copy事件,paste事件
以下三个事件属于文本操作触发的事件。
注意:在onbeforecopy和oncopy事件中调用的是自定义函数名,那么,必须在函数名的前面加return语句;否则不论在函数中返回的true,还是false,当前事件返回值一律是true,也就是允许复制。cut和paste同样
- cut事件:在将选中的内容从文档中移除,加入剪贴板后触发。
- copy事件:在选中的内容加入剪贴板后触发。
- paste事件:在剪贴板内容被粘贴到文档后触发。
这三个事件都有一个clipboardData只读属性。该属性存放剪贴的数据,是一个DataTransfer对象
2.6 焦点事件
焦点事件发生在Element节点和document对象上面,与获得或失去焦点相关。它主要包括以下四个事件。
- focus事件:Element节点获得焦点后触发,该事件不会冒泡。
- blur事件:Element节点失去焦点后触发,该事件不会冒泡。
- focusin事件:Element节点将要获得焦点时触发,发生在focus事件之前。该事件会冒泡。Firefox不支持该事件。
- focusout事件:Element节点将要失去焦点时触发,发生在blur事件之前。该事件会冒泡。Firefox不支持该事件。
这四个事件的事件对象,带有target属性(返回事件的目标节点)和relatedTarget属性(返回一个Element节点)。对于focusin事件,relatedTarget属性表示失去焦点的节点;对于focusout事件,表示将要接受焦点的节点;对于focus和blur事件,该属性返回null。
由于focus和blur事件不会冒泡,只能在捕获阶段触发,所以addEventListener方法的第三个参数需要设为true。
form.addEventListener("focus", function( event ) {
event.target.style.background = "pink";
}, true);
form.addEventListener("blur", function( event ) {
event.target.style.background = "";
}, true);
上面代码设置表单的文本输入框,在接受焦点时设置背景色,在失去焦点时去除背景色。
浏览器提供一个FocusEvent构造函数,可以用它生成焦点事件的实例。
var focusEvent = new FocusEvent(typeArg, focusEventInit);
上面代码中,FocusEvent构造函数的第一个参数为事件类型,第二个参数是可选的配置对象,用来配置FocusEvent对象。
3 自定义事件和事件模拟
除了浏览器预定义的那些事件,用户还可以自定义事件,然后手动触发。
// 新建事件实例
var event = new Event('build');
// 添加监听函数
elem.addEventListener('build', function (e) { ... }, false);
// 触发事件
elem.dispatchEvent(event);
上面代码触发了自定义事件,该事件会层层向上冒泡。在冒泡过程中,如果有一个元素定义了该事件的监听函数,该监听函数就会触发。
3.1 CustomEvent()
Event构造函数只能指定事件名,不能在事件上绑定数据。如果需要在触发事件的同时,传入指定的数据,需要使用CustomEvent构造函数生成自定义的事件对象。
var event = new CustomEvent('build', { 'detail': 'hello' });
function eventHandler(e) {
console.log(e.detail);
}
上面代码中,CustomEvent构造函数的第一个参数是事件名称,第二个参数是一个对象,该对象的detail属性会绑定在事件对象之上。
下面是另一个例子。
var myEvent = new CustomEvent("myevent", {
detail: {
foo: "bar"
},
bubbles: true,
cancelable: false
});
el.addEventListener('myevent', function(event) {
console.log('Hello ' + event.detail.foo);
});
el.dispatchEvent(myEvent);
IE不支持这个方法,可以用下面的垫片函数模拟。
(function () {
function CustomEvent ( event, params ) {
params = params || { bubbles: false, cancelable: false, detail: undefined };
var evt = document.createEvent( 'CustomEvent' );
evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
return evt;
}
CustomEvent.prototype = window.Event.prototype;
window.CustomEvent = CustomEvent;
})();
3.2 事件的模拟
有时,需要在脚本中模拟触发某种类型的事件,这时就必须使用这种事件的构造函数。
下面是一个通过MouseEvent构造函数,模拟触发click鼠标事件的例子。
function simulateClick() {
var event = new MouseEvent('click', {
'bubbles': true,
'cancelable': true
});
var cb = document.getElementById('checkbox');
cb.dispatchEvent(event);
}