原文链接:http://www.w3cfuns.com/notes/17398/8062de2558ef495ce6cb7679f940ae5c.html

学js,不懂事件机制,基本可以说学了js,就是白学。
本人看了很多js相关书籍,评价一本说讲得好不好,我主要看两块儿,一块儿是js面向对象讲得怎么样,另一块儿就是这个事件机制这块儿。面向对象按下不表,这里就详细说说事件机制。
事件这个东西可以说js中核心之一。为啥如此重要,因为js是一门事件驱动的语言。

说说本文的结构。(真的好长,又不想写成一个系列,希望你坚持看下去{:5_353:} )
先说说怎么绑定事件,
说到事件,就得说说冒泡机制,
说到冒泡机制,就必须说委托,
说到事件,就得提提自定义事件,
说到自定义事件,就不得不说观察者模式和发布订阅模式。
很多书认为观察者模式和发布订阅模式是一回事情,也有不这样认为的,我个人倾向同意后一种观点。
其实这两个模式(或说同个一个模式的两个名字)本质上就是一种委托(且看我的想法)。
一、监听事件
绑定事件,ie有attachEvent,w3c有addEventListener,
解除绑定,ie有detachEvent,w3c有removeEventListener
jQuery做了很好的封装,api也比较多,bind,live,delegate,on等等,其不同api也所区别,由于版本问题,个别api也有所区别。我个人一般都用同一个,要么用bind,要么用on。
要自己封装也可以,下面的代码是《js高设》中的,

HTML 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

var EventUtil ={
    addHandler: function(element, type, handler){
        //w3c
        if(element.addEventListener){
            element.addEventListener(type, handler, false);
        }
        //ie
        else if(element.attachEvent){
            element.attachEvent('on' + type,handler);
        }else{
            element['on' + type] = handler;
        }
    },
    removeHandler: function(){
        if(element.removeEventListener){
            element.removeEventListener(type, handler, false);
        }else if(element.detachEvent){
            element.detachEvent('on' + type, handler);
        }else{
            element['on' + type] = null;
        }
    }
}

使用如下

HTML 代码
1
2
3
4
5
6
7
8
9

var btn = document.getElementById('mybtn');
var handler = function(){
    alert("111")
};
//add click
EventUtil.addHandler(btn, 'click', handler);
//remove click
EventUtil.removeHandler(btn, 'click', handler);

二、冒泡机制和捕获机制
ie用的是冒泡机制,w3c都支持的。
element.addEventListener(type, handler, false)这句代码里的第三个参数就是说明是否使用捕获机制,一般我们都使用冒泡的。至于捕获机制,我个人目前尚未遇到具体的使用场景。
那啥叫冒泡机制和捕获机制呢?
看这样一种情况,父元素是ul,子元素是li,两个元素都绑定点击事件。如果我们点击了li,li的点击事件当然被触发了,那么ul是不是也触发了呢,答案是肯定的。那么就涉及到一个问题。谁先触发的问题。
冒泡机制是指先触发子元素的click事件,然后再触发父元素的click事件,是由内向外的。我觉得冒泡这个名字不好,冒泡给人的感觉是由下向上传播的感觉的。这个是根据dom树来的,不是太直观。是不是叫波纹机制比较好呢,因为在中间激起波纹,是由内向外传播的。
捕获机制就是从外向内传播的(捕获有一种“收网”的意味,或者说,捕获是“抓”,你想,用手抓自然就是由外向内嘛)。先触发父节点的点击事件,然后触发子节点的点击事件。
示例1:ie 678下,点击li触发,弹出顺序是li,ul,document

代码片段 1效果预览
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

<html>
<body>
<ul>
    <li ></li>
</ul>
<script>
var ul = document.getElementsByTagName('ul')[0];
var li = document.getElementsByTagName('li')[0];
document.attachEvent('onclick',function(){alert('document')})
ul.attachEvent('onclick',function(){alert('ul')});
li.attachEvent('onclick',function(){alert('li')});
</script>
</body>
</html>

示例2:chrome下,点击li触发,弹出顺序是document,ul,li

代码片段 2效果预览
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

<!doctype html>
<html>
<head>
<style>
ul{
background : gray;
padding : 20px;
}
ul li{
background : green;
}
</style>
</head>
<body>
<ul>
    <li ></li>
</ul>
<script>
var ul = document.getElementsByTagName('ul')[0];
var li = document.getElementsByTagName('li')[0];
document.addEventListener('click',function(){alert('document')},true);//第三个参数为true使用捕获
ul.addEventListener('click',function(){alert('ul')},true);
li.addEventListener('click',function(){alert('li')},true);
</script>
</body>
</html>

至于如何阻止冒泡,如何阻止默认事件这里不提了。《js高设》也有封装,jquery也有e.stopPropagation和e.preventDefault();
三、事件委托
本文核心内容开始了。

说委托之前先来算算一道题94*5等于多少?
so easy,有两种算法,第一种(90+4)*5=90*5+4*5=450+20=470。这个可以说是正常思维。
第二种是(100-6)*5=500-30=470。从心理感觉上来看,还是第二种快些。
为啥说js说得好好的,突然算题呢。这里说这个事儿,主要是讲思维的问题。也许我们第一直觉会用第一种方式来算。然而一旦我们知道了第二种方式,速度就会快了起来。说要算94*5直觉想不起来,如果要算99*5,几乎所有人都会用第二种方式来算。
本文剩下的内容讲得东西都是类似上面的第二种方式。因为违反直觉的。可一旦我们懂了,其实没啥了不起的。就像你一旦知道,点蚊香其实不用掰开(两个绕在一起的),直接点着其中一头,第二天自动会剩下另一半(如果不好使说明那个蚊香不是好蚊香)这个道理是一样的。(题外话,以前我一直拿这句话对付问我一些刁钻问题的面试官,嘿嘿)。

废话不说了,说委托。
啥是委托呢?从字面意思上讲,就是本来交给你办的事,你不去自己办,而是让别人办。生活中比较简单例子就是,我们网购,快递送到公司传达室,然后我们再去传达室去取一个道理。
js委托是啥呢,因为冒泡机制,既然点击子元素时,也会触发父元素的点击事件。那么我们为啥不把点击子元素的事件要做的事情,写到父元素的事件里呢?是不是有点类似500-30的道理呢?谁第一想出来的,真叫人佩服。
先看例子(百度一大把的几乎都拿这个例子)
示例3:w3c下使用

HTML 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

<!doctype html>
<html>
<body>
<ul>
    <li ></li>
    <li ></li>
    <li ></li>
    <li ></li>
</ul>
<script>
var ul = document.getElementsByTagName('ul')[0];
ul.addEventListener('click',function(e){
console.dir(e.target.tagName)
if(e.target.tagName=='LI'){
        alert('li');
}else{
        alert('outside of li')
}
},false);
</script>
</body>
</html>

代码片段 3效果预览
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

<style>
ul{
background : gray;
padding : 20px;
}
ul li{
background : green;
margin :5px;
}
</style>
</head>
<body>
<ul>
    <li ></li>
    <li ></li>
    <li ></li>
    <li ></li>
</ul>
<script>
var ul = document.getElementsByTagName('ul')[0];
ul.addEventListener('click',function(e){
console.dir(e.target.tagName)
if(e.target.tagName=='LI'){
        alert('li');
}else{
        alert('outside of li')
}
},false);
</script>
</body>
</html>

示例4 li的mouseover和mouseout事件委托于父节点

HTML 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

<!doctype html>
<html>
<head>
</head>
<body>
<ul>
    <li ></li>
    <li ></li>
    <li ></li>
    <li ></li>
</ul>
<script>
var ul = document.getElementsByTagName('ul')[0];
ul.addEventListener('mouseover',function(e){
if(e.target.tagName=='LI'){
        var target = e.target;
        target.style.background = "yellow";
}
},false);
ul.addEventListener('mouseout',function(e){
if(e.target.tagName=='LI'){
        var target = e.target;
        target.style.background = "green";
}
},false);
</script>
</body>
</html>

代码片段 4效果预览
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

}
ul li{
background : green;
margin :5px;
}
</style>
</head>
<body>
<ul>
    <li ></li>
    <li ></li>
    <li ></li>
    <li ></li>
</ul>
<script>
var ul = document.getElementsByTagName('ul')[0];
ul.addEventListener('mouseover',function(e){
if(e.target.tagName=='LI'){
        var target = e.target;
        target.style.background = "yellow";
}
},false);
ul.addEventListener('mouseout',function(e){
if(e.target.tagName=='LI'){
        var target = e.target;
        target.style.background = "green";
}
},false);
</script>
</body>
</html>

示例5 这里再举个比较常见例子,使用场景是这样的,我们经常要使用一些表格,来展示数据,一般最后一列都是数据相关操作,比如查看、修改和删除等操作。这时也可以使用委托。

HTML 代码
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

<html>
<head>
</head>
<body>
<div id = "oper">
<a href="#" name="info">showDetails</a>
<a href="#" name="update">update</a>
<a href="#" name="delete">delete</a>
<div>
<script>
var oper = document.getElementById('oper');
oper.addEventListener('click',function(e){
var target = e.target;
var name = null;
if(target){
        name = target.name;
}
if(!name){
        return ;
}
if(name == 'info'){
        alert('showDetails');
}else if(name== 'update'){
        alert('update');
}else if(name == 'delete'){
        alert('delete');
}
},false);
</script>
</body>
</html>

代码片段 5效果预览
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

<html>
<head>
</head>
<body>
<div id = "oper">
<a href="#" name="info">showDetails</a>
<a href="#" name="update">update</a>
<a href="#" name="delete">delete</a>
<div>
<script>
var oper = document.getElementById('oper');
oper.addEventListener('click',function(e){
var target = e.target;
var name = null;
if(target){
        name = target.name;
}
if(!name){
        return ;
}
if(name == 'info'){
        alert('showDetails');
}else if(name== 'update'){
        alert('update');
}else if(name == 'delete'){
        alert('delete');
}
},false);
</script>
</body>
</html>

ext的gridPanel做的比较好,行点击事件或表格点击事件,其event都传进来了。easy-ui就不咋地了,坑。

这里先总结一下js委托相关。
1.好处大大的,因为把事件绑定到父节点上了,因此事件少了很多。就算新增的子节点自然也有了相关事件。删除部分子节点不用你销毁对应节点上绑定的事件。
2.父节点是通过event.target来找到对应的子节点的。

我们再来看看jquery怎么使用委托?
jquery那可真方便了,委托的关键是怎么通过父节点来找到对应的子节点的。可以通过event.target,如能拿到this,那就是想做啥就做啥。委托也有专门的api。如delegate和on了。
target方式,不演示了,这里大致说一下on,请看例子。注意回调函数里this是currentTarget的(原文此处写的是target,且看下面的“注意”)(这个跟jquery没关,绑定到dom上的回调函数里的this都是currentTarget的)。
on函数还是蛮吊的请点这里看详细说明
实例6:大致写个表单验证,估计也没什么人这么写,这里只是演示jquery委托。

HTML 代码
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

        return; 
}
//alert(name)
switch(name){
case 'name' : 
        if(/^\s*$/.test(value)){
            alert('your name can not be empty')
        }
        break;
case 'age' :
        if(!/^[1-9]|[1-9]\d/.test(value)){
            alert('age should between 1 and 99');
        }
        break;
}
}).on('change','input[name=sex]',function(){
if($(this).val()=='shemale'){
        var r = confirm('shemale?? are you sure');
        if(!r){
            $(this).siblings('input[name=sex]:first').attr('checked','checked');
        }
}
}).on('click','button',function(){
var name = $(this).data('action');
if(name=='submit'){
        //验证逻辑不写了
        alert('submit');
}else{
        $(this).parent()[0].reset();
        alert('reset');
}
});
</script>

代码片段 6效果预览
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

<body>
<script src="http://libs.baidu.com/jquery/1.9.1/jquery.min.js"></script>
<form id="container">
    your name<input name="name" type="text"/><br />
    your age<input name="age" type="text"/><br />
    your sex<input name="sex" type="radio" value="male"/>male
    <input name="sex" type="radio" value="female">female 
    <input name="sex" type="radio" value="shemale">
    shemale
    <br>
    <button data-action="submit">submit</button>
    <button data-action="cancel">cancel</button>
</form>
<script type="text/javascript">
$('#container').on('blur','input',function(){
var $this = $(this);
var value = $this.val();
var name = $this.attr('name')
if(!name){
        return; 
}
//alert(name)
switch(name){
case 'name' : 
        if(/^\s*$/.test(value)){
            alert('your name can not be empty')
        }
        break;
case 'age' :
        if(!/^[1-9]|[1-9]\d/.test(value)){
            alert('age should between 1 and 99');
        }
        break;

注意 : 上面说的回调函数里this是target,说法是不正确的(差点误人子弟),其实是currentTarget。二者有什么区别呢?target是触发事件最开始的那个子节点。而currentTarget不言而喻,就是当前节点了,你绑定事件执行事件的那个节点。(之前例子一直是两层,我没仔细看)请看下面三层div的例子,注意currentTarget是中间层center(不是outer),而taget是最里层inner。说明匿名函数最后在center上执行的。

代码片段 7效果预览
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

<!doctype html>
<html>
<head>
<style>
#inner{
width : 200px;
height:200px;
cursor : pointer;
background : gray;
}
</style>
<body>
<div id='outer'>
    <div id='center'>
        <div id="inner"></div>
    </div>
</div>
<script src="http://libs.baidu.com/jquery/1.9.1/jquery.min.js"></script>
<script>
$('#outer').on('click','#center',function(e){
        alert(this.id);
        alert(e.target.id)
        alert(e.currentTarget.id)
});
</script>
</body>
</html>

四、自定义事件
ok,先来看看jquery是怎么实现自定义事件的
jquery中用on或bind绑定自定义事件,用trigger来触发
示例

HTML 代码
1
2
3
4
5
6
7

$(function(){
    $('button').on('mycustomEvent',function(){
        alert("trigger customEvent");
    });
    $('button').trigger('mycustomEvent');
});

代码片段 8效果预览
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

<!doctype html>
<html>
<head>
<script src="http://libs.baidu.com/jquery/1.9.1/jquery.min.js"></script>
<script>
$(function(){
$('button').on('mycustomEvent',function(){
        alert("trigger customEvent");
});
$('button').trigger('mycustomEvent');
});
</script>
</head>
<body>
<button style="width:100px;height:20px;"></button>
</body>
</html>

jquery是怎么用的,先看到这,这时要来说一个关键的事情。必须得解释一下什么叫自定义事件。
一旦你理解其精髓,你的js世界观就会变了,会感觉到整个天空都亮了(且听我胡说)。
先来看一下什么叫事件呢?不用去找什么定义,自己闭眼睛想想就知道。用户要跟浏览器交互,浏览器要做出反应。这个反应就是事件。
以button绑定onclick为例,我们来看看事件的组成要素
第一,谁的事件,button的
第二,谁来触发,用户点击按钮时触发事件,可以说是用户来触发的
第三,谁来执行,button来执行,所以对应函数执行环境(执行上下文)this是指向button的。
第四,要做什么事情,要做的事情就是函数的执行。
第五,既然是函数,那么参数是什么,是event。那event是什么?event是事件的状态对象。
细心的我们,有没有发现一件事情,函数是整个事件中最重要的组成部分。下面来打通任督二脉。

事件==函数

来看一下函数的组成部分
为了方便说,举个例子

HTML 代码
1
2
3
4
5
6
7
8

var obj ={
    fn : function(){
        console.log(this);
        console.log(arguments);
    }
}
obj.fn();

第一,谁的函数,obj的
第二,谁来触发,函数调用来触发的,或者说是代码obj.fn()来触发的
第三,谁来执行,obj来执行,因此对应函数的this是指向obj的。
第四,要做什么事情,不必说,
第五,既然是函数,那么参数是什么,也不必说。

其核心的地方就是触发等价于调用。再换个角度想想,事件不就是要做一些事情吗,而函数正是做一些事情的封装,二者肯定脱不了干系的。
那么什么叫自定义事件,狭义的来说,给dom元素绑定一些非浏览器默认支持的事件。广义的来说呢?要反过来来看上面的等式
函数==事件
函数其实就是自定义的事件
所以大家看那个jquery自定义事件例子,初看没啥用,其实就相当于

HTML 代码
1
2
3
4
5

var obj = $('button')
obj.customEvent = function(){
    alert("trigger customEvent");
}
obj.customEvent();

但是,事实关键不在于,代码写的如何,代码谁多谁少的问题。而是思维的转变,如果你把函数当成事件来看,看事情的角度就发生变化了。
为啥说js是事件驱动的一门语言。现在能更好的理解这句话了。

扯远了,回到jquery自定义事件。
先看一下全选的例子,可以直接看运行效果。下面是直觉思维的实现方式。

代码片段 9效果预览
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

<!doctype html>
<html>
<head>
<style>
body{
margin : 30px;
}
#container{
border : 1px solid black;
width : 200px;
height :200px;
}
</style>
<script src="http://libs.baidu.com/jquery/1.9.1/jquery.min.js"></script>
<script>
</script>
<script>
$(function(){
//绑定按钮点击事件
$('#add').on('click',function(){
        //看看是不是全选
        if($('#selectAll').is(':checked')){
            $('#container').append(' <input type="checkbox" checked="checked"/>');
        }else{
            $('#container').append(' <input type="checkbox" />');
        }
});
//全选逻辑
$('#selectAll').on('click',function(){
        if(this.checked){
            $('#container').find('input:checkbox').prop('checked','checked');
        }else{

其核心代码是
实现1:

HTML 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

$(function(){
    //绑定按钮点击事件
    $('#add').on('click',function(){
        //看看是不是全选
        if($('#selectAll').is(':checked')){
            $('#container').append(' <input type="checkbox" checked="checked"/>');
        }else{
            $('#container').append(' <input type="checkbox" />');
        }
    });
    //全选逻辑
    $('#selectAll').on('click',function(){
        if(this.checked){
            $('#container').find('input:checkbox').prop('checked','checked');
        }else{
            $('#container').find('input:checkbox').removeProp('checked');
        }
    });
})

从上面可以看出来
add要和container以及selectAll打交道
selectAll要和container打交道
如果把这三者,看做是三个模块,那么可以说这三者强耦合在一起了。

下面换一种方式来做
此时自定义事件出马,只让add、selectAll分别和container打交道
实现2

HTML 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

$(function(){
    var $container =$('#container');
    
    //绑定两个自定义事件
    $container.on('selectCheckbox',function(e,checked){
        //缓存状态
        var $this = $(this).data('state',checked);
        if(checked){
            $this.find('input:checkbox').prop('checked','checked');
        }else{
            $this.find('input:checkbox').removeProp('checked');
        }
    }).on('addCheckbox',function(){
        var $this = $(this);
        if($this.data('state')){
            $this.append(' <input type="checkbox" checked="checked"/>');
        }else{
            $this.append(' <input type="checkbox" />');
        }
    });
    
    //全选按钮触发自定义事件
    $('#selectAll').on('click',function(){
        $container.trigger('selectCheckbox',[this.checked])
    });
    
    //新增按钮触发自定义事件
    $('#add').on('click',function(){
        $container.trigger('addCheckbox');
    });
})

情况好了一些,还不够彻底,能不能这三个模块一点也不发生耦合呢?
貌似很难,其实再多出一个模块就ok了(就这个例子而言,代码确实比最开始的实现多了很多,这里为了把道理说明白,真实大才小用了)
实现3

HTML 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

$(function(){
    var $common =$({});
    
    //绑定两个自定义事件
    $common.on('selectCheckbox',function(e,checked){
        //缓存状态(原先是缓存到container中的)
        var $this = $(this).data('state',checked);
        if(checked){
            $('#container').find('input:checkbox').prop('checked','checked');
        }else{
            $('#container').find('input:checkbox').removeProp('checked');
        }
    }).on('addCheckbox',function(){
        if($(this).data('state')){
            $('#container').append(' <input type="checkbox" checked="checked"/>');
        }else{
            $('#container').append(' <input type="checkbox" />');
        }
    });
    
    //全选按钮触发自定义事件
    $('#selectAll').on('click',function(){
        $common.trigger('selectCheckbox',[this.checked])
    });
    
    //新增按钮触发自定义事件
    $('#add').on('click',function(){
        $common.trigger('addCheckbox');
    });
})

代码片段 10效果预览
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

            $('#container').find('input:checkbox').prop('checked','checked');
        }else{
            $('#container').find('input:checkbox').removeProp('checked');
        }
}).on('addCheckbox',function(){
        if($(this).data('state')){
            $('#container').append(' <input type="checkbox" checked="checked"/>');
        }else{
            $('#container').append(' <input type="checkbox" />');
        }
});
//全选按钮触发自定义事件
$('#selectAll').on('click',function(){
        $common.trigger('selectCheckbox',[this.checked])
});
//新增按钮触发自定义事件
$('#add').on('click',function(){
        $common.trigger('addCheckbox');
});
})
</script>
</head>
<body>
<input id="selectAll" type="checkbox"/> <button id="add">add</button>
<div id="container">
<input type="checkbox"/>
<input type="checkbox"/>
<input type="checkbox"/>
<input type="checkbox"/>
</div>
</body>

如果,需求变更了,还要实现反选,即只要一个不选,selectall要变成不选的,以及container中所有都挑上,selectall也要选上,在第三种逻辑上改应该是最轻松,为啥?解耦了呗。

现在来尝试打通这个任督二脉
组件交互==回调函数==绑定事件==事件监听==调用方法==传递消息

之前全选那个例子一直说三者如果是三个模块,如果真是三个模块怎么办?且看我用回调函数来模拟,再来看看是否真的可以把函数当做事件来理解
实现4

HTML 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

<!doctype html>
<html>
<head>
<style>
body{
margin : 30px;
}
.container{
border : 1px solid black;
width : 200px;
height :200px;
}
</style>
<script src="http://libs.baidu.com/jquery/1.9.1/jquery.min.js"></script>
<script>
//全选按钮模块
var SelectAll = (function($){
//构造函数
var fn = function(config){
        //渲染位置
        this.renderTo = config.renderTo || 'body';
        //自定义事件或者说监听函数(其实就是回调函数)
        this.listeners = {
            'onSelectAll' : null
        }
        //copy listeners 到本地
        if(typeof config.listeners == 'object'){
            for(var attr in config.listeners){
                if(attr in this.listeners){
                    this.listeners[attr] = config.listeners[attr];
                }
            }
        }

代码片段 11效果预览
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

<!doctype html>
<html>
<head>
<style>
body{
margin : 30px;
}
.container{
border : 1px solid black;
width : 200px;
height :200px;
}
</style>
<script src="http://libs.baidu.com/jquery/1.9.1/jquery.min.js"></script>
<script>
//全选按钮模块
var SelectAll = (function($){
//构造函数
var fn = function(config){
        //渲染位置
        this.renderTo = config.renderTo || 'body';
        //自定义事件或者说监听函数(其实就是回调函数)
        this.listeners = {
            'onSelectAll' : null
        }
        //copy listeners 到本地
        if(typeof config.listeners == 'object'){
            for(var attr in config.listeners){
                if(attr in this.listeners){
                    this.listeners[attr] = config.listeners[attr];
                }
            }
        }

原生js实现自定义事件代码待续(要么你百度)
本节也待续
五、发布订阅模式
在说发布订阅模式之前,让我们再重新看看委托。
这次再说委托可不是仅仅说由于冒泡机制的那个委托。
我们要站在一个更高层次上来研究研究这个东西(正所谓站得高尿得远,嘿嘿)。
委托本质是啥呢?还是你句话,你要办的事情,不是自己直接去办,而是拜托别人去办。
那好,由于主宾不同,这就引出了两种思路。
我委托别人,那相当于我是借鸡下蛋。(直觉思维)
别人委托我,那相当于我为其他人做嫁衣。
那么我们就来看看怎么做的嫁衣、怎么下的蛋

HTML 代码
1
2
3
4
5
6
7

var sum = function(a, b){
    return a + b;
}
var mysum = function(a, b){
    return sum(a, b);
}
alert(mysum(2,3));

mysum借用了sum下了蛋。
这时,你恐怕说,老姚,我读书少,你别忽悠我。这不就是简单函数调用嘛,跟委托有毛关系。
没错,这是函数调用,但我们要用委托的眼光来看我们的代码,由于角度不同,想法也许就会不同。
为了讲下去,把例子复杂一下

HTML 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

var operation = function(a,b,oper){
    var r = 0
    switch(oper){
        case '+': r = a+b;break;
        case '-': r = a-b;break;
        case '*': r = a*b;break;
        case '/': r = a/b;break;
        case '%': r = a%b;break;
        default : r = NAN;
    }
    return r;
}
var minus = function(a,b){
    return operation(a,b,'-');
}
alert(minus(4,2));

可以说写operation就是为minus做嫁衣的。代码虽简单其实这也是一种模式:门面模式。
你说直接要用operation来做减法,当然可以的,但是minus是不是更简单一些,更有语意化呢。
jquery中那可是大量用了这种方法,on是bind的门面,$.get是$.ajax的门面等等。

我们在做项目中,开始写代码时,对功能没有完全认识之前,思路基本上都是借鸡下蛋的,
总想着怎么使用之前的函数。这是可以理解的,功能都完成后,这时千万别认为活干完了。还有一个事情要做,那就是重构。
重构的思路是反过来的,是想办法来做各种嫁衣。
还有一种情况下蛋和嫁衣是同一个东西,你猜到是什么了吗?
递归。递归说简单也很简单,说难也很难,当初本人可是没少在上面耗时间,以后可能会出一篇文章专门讲这个的。
写文章其实也是为他人做嫁衣。我们学习嘛,当然就是借鸡下蛋了。你看看,委托的概念随随便便就还可以上升到哲学层次上哈。

扯远了,继续下蛋。

HTML 代码
1
2
3
4
5
6
7
8
9
10
11

var P = function(name){
    this.name = name;
}
var Man = function(name,sex){
    P.call(this,name);
    this.sex = sex;
}
var m = new Man('laoyao','male');
alert(m.name);

这个是类式继承的简单实现。Man也要写this.name = name的,结果一看P都写过了,那好我借你的鸡下我的蛋.
事件委托是通过冒泡来的,函数的委托呢?没错,就是调用,如果想把你的this变成我的,那得通过call和apply的。
arguments是伪数组,要变成真正的数组的话,我们得写一些逻辑。结果写完了,会发现跟数组的slice内部基本逻辑差不多。
那好你写完了,我就不写了。直接委托给你得了。[].slice.call(arguments,0);(当然,我这是马后炮,没看到别人这么用时自己哪里想得到)
组件间通信,怎么个交互法,有没有什么委托呢?没错,你猜对了,就是本节主题发布订阅模式。
还记得那个全选的例子吧,上面有一种实现(实现3)就是让各个模块充分解耦,填了一个新的模块$common,对的,它就是那个托,所有人之间的通信都是通过它来的。
而发布订阅模式做得更绝秒了,让我们甚至都觉察不到它的存在。
让我们来看看它是怎么实现的。

HTML 代码
1
2
3
4
5
6
7
8
9
10
11
12

(function($){
    var o = $({});
    $.sub = function(){
        o.on.apply(o,arguments);
    }
    $.unsub = function(){
        o.off.apply(o,arguments);
    }
    $.pub = function(){
        o.trigger.apply(o,arguments);
    }
})(jQuery);

使用如下:

HTML 代码
1
2
3
4
5
6
7

$.sub('customEvent',function(e,text){
    alert(text);
})
$.pub('customEvent',['oh yeah']);
$.pub('customEvent',['haha']);
$.unsub('customEvent');
$.pub('customEvent',['can not see']);

运行如下:

代码片段 12效果预览
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

<!doctype html>
<html>
<head>
<script src="http://libs.baidu.com/jquery/1.9.1/jquery.min.js"></script>
<script>
(function($){
var o = $({});
$.sub = function(){
        o.on.apply(o,arguments);
}
$.unsub = function(){
        o.off.apply(o,arguments);
}
$.pub = function(){
        o.trigger.apply(o,arguments);
}
})(jQuery);
</script>
<script>
$.sub('customEvent',function(e,text){
alert(text);
})
$.pub('customEvent',['oh yeah']);
$.pub('customEvent',['haha']);
$.unsub('customEvent');
$.pub('customEvent',['can not see']);
</script>
</head>
<body></body>
</html>

先来简单看看那几行代码 
给jQuery函数添加了三个静态函数,订阅、取消订阅、发布。函数内部通过on和trigger来实现的。是委托到$({})这个对象上的。
内部虽然是通过绑定自定义事件和触发自定义事件来实现的。自定义事件只不过就是个名字而已。我们可以按照官方来理解,把它看做是一个通道或者是主题,订阅我这个主题,我发消息你自然就能看到。
下面我们来改改全选的实现3那个例子。
实现5

HTML 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

$(function(){   
    var currentChecked = false;
    //订阅主题
    $.sub('selectCheckbox',function(e,checked){
        currentChecked = checked;
        if(checked){
            $('#container').find('input:checkbox').prop('checked','checked');
        }else{
            $('#container').find('input:checkbox').removeProp('checked');
        }
    })
    //订阅主题
    $.sub('addCheckbox',function(){
        if(currentChecked){
            $('#container').append(' <input type="checkbox" checked="checked"/>');
        }else{
            $('#container').append(' <input type="checkbox" />');
        }
    });
    
    //全选按钮发布主题
    $('#selectAll').on('click',function(){
        $.pub('selectCheckbox',[this.checked])
    });
    
    //新增按钮发布主题
    $('#add').on('click',function(){
        $.pub('addCheckbox');
    });
})

代码片段 13效果预览
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

var o = $({});
$.sub = function(){
        o.on.apply(o,arguments);
}
$.unsub = function(){
        o.off.apply(o,arguments);
}
$.pub = function(){
        o.trigger.apply(o,arguments);
}
})(jQuery);
</script>
<script>
$(function(){   
var currentChecked = false;
//订阅主题
$.sub('selectCheckbox',function(e,checked){
        currentChecked = checked;
        if(checked){
            $('#container').find('input:checkbox').prop('checked','checked');
        }else{
            $('#container').find('input:checkbox').removeProp('checked');
        }
})
//订阅主题
$.sub('addCheckbox',function(){
        if(currentChecked){
            $('#container').append(' <input type="checkbox" checked="checked"/>');
        }else{
            $('#container').append(' <input type="checkbox" />');
        }
});

同样的,我们也可以来改写一下全选的实现4的controller层

HTML 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

//controller逻辑层(可以写成类)
$(function(){
    var currentChecked = false;
    $.sub('selectAll',function(e,flag){
        currentChecked = flag;
        container.selectOrCacelAll(flag);
    });
    $.sub('addBox',function(e){
        container.add(currentChecked);
    });
    
    //selectAll实例
    var selectAll = new SelectAll({
        listeners : {
            onSelectAll :function(flag){
                $.pub('selectAll',[flag]);
            }
        }
    });
    //add实例
    var add = new Add({
        listeners : {
            onAdd : function(){
                $.pub('addBox');
            }
        }
    });
    //container实例
    var container = new Container({});
});

原生js实现,有很多种实现方法,这里我简单写了一个,匿名函数还是删不掉的(如果要实现的话,得弄个uid才行)。

HTML 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

var obj = (function(){
    return {
        topics : {},
        sub : function(topic,fn){
            if(!this.topics[topic]){
                this.topics[topic] = [];
            }
            this.topics[topic].push(fn);
        },
        pub : function(topic){
            var arr = this.topics[topic];
            var args = [].slice.call(arguments,1);
            if(!arr){
                return;
            }
            for(var i = 0, len = arr.length;i < len;i++){
                arr[i](args);
            }
        },
        unsub : function(topic,fn){
            var arr = this.topics[topic];
            if(!arr){
                return;
            }
            if(typeof fn !== 'function'){
                delete this.topics[topic];
                return;
            }
            for(var i = 0, len = arr.length;i < len;i++){
                if(arr[i] == fn){
                    arr.splice(i,1);
                }
            }

代码片段 14效果预览
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

<!doctype html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<script src="http://libs.baidu.com/jquery/1.9.1/jquery.min.js"></script>
<script>
var obj = (function(){
return {
        topics : {},
        sub : function(topic,fn){
            if(!this.topics[topic]){
                this.topics[topic] = [];
            }
            this.topics[topic].push(fn);
        },
        pub : function(topic){
            var arr = this.topics[topic];
            var args = [].slice.call(arguments,1);
            if(!arr){
                return;
            }
            for(var i = 0, len = arr.length;i < len;i++){
                arr[i](args);
            }
        },
        unsub : function(topic,fn){
            var arr = this.topics[topic];
            if(!arr){
                return;
            }
            if(typeof fn !== 'function'){
                delete this.topics[topic];
                return;

六、观察者模式
提到observer,你能想到什么?
我最开始想到的美剧《危机边缘》里面那些大光头,说是每次重大历史事件里面都有个大光头,在观察着一切的发生。
很少有人会直接想到浏览器。其实我们的浏览器整整就是一个观察者模式。

先说几个概念:
你说是观察者模式,肯定有观察者吧,
那观察谁呢,即被观察者,我们姑且称其为目标吧。
观察你的什么呢?相貌吗?观察的是状态。
我观察你干啥呢,看看你有啥变化?根据你的状态来做我的事情。

这里还是拿那个全选的例子来说说:
目标 : 全选复选框
观察者:container里的复选框,和add按钮
状态:全选按钮是否选上
行为:你选,container我就选上,点击add时,添加选上的按钮,反之,反然。

那怎么实现呢?
这里有两种思路,我观察你,想看看你的状态,我可以到你那去看,还有一种就是你的状态变化时,你来告诉我。
说白了就是“取”和“送”的概念。
观察者模式是采用“送”的概念。
那也涉及到一个问题,既然是“送”,你怎么来找到我呢?那你得保存我给你的地址。

现在可以来看看定义了。
Observer(观察者模式):定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。
ok,我们先按照这种思路来实现一下
实现6

HTML 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

$(function(){
    //缓存三个元素
    var $selectAll = $('#selectAll');
    var $add = $('#add');
    var $container = $('#container');
    
    //目标保存观察者
    $selectAll.cbList = [];
    $selectAll.addBtn = $add;
    
    //目标通知函数,通知观察者更新
    $selectAll.notify = function(value){
        $.each(this.cbList,function(index,checkbox){
            checkbox.update(value);
        });
        this.addBtn.update(value);
    };
    
    
    $selectAll.click(function(){
        $selectAll.notify(this.checked);
    })
    $add.click(function(){
        addCheckbox($add.data('checked'));
    });
    
    //观察者add的更新方法
    $add.update = function(value){
        this.data('checked',value);
    }
    function addCheckbox(flag){
        var $cb;

代码片段 15效果预览
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

<!doctype html>
<html>
<head>
<style>
body{
margin : 30px;
}
#container{
border : 1px solid black;
width : 200px;
height :200px;
}
</style>
<script src="http://libs.baidu.com/jquery/1.9.1/jquery.min.js"></script>
<script>
$(function(){
//缓存三个元素
var $selectAll = $('#selectAll');
var $add = $('#add');
var $container = $('#container');
//目标保存观察者
$selectAll.cbList = [];
$selectAll.addBtn = $add;
//目标通知函数,通知观察者更新
$selectAll.notify = function(value){
        $.each(this.cbList,function(index,checkbox){
            checkbox.update(value);
        });
        this.addBtn.update(value);
};

看到这里,难免会想这里哪有观察者模式的“模式”影子啊。
下来就要抽出目标和观察者的抽象接口,然后才是真正观察模式,才能得以复用。

HTML 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

var ObserverPatten = (function(){
    var Subject = function(){
        this.observerlist = [];
    }; 
    Subject.prototype = {
        add : function(observer){
            this.observerlist.push(observer);
        },
        notify : function(state){
            var arr = this.observerlist;
            for(var i = 0, len = arr.length;i<len;i++){
                arr[i].update(state);
            }
        }
        /*一些查找、移除、统计、清空等等函数  省略了。。。*/
    };
    var Observer = function(){
    };
    Observer.prototype = {
        //等待被复写
        update : function(){},
        /*...*/
    };
    return {
        Subject : Subject,
        Observer : Observer
    };
})();

因此全选的例子可以改成如下
实现7

HTML 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

$(function(){
    //缓存三个元素
    var $selectAll = $('#selectAll');
    var $add = $('#add');
    var $container = $('#container');
    
    //具体目标实现目标接口
    $selectAll.extend(new ObserverPatten.Subject);
    $selectAll.add($add);
    $selectAll.click(function(){
        $selectAll.notify(this.checked);
    })
    
    $add.click(function(){
        addCheckbox($add.data('checked'));
    });
    
    //观察者add实现观察者接口
    $add.extend(new ObserverPatten.Observer);
    
    //复写update方法
    $add.update = function(value){
        this.data('checked',value);
    }
    function addCheckbox(flag){
        var $cb;
        if(flag){
            $cb = $('<input type="checkbox" checked="checked" >');
        }else{
            $cb = $('<input type="checkbox">');
        }
        

代码片段 16效果预览
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

<!doctype html>
<html>
<head>
<style>
body{
margin : 30px;
}
#container{
border : 1px solid black;
width : 200px;
height :200px;
}
</style>
<script src="http://libs.baidu.com/jquery/1.9.1/jquery.min.js"></script>
<script>
var ObserverPatten = (function(){
var Subject = function(){
        this.observerlist = [];
}; 
Subject.prototype = {
        add : function(observer){
            this.observerlist.push(observer);
        },
        notify : function(state){
            var arr = this.observerlist;
            for(var i = 0, len = arr.length;i<len;i++){
                arr[i].update(state);
            }
        }
        /*一些查找、移除、统计、清空等等函数  省略了。。。*/
};
var Observer = function(){
};

七、后记
写到这里,写了一周也没写完,真的好长。
回顾本文,主要就讲了一件事情,特意去模糊事件和函数在我们头脑中的概念区别。目的也只是让我们理解代码更轻松些。
至于后面说的发布订阅模式和观察者模式,也只是大概地说说,发订模式在全选这个例子实现,其实是其一种特殊情况,就是中介者模式。
订阅者也可以作为另一个订阅者的发布者。也就是说发布者和订阅者,可以多对多的。
至于发订是观察者的一个分支,也好理解的。全选那个例子,三个模块就是观察者,目标就是发布订阅实现里面的$({})。
这个东西,本来想展开的,想写多对多的例子。一看本文太长了,还是算了。
写文章确实不容易,不仅要考虑说出去的话,有没有歧义。还要为自己的话,负责的。尽量不误人子弟。
还有一个事情,比较麻烦。就是案例的设计,还得尽量不去copy网上类似的文章,免得别人说你抄的。真头疼的。
其实还有几个点,原本也想展开说说的。念在眉毛胡子一把抓,没有重点,留给以后吧。
本文完。

我也来说说js的事件机制相关推荐

  1. 【JavaScript】JS事件机制学习

    常用的事件 通过事件机制,达到与用户的交互,与java的swing交互类似. 主要是结合js的函数使用. 当你添加一个事件之后没有达到想要的效果时,就要检查一下是不是给HTML标签添加了合适的事件,以 ...

  2. JavaScript之JS事件机制

    JS事件机制 一.JS事件机制 1.解释 2.作用 3.内容 3.1 单双击事件 3.2 鼠标事件 3.3 键盘事件 3.4 焦点事件 3.5 页面加载事件 4.注意 5.实例 二.衍生思考 1.给合 ...

  3. JavaScript 详说事件机制之冒泡、捕获、传播、委托

    DOM事件流(event  flow )存在三个阶段:事件捕获阶段.处于目标阶段.事件冒泡阶段. 事件捕获(event  capturing):通俗的理解就是,当鼠标点击或者触发dom事件时,浏览器会 ...

  4. click事件在什么时候出发_超全的js事件机制amp;事件委托

    超全的js事件机制&事件委托,想要理解js事件只需认真看完此篇即可~ 目录结构: 什么是事件机制 事件冒泡事件捕获 DOM事件流事件委托 误区 在同一个对象上注册事件,并不一定按照注册顺序执行 ...

  5. JS的线程机制与事件机制

    JS的线程机制与事件机制 前言 一.进程与线程的概念 二.浏览器内核 概念 三.思考定时器 1. 定时器真的是定时执行的吗? 2. 定时器回调函数是在哪个线程执行的? 3. 定时器是如何执行的? 四. ...

  6. JS的事件处理机制以及事件代理(事件委托)

    一.先记个小知识点.cssText cssText 本质:设置 HTML 元素的 style 属性值. 用法:document.getElementById("d1").style ...

  7. JS的事件监听与委托机制

    JS的事件监听机制 小故事: 很久以前有个叫Netscape的姑娘,她制订了Javascript的一套事件驱动机制(即事件捕获) 后来又有一个叫"IE"的小子,这孩子比较傲气,他认 ...

  8. 「前端面试题系列7」Javascript 中的事件机制(从原生到框架)

    前言 这是前端面试题系列的第 7 篇,你可能错过了前面的篇章,可以在这里找到: 理解函数的柯里化 ES6 中箭头函数的用法 this 的原理以及用法 伪类与伪元素的区别及实战 如何实现一个圣杯布局? ...

  9. jQuery中的事件机制深入浅出

    昨天呢,我们大家一起分享了jQuery中的样式选择器,那么今天我们就来看一下jQuery中的事件机制,其实,jQuery中的事件机制与JavaScript中的事件机制区别是不大的,只是,JavaScr ...

最新文章

  1. Typora最好用的Markdown编辑器
  2. Java - Poi 操作 Excel
  3. C++ Primer 5th笔记(chap 18 大型程序工具)虚继承
  4. Myeclipse+mysql出现中文乱码情况
  5. 运维基础(3)备份篇
  6. 免扣PNG图片素材,用着就是爽|png与jpg格式图片的区别
  7. 数据结构c语言版马睿课后答案,清华大学出版社-图书详情-《C语言程序设计习题解答与实验指导》...
  8. 偷取php网站源码,最新PHP新闻小偷采集站开源版本源码分享,集成六个广告位,不限制域名...
  9. Windows11拼音打字不出现候选字词窗口
  10. (根据关键词)查找论文的一些途径
  11. 工程项目提成标准方案_工程绩效提成奖金方案
  12. 需账号密码登陆的网页爬虫
  13. 怎么禁用笔记本的触摸板
  14. fuse文件系统调试环境
  15. RK3399平台开发系列讲解(内核驱动外设篇)6.5、音频芯片ES8323 基础知识及设备树相关配置
  16. 跌宕七十年,日本制造业兴衰「启示录」
  17. UIQ 3 概念认识
  18. IDA dword_xxx DCD 0xxxx用十六进制数表示的字符串解读
  19. 互联网金融 面试 java_Java笔试题(互联网金融方向)
  20. 佳力奇IPO过会:拟募资11亿 西安现代与华控湖北是股东

热门文章

  1. EmEditor编辑器正则表达式的优点
  2. Linux 信号随笔
  3. 在JSP客户端限制表单重复提交
  4. WindowsPE 第五章 导出表
  5. ZOJ3715 竞选班长求最小花费
  6. C语言经典例27-利用递归逆序输出字符串
  7. 计算机网络第二章:物理层
  8. 【Linux 内核】进程优先级与调度策略 ③ ( 设置、获取线程优先级的核心函数 | 修改线程调度策略函数 )
  9. 【Groovy】Groovy 脚本调用 ( Groovy 脚本中调用另外一个 Groovy 脚本 | 调用 evaluate 方法执行 Groovy 脚本 | 参数传递 )
  10. 【Android 安装包优化】资源混淆 ( resources.arsc 资源映射表混淆 | resources.arsc 资源映射表二进制格式分析 | 混淆全局字符串池和资源名称字符串池 )