Js高级程序设计第三版学习(十三章)
第十三章 事件
事件就是文档或者浏览器窗口中发生的特定的交互的瞬间
1.事件流:
事件流描述的是页面中接收事件的顺序
- 事件冒泡
事件开始由最具体的元素(目标元素) 直至不具体的节点(文档), 事件会依次传播,例如点击一个div,click事件会传递到他的父级直到document或window,所有浏览器都支持事件冒泡
<ul id="uu" style="height:1800px;margin-top: 0;position: relative">
<li id="hehe" class="ppap">
<div id="divClick">1</div>
<div>2</div>
<div>3</div>
<div>4</div>
</li>
<li class="ppap" id="pppp">2</li>
<li class="ppap"><input type="text" id="text" /></li>
<li class="ppap"><a href="https://www.baidu.com">adhlasdhl</a></li>
</ul>
<script>
var hehe = document.getElementById('hehe');
var divClick = document.getElementById('divClick');
hehe.onclick = function() {
console.log('hehe');
};
divClick.onclick = function() {
console.log('divClick');
};
window.onclick = function(){
console.log('window')
}
/*
页面会依次打印 hehe,divClick,document,window
*/
</script>
- 事件捕获
事件捕获与冒泡相反,不具体节点先接收事件,具体节点在最后接收事件,它的寓意在于事件在到达预定目标之前捕获他
- DOM事件流
DOM2级规定了事件流包括三个阶段 事件捕获, 处于目标阶段, 事件冒泡阶段,
2.事件处理程序:
事件就是用户或者浏览器自身执行的动作, 而事件处理程序就是响应某个事件的函数,
- HTML事件处理程序
元素支持的每种事件,都可以使用相应的事件处理程序同名的HTML特性绑定,这个值应该是可以执行的js代码,
<li class="ppap"><a href="https://www.baidu.com" onclick="console.log('AClick')">adhlasdhl</a></li>
<li class="ppap"><a href="javascript:void(0)" onclick="handleClick()">adhlasdhl</a></li>
HTML事件处理程序可能存在几个缺点, 1: 用户可能在页面刚加载就触发事件, 而此时事件处理程序没有条件执行 2: 代码与HTML紧密耦合, 试想一下有100个li 每个li都需要一个click事件,突然想改变事件改怎么办?
- DOM0级事件处理程序
通过js指定事件处理程序, 将一个函数赋值给一个事件处理程序的属性,删除事件可以给事件处理程序设置为null
hehe.onclick = function() {
console.log('hehe');
hehe.onclick = null;
};
- DOM2级事件处理程序
DOM2级定义了两个方法用来 添加或删除事件处理程序,addEventListener 添加 和removeEventListener 移除, 他们都接收3个参数, 参数1: 事件名, 参数2: 作为事件处理程序的函数, 参数3: 捕获阶段(true) or 冒泡阶段(false or 空)
hehe.addEventListener('click',function(){
console.log('hehe')
})
addEventListener 添加事件的主要好处是可以添加多个事件,但只能通过removeEventListener函数移除, 并且被移除的函数只能是外部函数, 不能是匿名函数, 另外说明下 addEventListener 的函数无法传参 hehe.addEventListener('click',fn('xxx')),像这种形式 fn会立即执行, 在不许要移除事件处理程序的情况下可以通过bind方式, 但我们需要移除的情况下,可以使用函数表达式的形式结合bind的方式
var hehe = document.getElementById('hehe');
var divClick = document.getElementById('divClick');
function fn(name) {
console.log(name);
}
var fff = fn.bind(hehe, 'lmxxxxx');
console.log(fff == fn);// false
console.log(typeof fff);// function
/*
如果仅仅需要传递参数在并不需要移除的情况下 那么可以使用bind的形式
*/
hehe.addEventListener('click', fn.bind(hehe,'lmx'));//lmx
divClick.onclick = function(){
hehe.removeEventListener('click',fn.bind(hehe,'lmx')); // 无效
}
/*
如果需要移除 可以使用函数表达式的形式 将绑定fff 并移除fff
*/
hehe.addEventListener('click', fff);//lmx lmxxxxx
divClick.onclick = function(){
hehe.removeEventListener('click',fff); // 移除成功
}
- IE事件处理程序
由于ie8及一下,不支持事件捕获, 但有类似于addEventListener的两个方法, attachEvent(增加), detachEvent(删除), 但需要注意的是 attachEvent 参数1 是事件处理程序而不是事件 (onclick 而不是 click) ,而且 参数2 使用匿名函数时 this指向的时window而addEventListener 匿名函数this指向触发事件的元素,另外后添加的事件处理程序先执行(addEventListener 也是)
var hehe = document.getElementById('hehe');
var divClick = document.getElementById('divClick');
function fn(name) {
console.log('name');
}
hehe.attachEvent('onclick',fn)
divClick.onclick = function(){
hehe.detachEvent('onclick',fn)
}
hehe.attachEvent('onclick',function(){
console.log(this === window) //true
})
3.事件对象:
在触发DOM上的某个事件的时候,会产生event对象,这个对象包含着所有与事件有关的信息,虽然所有浏览器都支持,但方式不同, 如火狐的事件方法的参数必须传入event ,而chrome 直接在函数体中使用event就可以了
- DOM的事件对象
无论指定事件的是什么方法, DOM浏览器都会把event对象传入事件处理器中,在事件结束后event对象被销毁,event对象会根据触发事件类型的不同,属性和方法也不同,不过所有事件都会有一些固定属性,和方法
var divClick = document.getElementById('divClick');
var uu = document.getElementById('uu');
divClick.onclick = function(event) {
//是否冒泡
console.log(event.bubbles);
//是否可以取消默认行为
console.log(event.cancelable);
//表示是否调用了event.preventDefault()
console.log(event.defaultPrevented);
//事件阶段 1捕获 2 目标阶段 3冒泡阶段
console.log(event.eventPhase);
//事件的类型
console.log(event.type); //click
//正在执行事件的元素
console.log(event.currentTarget); //divClick
//触发事件的目标元素
console.log(event.target); //divClick
//阻止当前元素的默认事件 如a标签的跳转
event.preventDefault();
console.log(event.defaultPrevented); //true
//阻止事件冒泡 点击divClick后 uu的click不会执行
event.stopPropagation();
};
uu.onclick = function(event) {
/*
当点击uu元素本身的时候触发click时 currentTarget = target 为uu
但当点击uu内部元素divClick 触发click时 click方法冒泡到了uu
此时uu执行click事件,event的currentTarget为 uu 而target 为divClick
*/
console.log(event.currentTarget); //uu
console.log(event.target); //divClick
};
/*
event.stopImmediatePropagation();
不单单会阻止事件冒泡,还会阻止事件分发(在绑定多个click处理器的情况下)
当执行 event.stopPropagation() 会打印 12 13 14
当执行 event.stopImmediatePropagation() 会打印 12
*/
divClick.addEventListener('click', function(event) {
// event.stopPropagation()
event.stopImmediatePropagation();
console.log('12');
});
divClick.addEventListener('click', function(event) {
console.log('13');
});
divClick.addEventListener('click', function(event) {
console.log('14');
});
- IE中的事件对象
ie8及一下不支持通过传参的形式,只能通过window.event,event函数稍有不用
divClick.onclick = function(event){
var e = event || window.event;
console.log(e);
//srcElement 类似target
console.log(e.srcElement === this);
// 类似于 preventDefault()
e.returnValue = false;
//类似于 stopPropagation()
e.cancelBubble = true
}
4.事件类型:
浏览器中可能发生的类型有很多种,不同类型具有不同的信息
- UI事件
UI事件指不一定与用户操作有关,如 load (当页面加载完毕,或者img加载完毕触发) unload(页面卸载,关闭或刷新) abort(页面停止下载) .... 等等
var divClick = document.getElementById('divClick');
var uu = document.getElementById('uu');
//页面加载完成后执行的方法
window.onload = function(){
var img = document.createElement('img')
//图片加载完成
img.onload = function(){
console.log(1);
}
img.src = './loading.png';
divClick.appendChild(img);
}
//调整浏览器大小后触发
window.onresize = function(event){
console.log('resize')
}
/*
滚动滚动条时触发, 如果存在<!DOCTYPE html> 用documentElement
如果不存在用body
*/
window.onscroll = function(event){
console.log(event.target.body.scrollTop);//0
console.log(event.target.documentElement.scrollTop);//滚动值
}
- 焦点事件
在页面获得焦点或者失去焦点时触发, 也可以通过手动调用 element.focus()触发
var input = document.getElementById('fouces');
/*
focus与blur不冒泡
*/
input.onfocus = function(){
console.log('获得焦点')
}
input.onblur = function(){
console.log('失去焦点')
}
/*
这种方式 浏览器无效
*/
// input.onfocusin = function(){
// console.log('获得焦点')
// }
// input.onfocusout = function(){
// console.log('失去焦点')
// }
/*
这种方式可以, 但ie8 不支持需要用兼容写法
*/
input.addEventListener('focusout',function(){
console.log('失去焦点')
})
- 鼠标与滚轮事件
DOM3级定义了9个鼠标事件,在鼠标事件的event对象中保存了一些关于鼠标的信息
var hehe = document.getElementById('hehe');
var divClick = document.getElementById('divClick');
var uu = document.getElementById('uu');
var input = document.getElementById('fouces');
/*
鼠标按下
*/
window.onmousedown = function(event){
/*
clientX 鼠标在视口中位于X轴的坐标 clientY鼠标在视口中位于Y轴的坐标
*/
console.log(event.clientX);
console.log(event.clientY);
/*
pageX 鼠标在页面中位于X轴的坐标 pageY鼠标在页面中位于Y轴的坐标
*/
console.log(event.pageX);
console.log(event.pageY);
/*
screenX 鼠标在屏幕中位于X轴的坐标 screenY鼠标在屏幕中位于Y轴的坐标
*/
console.log(event.screenX);
console.log(event.screenY);
/*
offsetX鼠标在触发元素内部的X轴坐标 offsetY鼠标在触发元素内部的Y轴坐标
新版火狐已经支持
*/
console.log(event.offsetX);
console.log(event.offsetY);
console.log('鼠标按下')
}
/*
鼠标抬起
*/
window.onmouseup = function(){
console.log('鼠标抬起')
}
/*
单击事件,
一次click事件 先触发onmousedown然后onmouseup
最后onclick
*/
window.onclick = function(){
console.log('单击')
}
/*
双击事件
一次dbclick 触发两次onmousedown,onmouseup,onclick
顺序onmousedown,onmouseup,onclick,onmousedown,onmouseup,onclick,ondbclick
*/
window.ondblclick = function(){
console.log('双击')
}
/*
鼠标进入元素触发 不冒泡
*/
hehe.onmouseenter = function(){
console.log('鼠标进入元素')
}
/*
鼠标离开元素触发 不冒泡
*/
hehe.onmouseleave = function(){
console.log('鼠标离开一个元素')
}
/*
鼠标在一个元素上, 在进入另一个元素时触发(包括包含元素),此方法冒泡
在鼠标经过后代元素上会频繁触发
*/
hehe.onmouseover = function(){
console.log('鼠标在一个元素上')
}
/*
鼠标在一个元素上, 在离开这个元素时触发(包括包含元素),此方法冒泡
在鼠标经过后代元素上会频繁触发
*/
hehe.onmouseout = function(){
console.log('鼠标离开一个元素')
}
hehe.onmousemove = function(){
console.log('鼠标在元素上移动')
}
DOM规定了4个属性, 当某个鼠标事件发生时,用来检测确定用户同时按下了其中某几个键
hehe.onclick = function(event){
//是否按下了shift键
console.log(event.shiftKey)
//是否按下了ctrl键
console.log(event.ctrlKey)
//是否按下了alt
console.log(event.altKey)
//是否按下了meta键 window下是windows键
console.log(event.metaKey)
}
触发click事件时event.button 记录了点击的鼠标按钮 0 代表左键 1中间键 2代表又键(最新版都相同), event.detail记录鼠标单击的次数 但各个浏览器都不相同 chrome pc端 会累计相加 模拟phone 恒等于1 ff pc与手机端相同 数字不超过3 ie11 恒等于0
针对onmouseout 与 onmouseover 的event对象中的 relatedTarget属性 , 这个属性 在onmouseout表示 移动到的元素(包括子元素), 在onmouseover表示被离开的元素
hehe.onmouseover = function(){
/*
onmouseover的relateTarget 表示被离开的元素
*/
console.log('onmouseover')
console.log(event.relatedTarget);
}
hehe.onmouseout = function(){
/*
onmouseout的relateTarget 表示移动到的元素
*/
console.log('onmouseout')
console.log(event.relatedTarget)
}
- 键盘与文本事件
用户使用键盘时会触发键盘事件,所有元素都支持三个事件,keydown,keyup,keypress和一个文本事件textInput(ie11,ff不支持),keydown与keypress都是在文本框变化之前触发,keyup在文本框变化之后触发, keydown,keyup,在用户按下键时就触发, 而keypress在用户按下字符键触发包括退格按钮,textInput仅仅在字符键按下触发
keydown,keyup 键码keyCode键码相同 但要是汉字的化 ff, chrome都显示299,ie 按下299 抬起时英文编码, keypress的keyCode, 与keydown,keyup 键码不同, ff中文和英文都恒等于0, ie,chrome中文不显示,英文相同, 而keypress的charCode 与keyCode字符编码相同.
keydown,keyup,keypress的 key属性 否返回按键的字符串如 按下shift 返回''shift.
DOM3级事件中添加了 location属性, 返回1-5数字 0:默认键盘 1:左侧 2右侧 3小键盘 4移动设备 5手柄
- 变动事件
在改变DOM结构时 我们可以为元素添加事件 但是兼容性太好 只列出绝大部分浏览器兼容的事件,DOMSubtreeModified(DOM结构改变就触发),DOMNodeInserted(插入节点触发),DOMNodeRemoved(移除或替换触发),可冒泡
var hehe = document.getElementById('hehe');
var divClick = document.getElementById('divClick');
var uu = document.getElementById('uu');
var input = document.getElementById('fouces');
uu.addEventListener('DOMSubtreeModified', function() {
console.log('DOM结构改变了');
});
uu.removeChild(uu.children[0])
uu.addEventListener('DOMNodeInserted',function(){
console.log('节点插入了')
})
hehe.appendChild(input);//未触发
uu.addEventListener('DOMNodeRemoved',function(){
console.log('节点移除了')
})
hehe.removeChild(input);//未触发
- H5事件
H5列出了浏览器应该支持的所有事件
contextmenu事件(右键菜单)
window.oncontextmenu = function(event){
console.log(1);
//可以阻止右键弹出菜单
event.preventDefault()
}
beforeunload 页面刷新或者关闭页面触发, 此事件内alert confrim等无效,
/*
离开页面或刷新触发 alert等弹窗无效
返回值 为弹窗显示的值 询问客户的字符串
*/
window.onbeforeunload = function(event){
var msg = "确定要离开么";
return msg;
}
DOMContentLoaded 事件 不必等到页面完全加载就可以执行,他在DOM树完成后就可以触发,这跟jq $(document).ready()效果是一样的, js标签之所以放在body的结束标签之前 是因为浏览器解析js时会阻断代码 浏览器执行完js后再接着构建html, 所以书中建议css放在头部, 而js代码放在body结束标签之前
window,addEventListener('DOMContentLoaded',function(){
console.log('1')
})
onreadystatechange事件, 早readyState状态改变时 触发事件,readyState有5个状态, uninitialized(未初始化),loading(正在加载) loaded(加载完毕) interactive(可以操作,但是未加载完全) complete(完成)
如果往返缓存中取得页面, 页面时不会再次执行onload事件的, 所以我们可以使用 pageshow(ie11已支持) 和pagehide, pageshow在onload执行之后执行, 只要页面重新恢复或者页面重新加载时就会触发,.pagehide同理但是在unload之前触发,两个事件都有persisted属性, 在pageshow中 如果时从缓存触发的persisted为true 否则false, pagehide 中, 如果页面进入缓存为true 否则false, (pc端测试ff chrome 都是重新加载, 手机端 测试 是从往返缓存)
haschange事件, 只要URL的参数列表变化 就会触发
- 触摸与手势事件
触摸事件会在手机放在屏幕上面,移动,等触发, touchstart 手指触摸屏幕时触发, touchmove手指在频幕上滑动时触发,会频繁触发, touchend 手指离开屏幕触发, 在移动端touch与click区别, 先触发touchstart -->touchend-->click,click只有在短时间内切并未滑动时触发.
触摸event对象 有额外三个属性 touches(当前屏幕触摸点集合) targetTouches(绑定事件元素上的触摸点集合) changedTouches(触摸事件改变的元素集合)
手势事件电脑不太方便测 先略
5.内存和性能:
在js中事件处理程序的数量, 影响着 页面的整体性能, 每个函数都是对象, 内存中对象越多,性能就越差.
- 事件委托
针对事件处理程序过多, 可以采用事件委托, 实质就是利用事件冒泡. 用一个事件处理程序,管理某一类所有的事件
/*
可以给父元素绑定事件
当子元素触发相同事件时 冒泡到父元素
父元素根据子元素类型 来判断如何执行
*/
uu.onclick =function(event){
console.log(event.target)
}
- 移除事件处理程序
在删除dom元素后, 事件处理程序极有可能不会被释放 , 可以在删除元素之前, 删除方法
6.模拟事件:
- DOM中的事件模拟
document.createEvent(),用来创建event对象并返回 , 这个方法接收一个参数MouseEvent鼠标事件,UIEvent一般ui事件,MutationEvent DOM变动事件 单数为DOM3级事件 复数为DOM2级事件, 模拟事件一般需要三个步骤 创建 --> 注册-->触发,
document.addEventListener("click",function(){
console.log(11);
})
// 创建事件 参数 为要注册的事件类型
var msTest = document.createEvent("MutationEvent")
// 初始化事件 不同类型的事件需要的具体参数也不同 不过前4个参数 基本都需要
// 事件类型, 是否冒泡, 事件是否可以取消, 视图
msTest.initMouseEvent('click',true,true,document.defaultView)
// 触发事件
document.dispatchEvent(msTest) // 11