一、前言
委托(代理)顾名思义就是请别人帮忙做某件事情而达到自己的需求,那么JS中的事件委托又是怎么样的呢?现在我们有公司员工拿快递这样一个需求,要实现这个需求有两个方案:一是公司的每个人都自己去快递点排队取快递;二是快递员把快递都送到前台,前台代为签收,前台可以判断快递是谁的。并且即使公司来了新员工,前台也可以代签。
敲重点!敲重点!…
一、现在职员工的快递可以委托前台代签。现有的DOM节点是有事件的。
二、新入职员工的快递也可以委托前台代签。新添的DOM节点也是有事件的。
二、原理
1、事件冒泡
当一个元素接收到事件的时候,会把它的事件一层一层传递给它的父级,一直到Window
注意:这里传递的仅仅是事件,并不是事件所绑定的事件函数。所以父级如果没有绑定该事件函数,就算传递了事件,也不会有什么表现。
<div id="div1">
<div id="div2"></div>
</div>
<script>
var div1 = document.getElementById("div1");
var div2 = document.getElementById("div2");
div2.onclick = function(){alert(1);};
div1.onclick = function(){alert(2);};//父亲
</script>
两个父子关系的div,然后分别加了点击事件,当我们在div2里面点击的时候,会发现弹出了一次1,接着又弹出了2,这说明点击的时候,不仅div2的事件被触发了,它的父级的点击事件也触发了,这种现象就叫做冒泡。点击了div1,自己父级的点击事件也会被触发。
2、事件委托
1、原理:利用事件冒泡的特性,将本应该注册在子元素上的处理事件注册在父元素上,这样点击子元素时发现其本身没有相应事件就到父元素上寻找作出相应。
2、举例:页面上有这么一个节点树,div>ul>li>a;比如给最里面的a加一个click点击事件,那么这个事件就会一层一层的往外执行,执行顺序a>li>ul>div,有这样一个机制,那么我们给最外面的div加点击事件,那么里面的ul,li,a做点击事件的时候,都会冒泡到最外层的div上,所以都会触发,这就是事件委托,委托它们父级代为执行事件。
3、优势:
(1)减少DOM操作,提高性能。
(2)随时可以添加子元素,添加的子元素会自动有相应的处理事件。
3、实现
1、先来看一个我们最常见的例子,我们想实现的功能是点击li,打印出li里面的内容
<ul id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
<script>
let ul = document.getElementById("ul");
let lis = ul.getElementsByTagName('li');
for(let i=0;i<lis.length;i++){
lis[i].onclick = function(){
console.log(this.innerHTML);
}
}
</script>
看起来每个li的点击事件触发的都是调用同一个函数,但其实不是的
console.log(lis[0].onclick == lis[1].onclick); // false
我们可以得出有多少个li,就有多少个函数在内存中创建并占用着内存,这对内存的开销是巨大的。
解决办法:用事件委托,给ul添加点击事件
<script>
let ul = document.getElementById("ul");
let lis = ul.getElementsByTagName('li');
ul.onclick = function (e) {
e = e || window.event; // 兼容写法
console.log(e.target.innerHTML); // e.target代表当前点击的li
};
</script>
我们给每个li添加点击事件,li本身没有点击事件的函数,利用冒泡原理,它就会点击事件传递给它的父级ul,ul有点击事件的函数就会被触发,通过e.target能够找到真正被点击的那个li,继而可以拿到它的innerHTML。
console.log(lis[0].onclick == lis[1].onclick); // true
我们看到每个li点击事件所触发的函数都是同一个,这样就大大节约了内存空间。
2、前面的是在现有DOM节点下的操作,如果是新增的节点,点击之后也会触发事件吗?
<button>添加</button>
<ul id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
<script>
let btn = document.querySelector('button');
let ul = document.querySelector('ul');
let lis = document.querySelectorAll('li');
let num = 4;
for(let i=0;i<lis.length;i++){
lis[i].onclick = function(){
console.log(this.innerHTML);
}
}
btn.onclick = function(){
num++;
let oli = document.createElement('li');
oli.innerHTML = num;
ul.appendChild(oli);
}
</script>
用传统的方法会发现,新增的li是没有事件的,因为一开始的时候for循环就给现有的li添加完了事件,而子节点在添加的时候,事件并没有添加进去。
如果我们用事件委托的方式
<script>
let btn = document.querySelector('button');
let ul = document.querySelector('ul');
let lis = document.querySelectorAll('li');
let num = 4;
ul.onclick = function (e) {
e = e || window.event; // 兼容写法
console.log(e.target.innerHTML); // e.target代表当前点击的li
};
btn.onclick = function () {
num++;
let oli = document.createElement('li');
oli.innerHTML = num;
ul.appendChild(oli);
}
</script>
点击新增的li节点,也能够触发相应的事件。这就是事件委托的效果,根本不需要去遍历子节点,只要给父级添加事件就行,大大减少了DOM操作。