事件基础、委托

1、绑定事件

  • 元素天生具有事件
  • JS绑定事件的实质是绑定事件的处理函数(反馈)
  • 事件 + 事件的反馈 = 前端交互、交互体验(前端的核心)

(1)绑定事件处理程序 = 事件处理函数

onclick = funcion(){//事件反馈}

(2)事件源、句柄、事件句柄

oBtn.onclick = function() {
    // 处理逻辑
}

事件源:oBtn
句柄:oBtn.onclick
事件句柄:oBtn.onclick = function() {...}

2、绑定事件处理函数的方法

<button>点击</button>
var oBtn = document.getElementsByTagName('button')[0];

// 1、elem.onclick = function(){}
// 同一个元素只能绑定一个onclick事件,多个会覆盖
oBtn.onclick = function() {
    this -> oBtn;
}

// 2、elem.addEventListener(事件类型, 事件处理函数, false); - IE9以下不支持,W3C规范
// 同一个元素可以绑定多个事件处理函数
oBtn.addEventListener('click', function(){
    this -> oBtn;
}, false);

oBtn.addEventListener('click', function(){
    this -> oBtn;
}, false);

// 以下示例为同一个事件处理函数
oBtn.addEventListener('click', test, false);
oBtn.addEventListener('click', test, false);
// 只会输出1次1,因为 test 为同一个事件处理函数

function test() {
    console.log(1);
}

// 3、elem.attachEvent(事件类型, 事件处理函数)
// IE8及以下的绑定方法
// 绑定几次就打印几次
oBtn.attachEvent('onclick', function(){
    this -> window
    // 只能用 oBtn

    // 解决如下: call 改变this指向
    test.call(oBtn);
});

// 示例
oBtn.attachEvent('onclick', test);
oBtn.attachEvent('onclick', test);
// 输出2次1

function test() {
    console.log(this);
}

3、绑定事件封装

// 事件封装
function addEvent(el, type, fn) {
    if (el.addEventListener) {
        // W3C
        el.addEventListener(type, fn, false);
    } else if (el.attachEvent) {
        // IE8及以下
        el.attachEvent('on' + type, function(){
            fn.call(el);
        });
    } else {
        // 最低版本
        el['on' + type] = fn;
    }
}

// 使用
addEvent(oBtn, 'click', function(){
    console.log(1);
});

4、解除事件绑定

element.onclick = null / false;
element.removeEventListener('click', test, false);
element.detachEvent('onclick', test);

// 封装
function removeEvent(el, type, fn) {
    if (el.addEventListener) {
        el.removeEventListener(type, fn, false);
    } else if (el.attachEvent) {
        el.detachEvent('on' + type, fn);
    } else {
        el['on' + type] = null;
    }
}

5、事件冒泡与捕获

(1)事件冒泡

  • addEventListener 第三个参数为false
  • 点击子元素,由子元素一层一层的传递给父元素,事件源先执行,然后在一层一层执行
  • p -> div -> body -> html -> document

(2)事件捕获

  • addEventListener 第三个参数为true
  • 点击子元素,由父级元素一层一层的传递给子元素,事件先不执行,找到顶层父级往下一层一层执行,最后执行事件源
  • document -> html -> body -> div -> p

(3)顺序

  • 如果事件源同时绑定多个事件:先捕获,后冒泡
  • 事件源不存在冒泡与捕获,按照绑定顺序执行

(4)无冒泡与捕获

  • focus、blur、change、submit、reset、select 无冒泡捕获
  • IE 无捕获

(5)示例

<div class="outer">
    <p class="inner">Click me!</p>
</div>

<script type="text/javascript">
    var outer = document.getElementsByClassName('outer')[0];
    var inner = document.getElementsByClassName('inner')[0];

    outer.addEventListener('click', function() {
        console.log('bubbleOuter');
    }, false);
    inner.addEventListener('click', function() {
        console.log('bubbleInner');
    }, false);

    outer.addEventListener('click', function() {
        console.log('outer');
    }, true);
    inner.addEventListener('click', function() {
        console.log('inner');
    }, true);
</script>

// 执行结果
outer
bubbleInner
inner
bubbleOuter

6、取消冒泡

// W3C: e.stopPropagation(); -> 存放在Event.prototype
// IE: e.cancelBubble = true;
function cancelBubble(e) {
    // 兼容IE8,IE8事件存放在 window.event
    var e = e || window.event;
    if (e.stopPropagation) {
        e.stopPropagation();
    } else {
        e.cancelBubble = true;
    }
}

7、取消默认事件

// 取消默认事件
document.oncontextmenu = function(e) {
    var e = e || window.event;
    // return false; // 兼容性最好,只能用于句柄事件绑定
    // W3C: e.preventDefault(); // IE9 不兼容
    // e.returnValue = false; // IE9以下
}

// 封装
function stopEvent(e) {
    var e = e || window.event;
    if (e.preventDefault) {
        // W3C
        e.preventDefault();
    } else {
        // IE9以下
        e.returnValue = false;
    }
}

8、a标签事件阻止

<!-- void(0) = return 0 -->
<a href="javascript:void(0)"></a>
<a href=":;"></a>
<a href="#"></a>

<script type="text/javascript">
    var a = document.getElementsByClassName('a')[0];
    a.onclick = function(e) {
        e.preventDefault();
    }
</script>

9、事件流

  • 定义:描述从页面中接受事件的顺序,和冒泡、捕获相关
  • 微软IE 提出的事件冒泡流(Event Bubbling)
  • 网景(Netscape) 提出的事件捕获流(Event Capturing)
  • 事件流三个阶段:事件捕获阶段、处理目标阶段、事件冒泡阶段
  • 事件冒泡:处理目标阶段 》 事件冒泡阶段
  • 事件捕获:事件捕获阶段 》 处理目标阶段

10、DOM级别对应事件

DOM0 定义 onXXX(onclick) 的两种写法
DOM1 没有定义事件模型
DOM2 
    定义addEventListener(3个参数) -> W3C规范
            removeEventListener
            attachEvent(2个参数)
            detachEvent

11、事件对象

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>DOM1</title>
<style type="text/css">
    .wrapper {
        width: 100px;
        height: 100px;
        background-color: orange;
    }
</style>
<body>

<div class="wrapper"></div>

<script type="text/javascript">
var wrapper = document.getElementsByClassName('wrapper')[0];
wrapper.onclick = function(e) {
    // e 事件触发之后的详细信息
    // Event 对象 = new MouseEvent()
    // IE 保存在 window.event
    var e = e || window.event; // 兼容性
    console.log(e);

    // 事件源对象: target、srcElement
    // 获取事件源:FF 只有target,IE 只有srcElement
    var tar = e.target || e.srcElement; // 兼容性写法
}
</script>
</body>
</html>

12、事件委托,事件代理

需求:给li绑定事件,点击输出下标
解决:把子元素的事件绑定到父元素上

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>DOM1</title>
<style type="text/css">
    .wrapper {
        width: 100px;
        height: 100px;
        background-color: orange;
    }
</style>
<body>
    <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
        <li>6</li>
        <li>7</li>
        <li>8</li>
        <li>9</li>
        <li>10</li>
    </ul>
<script type="text/javascript">
var oList = document.getElementsByTagName('ul')[0],
    oLi = document.getElementsByTagName('li');

// 利用冒泡机制的 事件委托
oList.onclick = function(e) {
    var e = e || window.event, // 事件兼容性
        tar = e.target || e.srcElement; // 事件源兼容性

    // 获取内容
    console.log(this); // ul..ul
    console.log(tar); // li..li
    console.log(tar.innerText); // li的文本

    // 获取下标
    // Array.prototype.indexOf.call(DOM对象集合, 当前事件源)
    var index = Array.prototype.indexOf.call(oLi, tar);
    console.log(index);
}
</script>
</body>
</html>