>  기사  >  웹 프론트엔드  >  myEvent.js javascript 크로스 브라우저 이벤트 Framework_javascript 기술

myEvent.js javascript 크로스 브라우저 이벤트 Framework_javascript 기술

WBOY
WBOY원래의
2016-05-16 18:00:321370검색

이벤트가 얼마나 복잡합니까? 6년 전 선배들의 노력이 최고의 addEvent는 어떻게 탄생했는지, 그리고 떠오르는 스타 jQuery도 힘들게 얻은 코드(v 1.5.1)를 1,600라인 이상 소비했다는 것을 알 수 있습니다. 6년 뒤에 나타난 문제를 해결하다 다양한 코어 브라우저.

저는 선배들의 코드와 제가 이해한 것을 참고하여 이벤트 프레임워크를 작성하려고 했습니다. 제 프레임워크는 이벤트 메커니즘의 핵심을 완성하여 다중 이벤트 바인딩을 구현하는 데 필요한 통합 인터페이스를 제공할 수 있습니다. 메모리 누수 등의 문제가 있으며 더 중요한 것은 성능이 꽤 좋습니다.

내 방법:

모든 콜백 함수는 콜백 함수의 요소, 이벤트 유형 및 고유 ID를 기반으로 _create 개체에 캐시됩니다(내부 구조는 아래 소스 코드에서 볼 수 있습니다. _cache에 대한 참고 사항).
이벤트 바인딩은 _create 프록시 함수를 사용하여 처리되며, 이를 통해 요소의 모든 유형의 이벤트가 배포되고, Apply 메소드를 사용하여 IE의 포인터가 해당 요소를 가리키도록 합니다.
Array Queue를 통해 IE 콜백 함수 실행 순서 문제를 해결합니다.
수정 함수는 콜백 함수에 전달된 이벤트 매개변수와 기타 호환성 문제를 처리합니다. jQuery.event.fix는 여기서 참조됩니다.
메모리 누수를 방지하려면 이벤트와 요소 사이의 순환 참조를 분리하세요.
1. 핵심 구현:

코드 복사 코드는 다음과 같습니다.

// myEvent 0.2
// 2011.04.06 - TangBin - planeart.cn - MIT Licensed
/**
* 이벤트 프레임
* @namespace
* @see http://www.planeart.cn/?p=1285
*/
var myEvent = (function () {
var _fid = 1,
_guid = 1,
_time = (새 날짜).getTime(),
_nEid = '{$eid}' _time,
_nFid = '{$fid }' _time,
_DOM = document.addEventListener,
_noop = 함수() {},
_create = 함수(guid) {
반환 함수(이벤트) {
event = api. fix(event || window.event);
var i = 0,
type = (event || (event = document.event)).type,
elem = _cache[guid].elem,
데이터 = 인수,
events = _cache[guid].events[type]
for (; i < events.length; i ) {
if (events[i].apply( elem, 데이터) === false) event.preventDefault();
},
_cache = {/*
1: {
elem: ( HTMLElement),
이벤트: {
클릭: [(함수), (..)],
(..)
},
리스너: (함수)
},
(..)
*/};
var api = {
/**
* 이벤트 바인딩
* @param {HTMLElement} 요소
* @param {String} 이벤트 이름
* 바인딩할 @param {Function} 함수
*/
bind: 함수(요소, 유형, 콜백) {
var guid = elem[_nEid] || (elem[_nEid] = _guid );
if (!_cache[guid]) _cache[guid] = {
elem,
listener: _create(guid) ,
이벤트: {}
};
if (type && !_cache[guid].events[type]) {
_cache[guid].events[type] = [];
api.add(elem, type, _cache[guid].listener);
};
if (콜백) {
if (!callback[_nFid]) 콜백[_nFid] = _fid ;
_cache[guid].events[유형].push(콜백);
} else
return _cache[guid];
},
/**
* 이벤트 언로드
* @param {HTMLElement} 요소
* @param {String} 이벤트 이름
* 언로드할 @param {Function} 함수
*/
바인딩 해제: 함수(elem, 유형, 콜백) {
var events, i, list,
guid = elem[_nEid],
핸들러 = _cache[guid];
(!handler)가 반환되는 경우;
이벤트 = handler.events;
if(콜백) {
list = 이벤트[유형];
(!list)가 반환되는 경우;
for (i = 0; i < list.length; i ) {
list[i][_nFid] === callback[_nFid] && list.splice(i--, 1);
};
if (list.length === 0) return api.unbind(elem, type);
} else if (유형) {
이벤트 삭제[유형];
api.remove(elem, type, handler.listener);
} else {
for (i in events) {
api.remove(elem, i, handler.listener);
};
_cache[guid] 삭제;
};
},
/**
* 이벤트 트리거(참고: 브라우저 기본 동작 및 버블링은 트리거되지 않음)
* @param {HTMLElement} 요소
* @param {String} 이벤트 이름
* @param {Array} ( 선택사항) 추가 데이터
*/
triggerHandler: 함수(elem, 유형, 데이터) {
var guid = elem[_nEid],
event = {
유형: 유형,
대상: elem,
현재 대상: elem,
preventDefault: _noop,
stopPropagation: _noop
};
데이터 = 데이터 || [];
data.unshift(이벤트);
guid && _cache[guid].listener.apply(elem, data);
try {
elem['on' 유형] && elem['on' 유형].apply(elem, data);
//elem[유형] && elem[유형]();
} catch (e) {};
},
// 原生事件绑정接구
추가: _DOM ? 함수(elem, 유형, 리스너) {
elem.addEventListener(유형, 리스너, false);
} : 함수(elem, 유형, 리스너) {
elem.attachEvent('on' 유형, 리스너);
},
// 原生事件卸载接口
제거: _DOM ? 함수(elem, 유형, 리스너) {
elem.removeEventListener(유형, 리스너, false);
} : 함수(elem, 유형, 리스너) {
elem.detachEvent('on' 유형, 리스너);
},
// 수정
fix: function (event) {
if (_DOM) return event;
var 이름,
newEvent = {},
doc = document.documentElement,
body = document.body;
newEvent.target = event.srcElement || 문서;
newEvent.target.nodeType === 3 && (newEvent.target = newEvent.target.parentNode);
newEvent.preventDefault = function () {event.returnValue = false};
newEvent.stopPropagation = function() {event.cancelBubble = true};
newEvent.pageX = newEvent.clientX (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
newEvent.pageY = newEvent.clientY (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
newEvent.관련Target = event.fromElement === newEvent.target ? event.toElement : 이벤트.fromElement;
// !!IE写event会极其容易导致内存泄漏,Firefox写event会报错
// 拷贝event
for (이벤트 이름) newEvent[name] = event[name];
newEvent를 반환합니다.
}
};
API 반환;
})();


我给一万个元素绑定事件进行了测试,测试工具为sIEve,结果:
事件框架 耗时 内存
IE8
jQuery.bind 1064 MS 79.80 MB
myEvent.bind 623 MS 35.82 MB
IE6
jQuery.bind 2503 MS 74.23 MB
myEvent.bind 1810 MS 28.48 MB

myEvent는 실행 효율성과 메모리 사용량 측면에서 특정 이점을 갖고 있음을 알 수 있습니다. 이는 jQuery 이벤트 메커니즘이 너무 강력하여 성능이 저하되기 때문일 수 있습니다.
테스트 샘플: http://www.planeart.cn/demo/myEvent/

2. 사용자 정의 이벤트 메커니즘 확장
jQuery는 사용자 정의 이벤트를 저장하기 위해 특별한 네임스페이스를 사용합니다. 위의 코드를 기반으로 jQuery 사용자 정의 이벤트를 모방했습니다. 유명한 Ready 이벤트와 또 다른 jQuery hashchange 이벤트 플러그인이 있습니다.

이 두 가지 맞춤 이벤트는 매우 중요합니다. Ready 이벤트는 DOM이 준비되면 이벤트를 요소에 바인딩할 수 있으며, 이는 window.onload의 기존 사용보다 훨씬 빠릅니다. hashchange 이벤트는 앵커 포인트 변경을 모니터링할 수 있으며 자주 사용됩니다. 예를 들어, 새 버전의 Twitter에서는 AJAX를 처리하기 위해 이 방법을 사용합니다. AJAX 애플리케이션의 사용자 경험을 향상시키는 것 외에도 특정 규칙을 따르는 경우 앵커 메커니즘을 사용하여 Google에서 색인을 생성할 수도 있습니다.

물론, 이전 글에서 구현한 imgReady 이벤트도 이 확장을 통해 포함될 수 있으며 추후 업데이트될 예정입니다.

코드 복사 코드는 다음과 같습니다.

// myEvent 0.2.2
// 2011.04.07 - TangBin - planeart.cn - MIT 라이센스
/**
* 이벤트 프레임
* @namespace
* @see http://www.planeart.cn/?p=1285
*/
var myEvent = (함수 () {
var _ret, _name,
_fid = 1,
_guid = 1,
_time = (새 날짜).getTime(),
_nEid = '{$eid}' _time,
_nFid = '{$fid}' _time,
_DOM = document.addEventListener,
_noop = 함수() {},
_create = 함수(guid) {
반환 함수(이벤트) ) {
event = myEvent.fix(event || window.event);
var type = (event || (event = document.event)).type,
elem = _cache[guid]. elem,
데이터 = 인수,
events = _cache[guid].events[type],
i = 0,
length = events.length
for (; i < length; ; i ) {
if (events[i].apply(elem, data) === false) event.preventDefault()
event = elem
};
},
_cache = {/*
1: {
elem: (HTMLElement),
events: {
click: [(함수), (..)],
(..)
},
리스너: (함수)
},
(..)
*/}
var API = 함수 () {} ;
API.prototype = {
/**
* 이벤트 바인딩
* @param {HTMLElement} 요소
* @param {String} 이벤트 이름
* 바인딩할 @param {Function} 함수
*/
bind: 함수(elem, 유형, 콜백) {
var 이벤트, 리스너,
guid = elem[_nEid ] || (elem[_nEid] = _guid ),
special = this.special[type] || {},
cacheData = _cache[guid];
if (!cacheData) 캐시데이터 = _cache[guid] = {
elem: elem,
listener: _create(guid),
events: {}
};
이벤트 = 캐시데이터.이벤트;
리스너 = 캐시데이터.리스너;
if (!events[type]) events[type] = [];
if (!callback[_nFid]) 콜백[_nFid] = _fid ;
if (!special.setup || 특수.setup.call(elem, 리스너) === false) {
events[type].length === 0 && this.add(elem, 유형, 리스너 );
};
이벤트[유형].push(콜백);
},
/**
* 이벤트 언로드
* @param {HTMLElement} 요소
* @param {String} 이벤트 이름
* 언로드할 @param {Function} 함수
*/
바인딩 해제: 함수(elem, 유형, 콜백) {
var events, Special, i, list, fid,
guid = elem [_nEid],
cacheData = _cache[guid];
(!cacheData)가 반환되는 경우;
이벤트 = 캐시데이터.이벤트;
if(콜백) {
list = 이벤트[유형];
fid = 콜백[_nFid];
(!list)가 반환되는 경우;
for (i = 0; i < list.length; i ) {
list[i][_nFid] === fid && list.splice(i--, 1);
};
if (!list.length) this.unbind(elem, type);
} else if (유형) {
special = this.special[type] || {};
if (!special.teardown || Special.teardown.call(elem) === false) {
this.remove(elem, type,cacheData.listener);
};
이벤트 삭제[유형];
} else {
for (i in events) {
this.remove(elem, i,cacheData.listener);
};
_cache[guid] 삭제;
};
},
/**
* 이벤트 트리거(참고: 브라우저 기본 동작 및 버블링은 트리거되지 않음)
* @param {HTMLElement} 요소
* @param {String} 이벤트 이름
* @param {Array} ( 선택사항) 추가 데이터
*/
triggerHandler: 함수(elem, 유형, 데이터) {
var guid = elem[_nEid],
cacheData = _cache[guid] ,
event = {
유형: 유형,
대상: elem,
currentTarget: elem,
preventDefault: _noop,
stopPropagation: _noop
};
데이터 = 데이터 || [];
data.unshift(이벤트);
cacheData && 캐시데이터.이벤트[유형] && _cache[guid].listener.apply(elem, data);
try {
elem['on' 유형] && elem['on' 유형].apply(elem, data);
//elem[유형] && elem[유형]();
} catch (e) {};
},
// 自定义事件接口
특수: {},
// 原生事件绑定接口
추가: _DOM ? 함수(elem, 유형, 리스너) {
elem.addEventListener(유형, 리스너, false);
} : 함수(elem, 유형, 리스너) {
elem.attachEvent('on' 유형, 리스너);
},
// 原生事件卸载接口
제거: _DOM ? 함수(elem, 유형, 리스너) {
elem.removeEventListener(유형, 리스너, false);
} : 함수(elem, 유형, 리스너) {
elem.detachEvent('on' 유형, 리스너);
},
// 수정
fix: function (event) {
if (_DOM) return event;
var 이름,
newEvent = {},
doc = document.documentElement,
body = document.body;
newEvent.target = event.srcElement || 문서;
newEvent.target.nodeType === 3 && (newEvent.target = newEvent.target.parentNode);
newEvent.preventDefault = function () {event.returnValue = false};
newEvent.stopPropagation = 함수() {event.cancelBubble = true};
newEvent.pageX = newEvent.clientX (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
newEvent.pageY = newEvent.clientY (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
newEvent.관련Target = event.fromElement === newEvent.target ? event.toElement : 이벤트.fromElement;
// !!直接写event IE导致内存泄漏,Firefox会报错
// 伪装event
for (이벤트 이름) newEvent[name] = event[name];
newEvent를 반환합니다.
}
};
새 API()를 반환합니다.
})();
// DOM就绪事件
myEvent.ready = (function () {
var ReadyList = [], DOMContentLoaded,
readyBound = false, isReady = false;
function Ready () {
if (!isReady) {
if (!document.body) return setTimeout(ready, 13);
isReady = true
if (readyList) {
var fn, i = 0;
while ((fn = ReadyList[i ])) {
fn.call(document, {})
readyList =
; };
};
function binReady() {
if (readyBound) return;
readyBound = true
if (document.readyState === 'complete') return Ready();
if (document.addEventListener) {
document.addEventListener('DOMContentLoaded', DOMContentLoaded, false)
window.addEventListener('load', 준비, false );
} else if (document.attachEvent) {
document.attachEvent('onreadystatechange', DOMContentLoaded)
window.attachEvent('onload', Ready)
var toplevel = false;
try {
toplevel = window.frameElement == null;
} catch (e) {}
if (document.documentElement.doScroll && toplevel) {
doScrollCheck();
};
};
};
myEvent.special.ready = {
setup: binReady,
teardown: function () {}
};
if (document.addEventListener) {
DOMContentLoaded = function () {
document.removeEventListener('DOMContentLoaded', DOMContentLoaded, false);
준비();
};
} else if (document.attachEvent) {
DOMContentLoaded = function () {
if (document.readyState === 'complete') {
document.detachEvent('onreadystatechange', DOMContentLoaded) ;
준비();
};
};
};
function doScrollCheck () {
if (isReady) return;
시도해 보세요 {
document.documentElement.doScroll('left');
} catch (e) {
setTimeout(doScrollCheck, 1);
반환;
};
준비();
};
반환 함수(콜백) {
bindReady();
if (isReady) {
callback.call(document, {});
} else if (readyList) {
readyList.push(콜백);
};
이것을 돌려주세요;
};
})();
// Hashchange Event v1.3
(function (window, undefine) {
var config = {
delay: 50,
src: null,
domain: null
},
str_hashchange = 'hashchange',
doc = 문서,
isIE = !-[1,],
fake_onhashchange, 특수 = myEvent.special,
doc_mode = doc.documentMode ,
supports_onhashchange = 'on' 창의 str_hashchange && (doc_mode === 정의되지 않음 || doc_mode > 7)
function get_fragment(url) {
url = url || >return '#' url.replace(/^[^#]*#?(.*)$/, '$1');
}
special[str_hashchange] = {
설정: 함수 () {
if (supports_onhashchange) return false
myEvent.ready(fake_onhashchange.start);
},
teardown: function () {
if (supports_onhashchange) return false; 🎜>myEvent.ready(fake_onhashchange.stop)
}
}
/**@안의*/
fake_onhashchange = (function () {
var self = {},
timeout_id, last_hash = get_fragment(),
/**@안의*/
fn_retval = function(val) {
return val;
},
history_set = fn_retval,
history_get = fn_retval;
self.start = function () {
timeout_id || 투표();
};
self.stop = function () {
timeout_id &&clearTimeout(timeout_id);
timeout_id = 정의되지 않음;
};
function poll() {
var hash = get_fragment(),
history_hash = History_get(last_hash);
if (hash !== last_hash) {
history_set(last_hash = hash, History_hash);
myEvent.triggerHandler(window, str_hashchange);
} else if (history_hash !== last_hash) {
location.href = location.href.replace(/#.*/, '') History_hash;
};
timeout_id = setTimeout(poll, config.delay);
};
isIE && !supports_onhashchange && (function () {
var iframe,iframe_src, iframe_window;
self.start = function () {
if (!iframe) {
iframe_src = config. src;
iframe_src = iframe_src && iframe_src get_fragment()
iframe = doc.createElement('');
myEvent.bind(iframe, 'load', function () {
myEvent.unbind(iframe, 'load');
iframe_src || History_set(get_fragment())
poll()>});
doc.getElementsByTagName('html')[0].appendChild(iframe);
iframe_window = iframe.contentWindow;
doc.onpropertychange = function () {
try {
if (event.propertyName === 'title') {
iframe_window.document.title = doc.title;
};
} catch (e) {};
};
};
};
self.stop = fn_retval;
/**@안의*/
history_get = function () {
return get_fragment(iframe_window.location.href);
};
/**@안의*/
history_set = 함수(해시, 히스토리_해시) {
var iframe_doc = iframe_window.document,
domain = config.domain;
if (hash !== History_hash) {
iframe_doc.title = doc.title;
iframe_doc.open();
도메인 && iframe_doc.write('<SCRIPT>document.domain="' 도메인 '"</SCRIPT>');
iframe_doc.close();
iframe_window.location.hash = 해시;
};
};
})();
본인 반환;
})();
})(이것);

ready事件是伪事件,调用方式:

复system代码 代码如下:
myEvent.ready(function () {
//[code..]
});

hashchange사건:
复system代码 代码如下:
myEvent.bind(window, 'hashchange', function () {
//[code..]
});
更优雅的兼容
(BELLEVE INVIS)
성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.