JavaScript原生事件相关知识点总结
本文由 liuxianan 发表于 2016-11-12 浏览(308) 下载md
最后修改 2017-08-30 标签:javascript 事件 绑定 解绑 委托 代理

事件的三个阶段

捕获阶段 -> 目标阶段 -> 冒泡阶段,IE低版本不支持捕获阶段。addEventListener的第三个参数useCapture就是表示是否在捕获阶段触发,默认false。

事件的绑定和解绑

一般有3种写法。

属性方式

此方式不推荐。如,直接在div上面写onclick:

target.onclick = fn; // 绑定事件

target.onclick = null; // 解绑

return false; // 阻止默认事件

W3C标准:

target.addEventListener(eventName, fn[, useCapture]); // 绑定事件

target.removeEventListener(eventName, fn[, useCapture]); // 解绑

e.preventDefault(); // 阻止默认事件

e.stopPropagation(); // 阻止冒泡

// 获取event对象:第一个参数就是e

IE8或更低:

不支持捕获阶段

target.attachEvent('on'+eventName, fn); // 绑定事件

target.detachEvent('on'+eventName, fn); // 解绑

e.returnValue = false; // 阻止默认事件

e.cancelBubble = true; // 阻止冒泡

// 获取event对象:window.event

封装

/**
 * 绑定事件
 */
function on(target, eventName, fn)
{
	if(!target) return;
	if(target.addEventListener) target.addEventListener(eventName, fn, false);
	else if(target.attachEvent) target.attachEvent('on'+eventName, fn); // 如果IE8或者更低版本
	else target['on'+eventName] = fn;
}
/**
 * 解绑事件
 */
function off(target, eventName, fn)
{
	if(!target) return;
	if(target.removeEventListener) target.removeEventListener(eventName, fn, false);
	else if(target.detachEvent) target.detachEvent('on'+eventName, fn); // 如果IE8或者更低版本
	else target['on'+eventName] = null;
}

详述useCapture

早期版本的useCapture不是可选的,为了兼容性最好始终写上它。

// TODO

移除事件需要注意的问题

removeEventListener时必须保证fn和原来的一样,否则remove不会生效;另外,如果同一个监听事件分别为“事件捕获”和“事件冒泡”注册了一次,那么这两次事件需要分别移除一次。

初学者可能经常会碰到一个问题,就是调用了removeEventListener之后事件依然存在,出现这个问题的一般原因都是addremovefn的引用不一致导致的,举个栗子:

// 切换事件
function toggleEvent(flag) {
	function fn(e) {
		console.log(e.target);
	}
	if(flag) window.addEventListener('click', fn, false);
	else window.removeEventListener('click', fn, false);
}
toggleEvent(true); // 添加事件
// 随便点击页面空白处几次测试效果
toggleEvent(false); // 移除事件
// 再次点击页面空白处发现事件没有移除成功

上面的代码想当然的把fn拿出来,以为这样写就能够正常移除了,但是由于每次执行的时候相当于重新定义了一个fn,虽然代码一模一样,但是二者不相等,所以移除失败,这就好比:

var a = function(){};
var b = function(){};
a === b; // false

委托

这段委托代码还有问题,先放这里:

function delegate(obj, eventName, selector, fn)
{
	on(obj, eventName, function(e)
	{
		e = e || window.event;
		var target = e.target || e.srcElement;
		var objs = document.querySelectorAll(selector);
		for(var i=0; i<objs.length; i++)
		{
			if(objs[i] == target) fn.apply(target, e);
		}
	});
}

JS模拟事件模型

function EventEmitter()
{
	this._listener = []; //_listener[事件名] = [事件列表]
}

// 注册事件
EventEmitter.prototype.bind = function(eventName, callback)
{
	this._listener[eventName] = this._listener[eventName] || [];
	this._listener[eventName].push(callback);
}

// 触发事件
EventEmitter.prototype.trigger = function(eventName)
{
	var args = Array.prototype.slice.call(arguments, 1);
	var listener = this._listener[eventName];
	if(!listener || listener.length == 0) return;
	listener.forEach(function(callback)
	{
		callback.apply(this, args);
	});
}

// 测试
var emitter = new EventEmitter();
emitter.bind("myevent", function(arg1, arg2)
{
	console.log(arg1, arg2);
});
emitter.bind("myevent", function(arg1, arg2)
{
	console.log(arg2, arg1);
});
emitter.trigger('myevent', "a", "b");

参考

https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/addEventListener

http://www.cnblogs.com/zhangmingze/p/4864367.html