js-事件

事件的三个阶段

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

img

事件的绑定和解绑

一般有3种写法。

2.1. 属性方式

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

<input type="button" value="测试" onclick="test(event)"/>
<script>
function test(e) {
	console.log(e);
	alert('你好!');
}
</script>

或者通过JS来绑定onclick

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

target.onclick = null; // 解绑

return false; // 阻止默认事件

2.2. W3C标准:

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

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

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

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

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

2.3. IE8或更低:

不支持捕获阶段

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

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

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

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

// 获取event对象:window.event

2.4. 封装

/**
 * 绑定事件
 */
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;
}

2.5. 事件内部兼容写法

on($0, function(e) {
	e = e || window.event;
	var target = e.target || e.srcElement; // srcElement是老版本IE写法
	console.log(e, target);
});

详述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模拟事件的实现

ES6版的:

class EventEmitter {
	constructor() {
		this._events = {};
	}
	on(type, fn) {
		this._events[type] = this._events[type] || [];
		this._events[type].push(fn);
	}
	off(type, fn) {
		if(fn === undefined) this._events[type] = [];
		else {
			let callbacks = this._events[type] || [];
			let idx = callbacks.indexOf(fn);
			if(idx >= 0) {
				callbacks.splice(idx, 1);
			}
		}
	}
	emit(type, ...args) {
		(this._events[type] || []).forEach(fn => fn.apply(this, args));
	}
}

测试:

var emitter = new EventEmitter();
var fn1 = function(a, b) { console.log('我是第1个', a, b); }
var fn2 = function(a, b) { console.log('我是第2个', a, b); }
emitter.on("myevent", fn1);
emitter.on("myevent", fn2);
emitter.emit('myevent', 111, 222);
emitter.off('myevent', fn1);
emitter.emit('myevent', 111, 222);

自定义事件

/**
 * @param eventName 自定义事件名称
 * @param canBubble 是否冒泡
 * @param cancelable 是否可以取消事件的默认行为
 * @param detail 要传递的自定义数据
 */
//event.initCustomEvent(string eventName, boolean canBubble, boolean cancelable, any detail);

var event = document.createEvent("CustomEvent");
event.initCustomEvent("myevent", true, true, {a:1, b:2});
document.addEventListener("myevent", function(e)
{
	console.log(e.detail); // 输出 {a:1, b:2}
});
document.dispatchEvent(event);

兼容

document.addEventListener('testevent', function(e){console.log(e.detail);});
document.dispatchEvent(new CustomEvent('testevent', {detail:'aaa'})); // 输出 aaa

API:

element.dispatchEvent(new CustomEvent(eventType, params));
/*params =
{
	detail: eventData,
	bubbles: true,
	cancelable: true
};*/

修复浏览器不支持window.CustomEvent对象

/**
 * fixed CustomEvent
 */
(function() {
	if (typeof window.CustomEvent !== 'undefined') return;
	function CustomEvent(eventName, params)
	{
		params = params ||
		{
			bubbles: false, // 默认不冒泡
			cancelable: false, // 默认不能取消默认事件
			detail: undefined
		};
		var event = document.createEvent('Events');
		var bubbles = true;
		for (var name in params) 
		{
			(name === 'bubbles') ? (bubbles = !!params[name]) : (event[name] = params[name]);
		}
		event.initEvent(eventName, bubbles, true);
		return event;
	};
	CustomEvent.prototype = window.Event.prototype;
	window.CustomEvent = CustomEvent;
})();
posted @ 2020-10-26 16:40  千年轮回  阅读(162)  评论(0)    收藏  举报