或叫--事件代理
参考链接或直接叫转自: js中的事件委托或是事件代理详解

1、定义

事件委托:事件委托利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事。
‘事件代理’就是把原本需要绑定的事件委托给父元素,让父元素负责事件监听。


事件委托就是利用事件冒泡原理,将子元素要绑定的事件委托给父元素,让父元素负责事件监听,指定父元素为事件处理程序,那么父元素就可以管理某一类型的所有事件。

网上的经典例子:取快递

有三个同事预计会在周一收到快递。为签收快递,有两种办法:一是三个人在公司门口等快递;二是委托给前台MM代为签收。前台MM收到快递后,她会判断收件人是谁,然后按照收件人的要求签收,甚至代为付款。这种方案还有一个优势,那就是即使公司里来了新员工(不管多少),前台MM也会在收到寄给新员工的快递后核实并代为签收。

注意!这里其实还有2层意思的:

第一,现在委托前台的同事是可以代为签收的,即程序中的现有的dom节点是有事件的;

第二,新员工也是可以被前台MM代为签收的,即程序中新添加的dom节点也是有事件的

2、为什么要用事件委托

如果有很多同样的标签要使用同样的事件,如果用for循环则性能很差。访问dom的次数越多,引起浏览器重绘和重排的次数也就越多,延长整个交互事件。
如果使用事件委托,就会将所有的操作放到js程序中,与dom的操作只需要交互一次,提高性能。

且如果给100个li绑定click事件,每个函数都是一个对象,会占用内存。如果写100个click函数,占用内存。如果用事件委托,那么只用对父级元素进行操作,只需要一个内存空间。

3、事件委托的原理

利用事件的冒泡原理。
例如:有一个dom节点树:div>ul>li>a,如果给最外层的div加点击事件,那么里面的ul,li,a做点击事件时,都会冒泡到最外层,所以都会触发,这就是事件委托,委托他们的父级代为执行事件。

4、事件委托的实现

第一:看不用事件委托的代码,对每一个li创建了一个click事件,性能差

<ul id="ulll">
        <li>111</li>
        <li>222</li>
        <li>333</li>
        <li>4444</li>
    </ul>
var oui = document.getElementById("ulll");
    var ali = oui.getElementsByTagName("li");
    for (var i = 0; i < ali.length; i++) {
        ali[i].onclick = function () {

          console.log(this.innerText)
        }
    }

第二:看使用了事件委托

// event对象提供target属性,可以返回事件的目标节点,即
    //target可以表示为当前的事件操作的dom,但不是真正操作dom
    //标准浏览器用ev.target,ie浏览器用event.srcElement
var oui = document.getElementById("ulll");
    oui.onclick = function (ev) {
        var ev = ev || window.event;
        var target = ev.target || window.srcElement;
        if (target.nodeName.toLowerCase() == 'li') {
            console.log(target.innerText);
        }
    }

第三:,上面是li是同一个方法,如果li不是同一个方法,使用普通方法:

var add=document.getElementById("add");
    var remove=document.getElementById("remove");
    var move=document.getElementById("move");
    var select=document.getElementById("select");
    add.onclick=function(){
        console.log(this.value);
    }
    remove.onclick=function(){
        console.log(this.value);
    }
    move.onclick=function(){
        console.log(this.value);
    }
    select.onclick=function(){
        console.log(this.value);
    }

第***委托的方法:

var obox = document.getElementsByClassName("box")[0];
    obox.onclick = function (ev) {
        var ev = ev || window.event;
        var target = ev.target || ev.srcElement;
        if (target.nodeName.toLowerCase() == 'input') {
            switch (target.id) {
                case 'add':
                    // 不能用this
                    console.log(target.value);
                    break;
                case 'remove':
                    console.log(target.value);
                    break;
                case 'move':
                    console.log(target.value);
                    break;
                case 'select':
                    console.log(target.value);
                    break;
            }
        }
    }

第五:有新增的节点咋办呢?新增的节点怎么给它添加事件呢?
先看正常的添加事件

     var btn = document.getElementById("btn");
    var oui = document.getElementById("ulll");
    var ali = document.getElementsByTagName("li");
    function mHover(){
        console.log(ali);
        for(var i=0;i<ali.length;i++){

            ali[i].onmouseover=function(){
                this.style.backgroundColor='red';
            };
            ali[i].onmouseout=function(){
                this.style.backgroundColor='yellow';
            }

        }

    }
    var num=4;
    mHover();
    btn.onclick=function(){
        num++;
        var oli=document.createElement('li');
        oli.innerHTML=111*num;
        oui.appendChild(oli);
        mHover();
    }

第六:事件委托方法:

var num = 4;

    oui.onmouseover = function (ev) {
        var ev = ev || window.event;
        var target = ev.target || ev.srcElement;
        if (target.nodeName.toLowerCase() == 'li') {
            target.style.backgroundColor = 'red';
        }
    }
    oui.onmouseout = function (ev) {
        var ev = ev || window.event;
        var target = ev.target || ev.srcElement;
        if (target.nodeName.toLowerCase() == 'li') {
            target.style.background = "yellow";
        }

    };

    //添加新节点
    btn.onclick = function () {
        num++;
        var oli = document.createElement('li');
        oli.innerHTML = 111 * num;
        oui.appendChild(oli);
    };

第七:

<ul id="test">
        <li>
            <p>11111111111</p>
        </li>
        <li>
            <div>
                22222222
            </div>
        </li>
        <li>
            <span>3333333333</span>
        </li>
        <li>4444444</li>
    </ul>
 // 现在给一个场景 ul > li > div > p,div占满li,
    // p占满div,还是给ul绑定时间,
    // 需要判断点击的是不是li(假设li里面的结构是不固定的)
    // ,那么e.target就可能是p,也有可能是div,
    // 这种情况你会怎么处理呢?

    // 点击到里面的事件时,直接指定到li上
    var oui=document.getElementById('test');
    oui.addEventListener('click',function(ev){
        var target=ev.target;
        while(target!=oui){
            if(target.tagName.toLowerCase()=='li'){
                console.log('li click');
            }
            target=target.parentNode;
        }

    })

适合用事件委托的事件:click、mousedown,mouseup,keydown,keyup,keypress
不适合的就有很多了,举个例子,mousemove,每次都要计算它的位置,非常不好把控,
focus,blur之类的,本身就没用冒泡的特性,自然就不能用事件委托了。