学习时间:1小时复习,2小时学新的。
让你和页面对话
事件知识
W3School 事件
- onload 和 onunload 事件(进入和离开页面)
- onchange 事件(与输入字段验证结合使用)
- onmouseover 和 onmouseout 事件(当用户将鼠标移至 HTML 元素上或移出时触发某个函数)
- onmousedown, onmouseup 以及 onclick 事件(点击)
以上几个事件的详细解释见 这里
MDN 事件
《day17-18进一步学习JS第二部分》已看。略。
HTML DOM Text 对象
https://www.w3school.com.cn/jsref/dom_obj_text.asp
HTML DOM Button 对象
https://www.w3school.com.cn/jsref/dom_obj_pushbutton.asp
用 JavaScript 操作 DOM 样式
JavaScript HTML DOM - 改变 CSS
语法:
document.getElementById(id).style.property = new style
实例:
document.getElementById("p2").style.color = "blue";
HTML DOM Style 对象
在这里查找属性
javascript 动态修改css样式方法汇总(四种方法)
前置代码:<input id="btnB" type="button" name="btnLogin" value="登录" class="style1" />
一、obj.style.backgroundColor是内联修改。内联修改的优先级高于外联修改,可能会造成不想要的结果。比如说,外联css里有个hover改变背景颜色,新建的内联背景样色让hover失效。
function changeStyle1() {
var obj = document.getElementById("btnB");obj.style.backgroundColor= "black";}
二、obj.style.cssText也是内联修改,效果和“一”一样。缺陷一样,只是写的代码不同。
function changeStyle2() {
var obj = document.getElementById("btnB");obj.style.cssText = " display:block;color:White";}
三、.setAttribute()这个是修改外联内容,不会造成上述缺陷。有两个变量,要把第一个变量改成第二个变量。
function changeStyle3() {
var obj = document.getElementById("btnB");//obj.className = "style2";obj.setAttribute("class", "style2");
}
- 修改类名还有另一种方法:
obj.className = "style2";
四、setAttribute()的另一种用用法。即直接把href的引用改了,实现换肤。(注意,这里obj变量的具体引用和“三”不同,)
<link href="css1.css" rel="stylesheet" type="text/css" id="css"/>
function changeStyle4() {
var obj = document.getElementById("css");obj.setAttribute("href","css2.css");}
综上
用这个不容易出问题:
.setAttribute("class", "style2");
cssText的用法以及特点
- 即上面的“二”
- cssText 的本质就是设置 HTML 元素的 style 属性值。
cssText的使用优势:
对比“一”,减少浏览器开销。一行完成“一”中需要写多行的任务。
(“一”这种通过JS来覆写对象的样式是比较典型的一种销毁原样式并重建的过程,这种销毁和重建,都会增加浏览器的开销。)
例:
var element= document.getElementById(“id”);
element.style.width=”20px”;
element.style.height=”20px”;
element.style.border=”solid 1px red”;
element.style.cssText=”width:20px;height:20px;border:solid 1px red;”;
但是,这样会有一个问题,会把原有的cssText清掉,比如原来的style中有’display:none;’,那么执行完上面的JS后,display就被删掉了。
为了解决这个问题,可以采用cssText累加的方法:
Element.style.cssText += ‘width:100px;height:100px;top:100px;left:100px;’
- 上面cssText累加的方法在IE中是无效的。(在某些浏览器中(比如 Chrome),你给他赋什么值,它就返回什么值。在 IE 中则比较痛苦,它会格式化输出、会把属性大写、会改变属性顺序、会去掉最后一个分号。)
可以在前面添加一个分号来解决这个问题(这样在IE中就有效啦!):
Element.style.cssText += ‘;width:100px;height:100px;top:100px;left:100px;’
- 如果前面有样式表文件写着 div { text-decoration:underline; },这个会被覆盖吗?不会!因为它不是直接作用于 HTML 元素的 style 属性。(这个问题在上面的四种方法中的“一”和“二”提到啦!)
综上
用这个方便:
Element.style.cssText += ‘width:100px;height:100px;top:100px;left:100px;’
JavaScript之ClassName属性学习
- 在前面的style属性学习中,知道了通过style属性可以控制元素的样式,从而实现了行为层通过DOM的style属性去干预变现层显示的目地,但是这种就是不好的,而且为了实现通过DOM脚本设置的样式,你就不得不花时间去研究JavaScript函数,去寻找对应修改和设置样式的有关语句。而且每添加或修改js脚本的代码量远大于我们修改css样式的代码量。所以与其使用DOM直接改变莫个元素的样式,不如通过JavaScript代码去更新这个元素的class属性。(对照上面的方法“四”)
下面通过代码来比较这两种方式的差别:
function setStyleHeaderSiblings() {
if (!checkCompatibility()) return; //check compatibilityvar heads = document.getElementsByTagName("h1");var ele; //defines a element for receive;for (var i = 0; i < heads.length; i++) {
ele = getNextElement(heads[i].nextSibling);ele.style.fontWeight = "bold";ele.style.fontSize = "1.2em";}
}
function setStyleHeaderSiblings() {
if (!checkCompatibility()) return; //check compatibilityvar heads = document.getElementsByTagName("h1");var ele; //defines a element for receive;for (var i = 0; i < heads.length; i++) {
ele = getNextElement(heads[i].nextSibling);ele.className="change";}
css样式表
.change{
font-weight:bold;
font-size:1.2em;
}
- 假设我们这个需要给这个效果加上一个background-color:blue;上面这种做法需要在js里面添加代码:
ele.style.backgroundColor="blue";
- 而第二种做法则只要在样式表里加一个样式就可以实现,而且实现了样式与行为的分离,还减少了我们的代码量,所以在后期需求不确定的情况下,建议第二种方法来动态的修改元素的样式。
- 但是通过className设置元素的样式也有一个缺陷,那就是通过className设置元素的class属性时将替换(而不是追加)该元素原有的class属性,在实际开发中往往很多时候我们需要追加class;
所以根据这个需要我们可以自定义一个方法来实现追加className的效果(即为元素追加类名的效果)代码如下:
function addClass(element,value) {
if (!element.className) //检查className属性值是否为null{
element.className = value;} else {
newClassName = element.className;//这个变量不用声明吗?我觉得应该声明。newClassName += " "; //这句代码追加的类名分开newClassName += value;element.className = newClassName;//即一个元素有多个类名。}
}
- !表示逻辑非
- 类名要加引号哦。
再次理解 JavaScript 的事件机制,并了解事件代理
day17-18进一步学习JS第二部分中提到:JS中的事件机制不同于一些其他语言。在此进一步介绍。
初步理解JS的事件机制
一、事件流(捕获,冒泡)
为了兼容更多的浏览器,非特殊情况一般我们都是把事件添加到在事件冒泡阶段。
二、事件处理程序
DOM0级事件处理程序
1 var btn5 = document.getElementById('btn5');
2 btn5.onclick=function(){
3 console.log(this.id);//btn5
4 };
基于DOM0的事件,对于同一个dom节点而言,只能注册一个,后边注册的同种事件会覆盖之前注册的。利用这个原理我们可以解除事件,btn5.onclick=null;
其中this就是绑定事件的那个元素;
以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理;
DOM2级事件处理程序
DOM2支持同一dom元素注册多个同种事件,事件发生的顺序按照添加的顺序依次触发(IE是相反的)。
DOM2事件通过addEventListener和removeEventListener管理
两个方法都一样接收三个参数,第一个是要处理的事件名,第二个是事件处理程序,第三个值为false时表示在事件冒泡阶段调用事件处理程序,一般建议在冒泡阶段使用,特殊情况才在捕获阶段;
注意:通过addEventListener()添加的事件处理程序只能用removeEventListener()来移除,并且移除时传入的参数必须与添加时传入的参数一样。
例:
1 var btn2 = document.getElementById('btn2');
2 var handlers = function () {
3 console.log(this.id);
4 };
5
6 btn2.addEventListener('click',handlers,false);
7
8 btn2.removeEventListener('click',handlers.false);
IE事件处理程序
IE用了attachEvent(),detachEvent(),接收两个参数,事件名称和事件处理程序,通过attachEvent()添加的事件处理程序都会被添加到冒泡阶段,所以平时为了兼容更多的浏览器最好将事件添加到事件冒泡阶段,IE8及以前只支持事件冒泡;
1 var btn3 = document.getElementById('btn3');
2 var handlers2=function(){
3 console.log(this===window);//true,注意attachEvent()添加的事件处理程序运行在全局作用域中;
4 };
5 btn3.attachEvent('onclick',handlers2);
跨浏览器事件处理程序
我没看懂:https://www.cnblogs.com/lazychen/p/5664788.html
- 在同一个对象上注册事件,并不一定按照注册顺序执行,冒泡或捕获模式会影响其被触发的顺序;
三、事件对象
我想先不管IE。
四、事件委托
- 因为把事件绑定到了父节点上,因此省了绑定事件。就算后面新增的子节点也有了相关事件,删除部分子节点不用去销毁对应节点上绑定的事件
- 父节点是通过event.target来找对应的子节点的。(事件处理程序中的this值始终等于currentTarget的值,指向的是绑定到的那个元素)
js中的事件委托或是事件代理详解(https://www.cnblogs.com/liugang-vip/p/5616484.html)
为什么要用事件委托和原理:https://www.cnblogs.com/liugang-vip/p/5616484.html
简而言之:利用冒泡原理优化性能。
实现方法:利用事件对象(target属性始终是事件刚刚发生的元素的引用。)
- Event对象提供了一个属性叫target,可以返回事件的目标节点,我们成为事件源,也就是说,target就可以表示为当前的事件操作的dom,但是不是真正操作dom,
我简化的版本:
window.onload = function(){
var oUl = document.getElementById("ul1");oUl.onclick = function(ev){
if(ev.target.nodeName.toLowerCase() == 'li'){
alert(123);alert(ev.target.innerHTML);}}
}
原文中版本(考虑了IE浏览器):
window.onload = function(){
var oUl = document.getElementById("ul1");oUl.onclick = function(ev){
var ev = ev || window.event;var target = ev.target || ev.srcElement;if(target.nodeName.toLowerCase() == 'li'){
alert(123);alert(target.innerHTML);}}
}
上面的例子是说li操作的是同样的效果,要是每个li被点击的效果都不一样,那么用事件委托还有用吗?
再写一层if就好了!
<div id="box"><input type="button" id="add" value="添加" /><input type="button" id="remove" value="删除" /><input type="button" id="move" value="移动" /><input type="button" id="select" value="选择" /></div>
window.onload = function(){
var oBox = document.getElementById("box");oBox.onclick = function (ev) {
if(ev.target.nodeName.toLocaleLowerCase() == 'input'){
switch(target.id){
case 'add' :alert('添加');break;case 'remove' :alert('删除');break;case 'move' :alert('移动');break;case 'select' :alert('选择');break;}}}}
现在讲的都是document加载完成的现有dom节点下的操作,那么如果是新增的节点,新增的节点会有事件吗?也就是说,一个新员工来了,他能收到快递吗?
插入一条tip:
- 事件对象属性e.target和this的区别:
- this返回的是绑定事件的对象(元素)
- e.target返回的是触发事件的对象(元素)
事件对象可以利用冒泡原理优化性能,而this不行。
这是一般的做法,但是你会发现,新增的li是没有事件的,说明添加子节点的时候,事件没有一起添加进去,这不是我们想要的结果,那怎么做呢?一般的解决方案会是这样,将for循环用一个函数包起来,命名为mHover,如下:
window.onload = function(){
var oBtn = document.getElementById("btn");var oUl = document.getElementById("ul1");var aLi = oUl.getElementsByTagName('li');var num = 4;function mHover () {
//鼠标移入变红,移出变白for(var i=0; i<aLi.length;i++){
aLi[i].onmouseover = function(){
this.style.background = 'red';};aLi[i].onmouseout = function(){
this.style.background = '#fff';}}}mHover ();//添加新节点oBtn.onclick = function(){
num++;var oLi = document.createElement('li');oLi.innerHTML = 111*num;oUl.appendChild(oLi);mHover ();};}
虽然功能实现了,看着还挺好,但实际上无疑是又增加了一个dom操作,在优化性能方面是不可取的,那么用事件委托的方式,能做到优化吗?
window.onload = function(){
var oBtn = document.getElementById("btn");var oUl = document.getElementById("ul1");var aLi = oUl.getElementsByTagName('li');var num = 4;//事件委托,添加的子元素也有事件oUl.onmouseover = function(ev){
if(ev.target.nodeName.toLowerCase() == 'li'){
ev.target.style.background = "red";}};oUl.onmouseout = function(ev){
if(ev.target.nodeName.toLowerCase() == 'li'){
ev.target.style.background = "#fff";}};//添加新节点oBtn.onclick = function(){
num++;var oLi = document.createElement('li');oLi.innerHTML = 111*num;oUl.appendChild(oLi);};}
看,上面是用事件委托的方式,新添加的子元素是带有事件效果的,我们可以发现,当用事件委托的时候,根本就不需要去遍历元素的子节点,只需要给父级元素添加事件就好了,其他的都是在js里面的执行,这样可以大大的减少dom操作,这才是事件委托的精髓所在。
特例:
现在给一个场景 ul > li > div > p,div占满li,p占满div,还是给ul绑定事件,需要判断点击的是不是li(假设li里面的结构是不固定的),那么e.target就可能是p,也有可能是div,这种情况你会怎么处理呢?
加一个while即可。
var oUl = document.getElementById('test');oUl.addEventListener('click',function(ev){
var target = ev.target;while(target !== oUl ){
if(target.tagName.toLowerCase() == 'li'){
console.log('li click~');break;}target = target.parentNode;}})
- 那什么样的事件可以用事件委托,什么样的事件不可以用呢?
适合用事件委托的事件:click,mousedown,mouseup,keydown,keyup,keypress。
不适合的就有很多了,举个例子,mousemove,每次都要计算它的位置,非常不好把控,在不如说focus,blur之类的,本身就没用冒泡的特性,自然就不能用事件委托了。
关于JS事件冒泡与JS事件代理(事件委托)
一、阻止冒泡(冒泡导致本不想触发的父元素的点击事件被触发)
document.getElementById('parent').οnclick=function () {
console.log(this.getAttribute('data-id'));};document.getElementById('child').οnclick=function (ev) {
var e = ev||window.event;//<span style="color:#FF0000;">IE中event可以通过window.event随时取到,而其他浏览器需要通过参数传递</span>console.log(this.getAttribute('data-id'));stopPropagation(e);};function stopPropagation(e) {
if (e.stopPropagation) {
e.stopPropagation();} else {
e.cancelBubble = true;}
二、事件委托
var ul = document.getElementById('parentUl');ul.οnclick=function (event) {
var e = event||window.event,source = e.target || e.srcElement;//target表示在事件冒泡中触发事件的源元素,//在IE中是srcElementif(source.nodeName.toLowerCase() == "li"){
//判断只有li触发的才会输出内容alert(source.innerHTML);}stopPropagation(e); //阻止继续冒泡};function addElement() {
var li = document.createElement('li');li.innerHTML="我是新孩子";ul.appendChild(li);
}
完成事件委托练习,tip
- id属性不在style里,直接.id即可。