JavaScript Events

2018-03-02

浏览器中的事件。

基本使用

1
2
3
var button = document.getElementById("createButton");

button.addEventListener('click', function(event){ /* function body */ }, false);//添加事件监听
  • 当监听事件的函数为匿名时, 由于我们没有该匿名函数的reference, 我们无法通过removeEventListener()取消该事件监听。
  • 监听函数获得event对象,它包含了这个事件的相关信息,如timestamp, coordinates, 触发事件的对象(target),还有相关的function。

事件的触发顺序

  • 如果该元素和其父元素拥有相同的事件监听,当这个事件被触发时,哪个事件监听首先执行呢 ?

  • 对于这个问题,网景公司和微软采取了两种不同的处理方式。

事件捕获(Netscape): 由外而内

  • 当某元素触发某个事件时(如Click), 顶层对象document会发出一个事件流,随着DOM树节点向目标节点流去,经过的节点如果绑定了 特定的在捕获阶段触发的事件监听器 ,则该事件监听器被触发,事件流最终流向目标节点。

事件冒泡(Microsoft): 由内到外

  • 从目标元素开始,往顶层元素传播。途中如果经过的节点绑定了 在冒泡阶段触发的事件监听器,则该监听器被触发,事件流最终流向顶层document对象。可以使用 event.stopPropagation() 终止冒泡过程。

W3C标准: 由外到内再到外

取消事件

  • 在事件捕获和冒泡阶段中,可以通过event.stopPropagration()来取消事件的进一步传播

    1
    2
    3
    4
    5
    let button = document.getElementById('click');
    button.addEventListener('click', function(e){
    e.stopPropagation();
    /*...*/
    });
  • 同一元素绑定针对相同事件的多个EventHandler, EventHanlder按照其绑定顺序被执行; 如果其中的一个 EventHandler A 执行了event.stopImmediatePropagation(),那么不仅事件传播停止,顺序在A之后的其他 EventHandler 也不会被执行。

Event 对象

  • 传入 EventHanlder() 的Event对象的属性可以分为以下几个类型:
    • 规定事件类型: bubbles
    • 关于事件执行环境的属性: button, ctrlKey, altKey
    • 与按键事件有关的属性:charCode, keyCode, which
    • 事件的触发位置: pageX, pageY, screenX, screenY
    • 与事件相关的元素: target, originalTarget,currentTarget, realtedTarget
    • 事件传播: stopPropagation(), stopImmediatePropagation
    • 默认行为: preventDefault()

上下文(context)变化

  • 当使用addEventListener()添加的事件监听被触发时,事件处理函数中的this被修改为指向目标元素。

  • 通过如下方法保存原始上下文:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function proxy(func, thisObject){
    return (function(){
    return func.apply(thisObject, arguments);
    });
    }

    var clicky = {
    wasClicked: function(){},
    addListeners: function(){
    var self = this;
    document.getElementById('id').addEventListeners(proxy(self.wasclick, this));
    }
    }

事件委派

  • 由于事件的冒泡机制, 我们可以仅仅在元素的父元素上添加EventHanlder来监听所有子元素的事件。
    • 可以减少代码量
    • 动态添加地子元素仍然会拥有该事件监听器
1
2
3
4
5
6
// Delegating Events on a ul list
list.addEventListener('click', function(e){
if(e.currentTarget.tagName = 'li'){
/*...*/
}
});

自定义事件

  • 通过 jQuery 创建自定义事件

    1
    2
    3
    4
    //Bind custom event
    $('.class').bind('refresh.widget',function(){});
    //Trigger Custom event
    $('.class').trigger('refresh.widget');
  • 通过 Event 构造函数创建自定义事件

    1
    2
    3
    4
    5
    6
    7
    8
    var event = new Event('build');
    var eventWithData = new CustomEvent('build',{ 'detail': ele.dataset.time });
    // Listen for the event
    element.addEventListener('build', function(e){ /**/ });

    // Dispatch the event

    element.dispatchEvent(event);
  • 通过自定义事件实现的jQuery Tab Plug-ins

    1
    2
    3
    4
    5
    6
    7
    8
    <ul id='tabs'>
    <li data-tab="users">Users</li>
    <li data-tab="groups">Groups</li>
    </ul>
    <div id='tabsContent'>
    <div data-tab='users'>...</div>
    <div data-tab='group'>...</div>
    </div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
jQuery.fn.tabs = function(control){
var element = $(this);
control = $(contorl);

element.delegate('li', 'click', function(){
var tabName = $(this).attr('data-tab');
element.trigger('change.tabs', tagName);
})

element.bind('change.tabs', function(e, tabName){
element.find('li').removeClass('active');
element.find('>[data-tab="' + tagName + '"]');
})
element.bind('change.tabs', function(e, tabName){
control.find('>[data-tab]').removeClass('active');
control.find('>[data-tab"' + tabName + '"]');
})

element.find('li:first').addClass('active');
return this;
}

$('ul#tabs').tabs('#tabContent');
  • 通过Javascript 实现
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
var element = document.getElementById('tabs');
var elementContent = document.getElementById('tabsContent');
element.addEventListener('click',function(event){
var tabName = event.target.getAttribute('data-tab');
var eventWithData = new CustomEvent('change-tab',{
detail:{
tabName: tabName
},
bubbles: true,
cancelable: true
});
element.dispatchEvent(event);
});
element.addEventListener('change-tab', function(event){
var elementList = element.querySelectorAll('li');
for(var a in elementList){
a.classlist.remove('active');
}
element.querySelector('>[data-tab="' + event.detail.tabName + '"]').classList.add('active');
});
element.addEventListener('change-tab', function(event){
var elementList = elementContent.querySelectorAll('div');
for(var a in elementList){
a.classlist.remove('active');
}
elementContent.querySelector('>[data-tab="' + event.detail.tabName + '"]').classList.add('active');
});

非DOM事件

  • Publish / Subscribe模式: Pub / Sub 是一种沟通模式, Publisher向某一特定频道发送消息。 当消息被发送时,订阅了该频道的Subscriber收到通知。
  • Publisher 和 Subscriber是完全分离的。它们只是享有一个共同的频道。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var PubSub = {
subscribe: function(ev, callback){
var calls = this._callbacks || (this_callbacks = {});
(this._callbacks[ev] || (this._callbacks[ev] = [])).push(callback);
return this;
},
publish: function(){
var args = Array.prototype.slice.call(arguments, 0);

var ev = args.shift();

var list, calls,i,l;

if(!(calls = this._callbacks)) return this;
if(!(list = this._callback[ev])) return this;


for(i = 0, l = list.length; i < l; i++){
list[i].apply(this, args);
}
return this;
}
}