>  기사  >  웹 프론트엔드  >  브라우저 기능 감지에 대한 자세한 설명(2) - 일반 이벤트 감지_jquery

브라우저 기능 감지에 대한 자세한 설명(2) - 일반 이벤트 감지_jquery

PHP中文网
PHP中文网원래의
2016-05-16 18:16:50984검색

이전 기사에서는 jQuery 1.4 버전의 몇 가지 새로운 브라우저 기능 감지 솔루션과 구체적인 목적을 소개했습니다. 이 기사에서는 이벤트에 중점을 두고 비교적 완전하고 보편적인 이벤트 감지 솔루션을 소개합니다.

이벤트 감지는 다양한 브라우저에서 이벤트가 존재하는지(사용 가능한지) 감지하는 것입니다. 이는 Javascript를 작성하는 과정에서도 매우 중요합니다. 예를 들어 mouseenter/mouseleave 이벤트는 실용적이지만 사용할 수 없습니다. 모든 브라우저는 표준 지원을 제공하므로 수동으로 시뮬레이션해야 합니다. 즉,

function addEvent(element, name, handler) { 
if (name == 'mouseenter' && !hasEvent(name, element)) { 
//通过其他手段模拟mouseenter事件 
} 
//正常的事件注册 
};

이 기사에서는 위 코드에서 hasEvent의 특정 구현에 중점을 둘 것입니다.

기본 솔루션

가장 기본적인 이벤트 감지 방법은 이벤트 등록 방법부터 시작해야 합니다.

이벤트를 등록하는 방법에는 일반적으로 3가지가 있으며 그 중 하나는 인라인입니다. 즉, HTML의 속성을 통해 이벤트를 선언하는 것입니다.

<button onclick="alert(&#39;CLICKED!&#39;);">CLICK ME</button>

위 코드는 버튼을 생성합니다. 태그를 지정하고 클릭 이벤트를 등록했습니다.

또 다른 해결 방법은 onclick에 직접 값을 할당하여 이벤트를 등록하는 것입니다.

document.getElementById('myButton').onclick = function() { 
alert('CLICKED!'); 
};

위의 두 가지 이벤트 등록 방법에서 onclick이 실제로 버튼 태그)에 값을 지정하면 이벤트 등록이 완료됩니다.

따라서 가장 기본적인 이벤트 감지 솔루션은 on[이벤트 이름] 속성이 DOM 요소에 존재하는지 확인하는 것이므로 가장 간단한 버전이 있습니다.

function hasEvent(name, element) { 
name = name.indexOf('on') ? 'on' + name : name; 
element = element || document.createElement('p'); 
var supported = name in element; 
};

필수 이벤트는 on[이벤트 이름] 형태로 요소의 속성으로 존재하므로, 범용성 측면에서 필요할 경우 이벤트 이름에 'on'만 추가하면 됩니다. 또한, 이벤트 발생 여부를 판단하는 일반적인 함수이므로, 특정 요소가 지정되지 않은 경우에는 가장 널리 사용되는 p 요소를 대신 사용할 수 있다.

일부 태그별 이벤트

일부 이벤트는 일부 요소에 고유하며 일반적으로 다음을 포함합니다.

  • 양식 고유 이벤트: 제출, 재설정

  • 고유 이벤트 입력: 변경, 선택

  • img 고유 이벤트: 로드, 오류, 중단

이러한 이벤트의 존재를 고려하면 p 요소를 사용하면 잘못된 결과가 나올 수 있으므로 범용 대체 요소를 만들 때 사전을 사용하여 만들어야 하는 요소 태그 이름을 유지할 수 있습니다.

var hasEvent = (function() { 
var tags = { 
onsubmit: 'form', onreset: 'form', 
onselect: 'input', onchange: 'input', 
onerror: 'img', onload: 'img', onabort: 'img' 
}; 
return function(name, element) { 
name = name.indexOf('on') ? 'on' + name : name; 
element = element || document.createElement(tags[name] || 'p'); 
supported = name in element; 
} 
})();

클로저를 사용하여 태그를 정적 사전으로 사용하면 객체 생성 비용을 어느 정도 줄일 수 있습니다.

DOM 오염

DOM 요소에 onclick과 같은 속성이 있는 이유는 DOM 요소 객체의 __proto__에 이 속성이 있기 때문입니다. Javascript의 약한 유형 메커니즘으로 인해 외부 코드가 pass __proto__에 속성을 추가하면 hasEvent 함수의 결과에 영향을 미칩니다. 예를 들어 다음 코드는 Firefox 및 Chrome에서 잘못된 결과를 생성합니다.

document.createElement('p').__proto__.ontest = function() {}; 
var supported = hasEvent('test', document.createElement('p')); //true

위 예에서는 호출할 때 __proto__ 속성이 수정됩니다. hasEvent에는 다른 p 객체가 사용되지만 __proto__의 본질은 프로토타입 체인의 객체이므로 모든 p 객체에 영향을 미칩니다.

이 상황을 처리하려면 __proto__ 속성에서 해당 속성을 삭제해야 합니다. 기본 유형 속성은 DontDelete로 표시되어 있으므로 delete 키워드를 사용하여 삭제할 수 없으므로 hasEvent 함수 다음 로직을 추가하면 더욱 안전한 판단을 내릴 수 있습니다.

var temp; 
if (supported && (temp = proto[name]) && delete proto[name]) { 
supported = name in element; 
proto[name] = temp; 
}

로직은 매우 간단합니다. __proto__에 추가될 수 있는 항목을 삭제하고 다시 시도해 보세요. 원래 값을 추가하면 값이 다시 변경됩니다.

Firefox가 BUG를 시작합니다

안타깝게도 위에 제공된 hasEvent 함수는 Firefox에서 완벽하게 작동하지 않습니다. Firefox에서 다음 코드를 실행하면 잘못된 결과가 나타납니다.

alert('onclick' in document.documentElement); //Firefox弹出false

따라서 Firefox를 지원하려면 hasEvent 함수를 다시 수정해야 합니다. 대부분의 브라우저에서 요소가 인라인으로 이벤트를 등록하면 element.on[이벤트 이름]을 통해 해당 요소에 등록된 함수 객체를 가져올 수 있습니다. 예:

<button id="test" onclick="alert(&#39;CLICKED!&#39;);" ontest="alert(&#39;TEST!&#39;);">CLICK ME</button> 
<script type="text/javascript"> 
var button = document.getElementById('test'); 
alert(typeof button.onclick); //弹出function 
alert(typoef button.ontest); //弹出string 
</script>

따라서 Javascript를 통해 on[이벤트 이름] 속성에 함수를 추가한 후 함수 객체를 얻었는지 확인합니다.

따라서 위에 제공된 메소드에서 hasEvent 함수가 false를 반환하는 경우 다음 추가 코드를 추가하여 이벤트가 존재하는지 추가로 확인할 수 있습니다.

if (!supported) { 
element.setAttribute(name, 'return;'); 
supported = typeof element[name] == 'function'; 
}

Firefox continue BUG

to 지금까지는 대부분의 브라우저와 호환되면서 각 DOM 요소의 이벤트 감지가 가능하지만, 윈도우 객체의 이벤트 감지에 대한 완전한 솔루션은 없습니다.

IE 시리즈, Chrome, Safari의 경우 창에서 간단한 on[이벤트 이름]을 사용하여 이벤트 존재 여부를 감지할 수 있으므로 DOM 오염을 방지하는 원래의 hasEvent 함수를 사용하면 작업을 잘 완료할 수 있습니다.

Firefox에서만 다음 코드는 잘못된 결과를 제공합니다.

alert('onload' in window); //Firefox弹出false 
alert('onunload' in window); //Firefox弹出false 
alert('onerror' in window); //Firefox弹出false

다행스럽고도 터무니없는 점은 Firefox가 p 3 이벤트와 같은 요소에서 위의 내용을 이상하게 감지할 수 있다는 것입니다. 일반 DOM 요소에서 이벤트를 감지하는 데 직접 오류가 발생하고 창에서 이벤트를 감지할 수도 있습니다. 다행히 대부분의 개발자는 p와 같은 요소에 언로드 이벤트가 있는지 확인하지 않습니다. 따라서 일부 이벤트를 감지하기 위해 창의 이벤트를 p 객체로 전달하기 위해 hasEvent 함수가 추가됩니다.

if (!supported) { 
if (!element.setAttribute || !element.removeAttribute) { 
element = document.createElement('p'); 
} 
element.setAttribute(name, 'return;'); 
supported = typeof element[name] == 'function'; 
element.removeAttribute(name); 
}

이 시점에서 비교적 완전한 hasEvent 함수가 완성되었지만 여전히 몇 가지 문제가 있습니다. 다음 코드와 같은 Firefox:

alert(hasEvent('unload', document.createElement('p')); //Firefox弹出true

但是在99%的应用场合之下,这个函数是可以正确的工作的。

添加缓存

为了进一步提高hasEvent的工作效率,考虑到DOM规范规定的事件数量不多,可以对通用的事件(即不指定检测的元素对象)检测添加缓存机制。

添加了缓存之后,最终完整的hasEvent函数如下:

var hasEvent = (function () { 
var tags = { 
onsubmit: 'form', onreset: 'form', 
onselect: 'input', onchange: 'input', 
onerror: 'img', onload: 'img', onabort: 'img' 
}, 
cache = {}; 

return function(name, element) { 
name = name.indexOf('on') ? 'on' + name : name; 
//命中缓存 
if (!element && name in cache) { 
return cache[name]; 
} 
element = element || document.createElement(tags[name] || 'p'); 
var proto = element.__proto__ || {}, 
supported = name in element, 
temp; 
//处理显示在元素的__proto__上加属性的情况 
if (supported && (temp = proto[name]) && delete proto[name]) { 
supported = name in element; 
proto[name] = temp; 
} 
//处理Firefox不给力的情况 
//Firefox下'onunload' in window是false,但是p有unload事件(OTL) 
if (!supported) { 
if (!element.setAttribute || !element.removeAttribute) { 
element = document.createElement('p'); 
} 
element.setAttribute(name, 'return;'); 
supported = typeof element[name] == 'function'; 
element.removeAttribute(name); 
} 
//添加到缓存 
cache[name] = supported; 
return supported; 
}; 
})();

Mutation Event

Mutation Event是由DOM Level 2制定的一类特殊的事件,这些事件在某个元素为根的DOM树结构发生变化时触发,可以在这里看到具体的事件列表。

遗憾的是hasEvent函数无法检测到Mutation Event,因此对于此类事件,需要另一种较为复杂的事件检测方案。

从Mutation Event的列表中可以发现,此类事件的特点在于当DOM树结构发生变化时才会被触发,因此可以使用下面这套逻辑去检测:

  1. 准备一个标记位,默认为false。

  2. 创建出一个DOM树结构。

  3. 注册一个Mutation Event。

  4. 通过一定手段让这个DOM树变化,从而触发注册的事件。

  5. 在事件处理函数中,将标记位设为true。

  6. 返回标记位。

具体的实现代码可以如下:

function hasMutationEvent(name, tag, change) { 
var element = document.createElement(tag), 
supported = false; 
function handler() { 
supported = true; 
}; 
//IE9开始支持addEventListener,因此只有IE6-8没有这个函数 
//但是IE6-8已经确定不支持Mutation Event,所以有这个判断 
if (!element.addEventListener) { 
return false; 
} 
element.addEventListener(name, handler, false); 
change(element); 
element.removeEventListener(name, handler, false); 
return supported; 
};

例如需要检测DOMAttrModified事件是否存在,只需要用以下代码:

var isDOMAttrModifiedSupported = 
hasMutationEvent('DOMAttrModified', 'p', function (p) { p.id = 'new'; });

对于其他事件的检测,同样只需要制作出一个特定的change函数即可。

DOMContentLoaded

这个事件在文档加载完成时触发,但不需要等待图片等资源下载,多数Javascript框架的document.ready都会试图使用这个事件。

无论是hasEvent函数还是hasMutationEvent函数都无法检测到这个事件,但是问题不大,因为:

  1. 这事件和onload一样,页面的生命周期中只会触发一次,不会频繁使用。

  2. 所有支持addEventListener的浏览器都支持这个事件(包括IE9),因此判断简单。

所以这个事件被排除在了本文讨论范围之外,具体的可以查看各框架的document.ready函数的实现方式。

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.