Event 系列: jquery event 源码

  1. /*
  2. * author:prk
  3. * date:2008-08-17
  4. * comment:analyse of jquery event
  5. *
  6. */
  7. jQuery.event = {
  8. // add 事件到一个元素上。
  9. add : function(elem, types, handler, data) {
  10. if (elem.nodeType == 3 || elem.nodeType == 8)// 空白节点或注释
  11. return;
  12. // IE不能传入window,先复制一下。
  13. if (jQuery.browser.msie && elem.setInterval)
  14. elem = window;
  15. // 为handler分配一个全局唯一的Id
  16. if (!handler.guid)
  17. handler.guid = this.guid++;
  18. // 把data附到handler.data中
  19. if (data != undefined) {
  20. var fn = handler;
  21. handler = this.proxy(fn, function() {// 唯一Id,wrap原始handler Fn
  22. return fn.apply(this, arguments);
  23. });
  24. handler.data = data;
  25. }
  26. // 初始化元素的events。如果没有取到events中值,就初始化data: {}
  27. var events = jQuery.data(elem, "events")
  28. || jQuery.data(elem, "events", {}),
  29. // 如果没有取到handle中值,就初始化data: function() {....}
  30. handle = jQuery.data(elem, "handle")
  31. || jQuery.data(elem, "handle", function() {
  32. // 处理一个触发器的第二个事件和当page已经unload之后调用一个事件。
  33. if (typeof jQuery != "undefined"
  34. && !jQuery.event.triggered)
  35. return jQuery.event.handle.apply(// arguments.callee.elem=handle.elem
  36. arguments.callee.elem, arguments);
  37. });
  38. // 增加elem做为handle属性,防止IE由于没有本地Event而内存泄露。
  39. handle.elem = elem;
  40. // 处理采用空格分隔多个事件名,如jQuery(...).bind("mouseover mouseout", fn);
  41. jQuery.each(types.split(/\s+/), function(index, type) {
  42. // 命名空间的事件,一般不会用到。
  43. var parts = type.split(".");
  44. type = parts[0];
  45. handler.type = parts[1];
  46. // 捆绑到本元素type事件的所有处理函数
  47. var handlers = events[type];
  48. if (!handlers) {// 没有找到处理函数列表就初始化事件队列
  49. handlers = events[type] = {};
  50. // 如果type不是ready,或ready的setup执行返回false
  51. if (!jQuery.event.special[type]
  52. || jQuery.event.special[type].setup
  53. .call(elem, data) === false) {
  54. // 调用系统的事件函数来注册事件
  55. if (elem.addEventListener)// FF
  56. elem.addEventListener(type, handle, false);
  57. elseif (elem.attachEvent)// IE
  58. elem.attachEvent("on" + type, handle);
  59. }
  60. }
  61. // 把处理器的id和handler形式属性对的形式保存在handlers列表中,
  62. // 也存在events[type][handler.guid]中。
  63. handlers[handler.guid] = handler;
  64. // 全局缓存这个事件的使用标识
  65. jQuery.event.global[type] = true;
  66. });
  67. // 防止IE内存泄露。
  68. elem = null;
  69. },
  70. guid : 1,
  71. global : {},
  72. // 从元素中remove一个事件
  73. remove : function(elem, types, handler) {
  74. if (elem.nodeType == 3 || elem.nodeType == 8)
  75. return;
  76. // 取出元素的events中Fn列表
  77. var events = jQuery.data(elem, "events"), ret, index;
  78. if (events) {
  79. // remove所有的该元素的事件 .是命名空间的处理
  80. if (types == undefined
  81. || (typeof types == "string" && types.charAt(0) == "."))
  82. for (var type in events)
  83. this.remove(elem, type + (types || ""));
  84. else {
  85. // types, handler参数采用{type:xxx,handler:yyy}形式
  86. if (types.type) {
  87. handler = types.handler;
  88. types = types.type;
  89. }
  90. // 处理采用空格分隔多个事件名 jQuery(...).unbind("mouseover mouseout", fn);
  91. jQuery
  92. .each(types.split(/\s+/), function(index, type) {
  93. // 命名空间的事件,一般不会用到。
  94. var parts = type.split(".");
  95. type = parts[0];
  96. if (events[type]) {// 事件名找到
  97. if (handler)// handler传入,就remove事件名的这个处理函数
  98. delete events[type][handler.guid];//guid的作用
  99. else // remove这个事件的所有处理函数,带有命名空间的处理
  100. for (handler in events[type])
  101. if (!parts[1]
  102. || events[type][handler].type == parts[1])
  103. delete events[type][handler];
  104. // 如果没有该事件的处理函数存在,就remove事件名
  105. for (ret in events[type])// 看看有没有?
  106. break;
  107. if (!ret) {// 没有
  108. if (!jQuery.event.special[type]//不是ready
  109. || jQuery.event.special[type].teardown
  110. .call(elem) === false) {// type不等于ready
  111. if (elem.removeEventListener)// 在浏览器中remove事件名
  112. elem.removeEventListener(type,
  113. jQuery.data(elem,
  114. "handle"),
  115. false);
  116. elseif (elem.detachEvent)
  117. elem.detachEvent("on" + type,
  118. jQuery.data(elem,
  119. "handle"));
  120. }
  121. ret = null;
  122. delete events[type];// 在缓存中除去。
  123. }
  124. }
  125. });
  126. }
  127. // 不再使用,除去expando
  128. for (ret in events)
  129. break;
  130. if (!ret) {
  131. var handle = jQuery.data(elem, "handle");
  132. if (handle)
  133. handle.elem = null;
  134. jQuery.removeData(elem, "events");
  135. jQuery.removeData(elem, "handle");
  136. }
  137. }
  138. },
  139. trigger : function(type, data, elem, donative, extra) {
  140. data = jQuery.makeArray(data);
  141. if (type.indexOf("!") >= 0) {// 支持!的not的操作如!click,除click之后的所有
  142. type = type.slice(0, -1);// 除最后一个字符?
  143. var exclusive = true;
  144. }
  145. if (!elem) {// 处理全局的fire事件
  146. if (this.global[type])
  147. jQuery.each(jQuery.cache, function() {
  148. // 从cache中找到所有注册该事件的元素,触发改事件的处理函数
  149. if (this.events && this.events[type])
  150. jQuery.event.trigger(type, data, this.handle.elem);
  151. });
  152. } else {// 处理单个元素事件的fire事件
  153. if (elem.nodeType == 3 || elem.nodeType == 8)
  154. return undefined;
  155. var val, ret, fn = jQuery.isFunction(elem[type] || null),
  156. // 我们是否要提交一个伪造的事件?
  157. event = !data[0] || !data[0].preventDefault;
  158. // 构建伪造的事件。
  159. if (event) {
  160. data.unshift( {//存到数组中的第一个
  161. type : type,
  162. target : elem,
  163. preventDefault : function() {
  164. },
  165. stopPropagation : function() {
  166. },
  167. timeStamp : now()
  168. });
  169. data[0][expando] = true; // 不需要修正伪造事件
  170. }
  171. //防止事件名出错
  172. data[0].type = type;
  173. if (exclusive)
  174. data[0].exclusive = true;
  175. // 触发事件
  176. var handle = jQuery.data(elem, "handle");
  177. if (handle)
  178. val = handle.apply(elem, data);
  179. // Handle triggering native .onfoo handlers (and on links since we
  180. // don't call .click() for links)
  181. //处理触发.onfoo这样的本地处理方法,但是是对于links 's .click()不触发
  182. if ((!fn || (jQuery.nodeName(elem, 'a') && type == "click"))
  183. && elem["on" + type]&& elem["on" + type].apply(elem, data) === false)
  184. val = false;
  185. // Extra functions don't get the custom event object
  186. if (event)
  187. data.shift();
  188. // 处理触发extra事件
  189. if (extra && jQuery.isFunction(extra)) {
  190. //执行extra,同时把结果存到data中。
  191. ret = extra.apply(elem, val == null ? data : data.concat(val));
  192. // if anything is returned, give it precedence and have it
  193. // overwrite the previous value
  194. if (ret !== undefined)
  195. val = ret;
  196. }
  197. // 触发本地事件
  198. if (fn && donative !== false && val !== false
  199. && !(jQuery.nodeName(elem, 'a') && type == "click")) {
  200. this.triggered = true;
  201. try {
  202. elem[type]();
  203. //对于一些hidden的元素,IE会报错
  204. } catch (e) {
  205. }
  206. }
  207. this.triggered = false;
  208. }
  209. return val;
  210. },
  211. handle : function(event) {
  212. // 返回 undefined or false
  213. var val, ret, namespace, all, handlers;
  214. event = arguments[0] = jQuery.event.fix(event || window.event);
  215. // 命名空间处理
  216. namespace = event.type.split(".");
  217. event.type = namespace[0];
  218. namespace = namespace[1];
  219. // all = true 表明任何 handler
  220. all = !namespace && !event.exclusive;
  221. // 找到元素的events中缓存的事件名的处理函数列表
  222. handlers = (jQuery.data(this, "events") || {})[event.type];
  223. for (var j in handlers) {// 每个处理函数执行
  224. var handler = handlers[j];
  225. // Filter the functions by class
  226. if (all || handler.type == namespace) {
  227. // 传入引用,为了之后删除它们
  228. event.handler = handler;
  229. event.data = handler.data;
  230. ret = handler.apply(this, arguments);// 执行事件处理函数
  231. if (val !== false)
  232. val = ret;// 只要有一个处理函数返回false,本函数就返回false.
  233. if (ret === false) {// 不执行浏览器默认的动作
  234. event.preventDefault();
  235. event.stopPropagation();
  236. }
  237. }
  238. }
  239. return val;
  240. },
  241. props : "altKey attrChange attrName bubbles button cancelable charCode clientX "
  242. + "clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode "
  243. + "metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX "
  244. + "screenY shiftKey srcElement target timeStamp toElement type view wheelDelta which"
  245. .split(" "),
  246. //对事件进行包裹。
  247. fix : function(event) {
  248. if (event[expando] == true)//表明事件已经包裹过
  249. return event;
  250. //保存原始event,同时clone一个。
  251. var originalEvent = event;
  252. event = {
  253. originalEvent : originalEvent
  254. };
  255. for (var i = this.props.length, prop;i;) {
  256. prop = this.props[--i];
  257. event[prop] = originalEvent[prop];
  258. }
  259. event[expando] = true;
  260. //加上preventDefault and stopPropagation,在clone不会运行
  261. event.preventDefault = function() {
  262. // 在原始事件上运行
  263. if (originalEvent.preventDefault)
  264. originalEvent.preventDefault();
  265. originalEvent.returnValue = false;
  266. };
  267. event.stopPropagation = function() {
  268. // 在原始事件上运行
  269. if (originalEvent.stopPropagation)
  270. originalEvent.stopPropagation();
  271. originalEvent.cancelBubble = true;
  272. };
  273. // 修正 timeStamp
  274. event.timeStamp = event.timeStamp || now();
  275. // 修正target
  276. if (!event.target)
  277. event.target = event.srcElement || document;
  278. if (event.target.nodeType == 3)//文本节点是父节点。
  279. event.target = event.target.parentNode;
  280. // relatedTarget
  281. if (!event.relatedTarget && event.fromElement)
  282. event.relatedTarget = event.fromElement == event.target
  283. ? event.toElement
  284. : event.fromElement;
  285. // Calculate pageX/Y if missing and clientX/Y available
  286. if (event.pageX == null && event.clientX != null) {
  287. var doc = document.documentElement, body = document.body;
  288. event.pageX = event.clientX
  289. + (doc && doc.scrollLeft || body && body.scrollLeft || 0)
  290. - (doc.clientLeft || 0);
  291. event.pageY = event.clientY
  292. + (doc && doc.scrollTop || body && body.scrollTop || 0)
  293. - (doc.clientTop || 0);
  294. }
  295. // Add which for key events
  296. if (!event.which
  297. && ((event.charCode || event.charCode === 0)
  298. ? event.charCode
  299. : event.keyCode))
  300. event.which = event.charCode || event.keyCode;
  301. // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
  302. if (!event.metaKey && event.ctrlKey)
  303. event.metaKey = event.ctrlKey;
  304. // Add which for click: 1 == left; 2 == middle; 3 == right
  305. // Note: button is not normalized, so don't use it
  306. if (!event.which && event.button)
  307. event.which = (event.button & 1 ? 1 : (event.button & 2
  308. ? 3
  309. : (event.button & 4 ? 2 : 0)));
  310. return event;
  311. },
  312. proxy : function(fn, proxy) {
  313. // 作用就是分配全局guid.
  314. proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++;
  315. return proxy;
  316. },
  317. special : {
  318. ready : {
  319. // Make sure the ready event is setup
  320. setup : bindReady,
  321. teardown : function() {
  322. }
  323. }
  324. }
  325. };
  326. if (!jQuery.browser.msie) {
  327. // Checks if an event happened on an element within another element
  328. // Used in jQuery.event.special.mouseenter and mouseleave handlers
  329. var withinElement = function(event) {
  330. // Check if mouse(over|out) are still within the same parent element
  331. var parent = event.relatedTarget;
  332. // Traverse up the tree
  333. while (parent && parent != this)
  334. try {
  335. parent = parent.parentNode;
  336. } catch (e) {
  337. parent = this;
  338. }
  339. if (parent != this) {
  340. // set the correct event type
  341. event.type = event.data;
  342. // handle event if we actually just moused on to a non sub-element
  343. jQuery.event.handle.apply(this, arguments);
  344. }
  345. };
  346. jQuery.each( {
  347. mouseover : 'mouseenter',
  348. mouseout : 'mouseleave'
  349. }, function(orig, fix) {
  350. jQuery.event.special[fix] = {
  351. setup : function() {
  352. jQuery.event.add(this, orig, withinElement, fix);
  353. },
  354. teardown : function() {
  355. jQuery.event.remove(this, orig, withinElement);
  356. }
  357. };
  358. });
  359. }
  360. jQuery.fn.extend( {
  361. bind : function(type, data, fn) {
  362. return type == "unload" ? this.one(type, data, fn) : this
  363. .each(function() {// fn || data, fn && data实现了data参数可有可无
  364. jQuery.event.add(this, type, fn || data, fn && data);
  365. });
  366. },
  367. // 为每一个匹配元素的特定事件(像click)绑定一个一次性的事件处理函数。
  368. // 在每个对象上,这个事件处理函数只会被执行一次。其他规则与bind()函数相同。
  369. // 这个事件处理函数会接收到一个事件对象,可以通过它来阻止(浏览器)默认的行为。
  370. // 如果既想取消默认的行为,又想阻止事件起泡,这个事件处理函数必须返回false。
  371. one : function(type, data, fn) {
  372. var one = jQuery.event.proxy(fn || data, function(event) {
  373. jQuery(this).unbind(event, one);
  374. return (fn || data).apply(this, arguments);// this-->当前的元素
  375. });
  376. returnthis.each(function() {
  377. jQuery.event.add(this, type, one, fn && data);
  378. });
  379. },
  380. // bind()的反向操作,从每一个匹配的元素中删除绑定的事件。
  381. // 如果没有参数,则删除所有绑定的事件。
  382. // 你可以将你用bind()注册的自定义事件取消绑定。
  383. // I如果提供了事件类型作为参数,则只删除该类型的绑定事件。
  384. // 如果把在绑定时传递的处理函数作为第二个参数,则只有这个特定的事件处理函数会被删除。
  385. unbind : function(type, fn) {
  386. returnthis.each(function() {
  387. jQuery.event.remove(this, type, fn);
  388. });
  389. },
  390. trigger : function(type, data, fn) {
  391. returnthis.each(function() {
  392. jQuery.event.trigger(type, data, this, true, fn);
  393. });
  394. },
  395. //这个特别的方法将会触发指定的事件类型上所有绑定的处理函数。但不会执行浏览器默认动作.
  396. triggerHandler : function(type, data, fn) {
  397. returnthis[0]
  398. && jQuery.event.trigger(type, data, this[0], false, fn);
  399. },
  400. //每次点击后依次调用函数。
  401. toggle : function(fn) {
  402. var args = arguments, i = 1;
  403. while (i < args.length)//每个函数分配GUID
  404. jQuery.event.proxy(fn, args[i++]);
  405. returnthis.click(jQuery.event
  406. .proxy(fn, function(event) {//分配GUID
  407. this.lastToggle = (this.lastToggle || 0) % i;//上一个函数
  408. event.preventDefault();//阻止缺省动作
  409. //执行参数中的第几个函数,apply可以采用array-like的参数
  410. //With apply, you can use an array literal,
  411. //for example, fun.apply(this, [name, value]),
  412. //or an Array object, for example, fun.apply(this, new Array(name, value)).
  413. return args[this.lastToggle++].apply(this,
  414. arguments) || false;
  415. }));
  416. },
  417. //一个模仿悬停事件(鼠标移动到一个对象上面及移出这个对象)的方法。
  418. //这是一个自定义的方法,它为频繁使用的任务提供了一种“保持在其中”的状态。
  419. //当鼠标移动到一个匹配的元素上面时,会触发指定的第一个函数。当鼠标移出这个元素时,
  420. //会触发指定的第二个函数。而且,会伴随着对鼠标是否仍然处在特定元素中的检测(例如,处在div中的图像),
  421. //如果是,则会继续保持“悬停”状态,而不触发移出事件(修正了使用mouseout事件的一个常见错误)。
  422. hover : function(fnOver, fnOut) {
  423. returnthis.bind('mouseenter', fnOver).bind('mouseleave', fnOut);
  424. },
  425. //dom ready时执行 fn
  426. ready : function(fn) {
  427. bindReady();//注册监听
  428. if (jQuery.isReady)//ready就运行
  429. fn.call(document, jQuery);
  430. else
  431. // 增加这个函数到queue中。可见支持无数的ready的调用。
  432. jQuery.readyList.push(function() {
  433. return fn.call(this, jQuery);
  434. });
  435. returnthis;
  436. }
  437. });
  438. jQuery.extend( {
  439. isReady : false,
  440. readyList : [],
  441. // Handle when the DOM is ready
  442. ready : function() {
  443. if (!jQuery.isReady) {
  444. jQuery.isReady = true;
  445. if (jQuery.readyList) {
  446. jQuery.each(jQuery.readyList, function() {
  447. this.call(document);
  448. });
  449. jQuery.readyList = null;
  450. }
  451. jQuery(document).triggerHandler("ready");
  452. }
  453. }
  454. });
  455. var readyBound = false;
  456. function bindReady() {
  457. if (readyBound)
  458. return;
  459. readyBound = true;
  460. // Mozilla, Opera, webkit nightlies 支持DOMContentLoaded事件
  461. if (document.addEventListener && !jQuery.browser.opera)
  462. //当DOMContentLoaded事件触发时就运行jQuery.ready
  463. document.addEventListener("DOMContentLoaded", jQuery.ready, false);
  464. //IE或不是frame的window
  465. if (jQuery.browser.msie && window == top)
  466. (function() {
  467. if (jQuery.isReady)
  468. return;
  469. try {
  470. // 在ondocumentready之前,一直都会抛出异常
  471. // http://javascript.nwbox.com/IEContentLoaded/
  472. document.documentElement.doScroll("left");
  473. } catch (error) {
  474. //一直运行bindReady()(=arguments.callee)
  475. setTimeout(arguments.callee, 0);
  476. return;
  477. }
  478. jQuery.ready();//documentready就运行jQuery.ready
  479. })();
  480. if (jQuery.browser.opera)
  481. document.addEventListener("DOMContentLoaded", function() {
  482. if (jQuery.isReady)
  483. return;
  484. //只有styleSheets完全enable时,才是完全的load,其实还有pic
  485. for (var i = 0;i < document.styleSheets.length; i++)
  486. if (document.styleSheets[i].disabled) {//通过styleSheets来判断
  487. setTimeout(arguments.callee, 0);
  488. return;
  489. }
  490. jQuery.ready();
  491. }, false);
  492. if (jQuery.browser.safari) {
  493. var numStyles;
  494. (function() {
  495. if (jQuery.isReady)
  496. return;
  497. //首先得得到readyState=loaded或=complete
  498. if (document.readyState != "loaded"
  499. && document.readyState != "complete") {
  500. setTimeout(arguments.callee, 0);
  501. return;
  502. }
  503. //取得style的length,比较它们之间的长度,看看是不是完成loaded
  504. if (numStyles === undefined)
  505. numStyles = jQuery("style, link[rel=stylesheet]").length;
  506. if (document.styleSheets.length != numStyles) {
  507. setTimeout(arguments.callee, 0);
  508. return;
  509. }
  510. jQuery.ready();
  511. })();
  512. }
  513. //最后只能依赖于window.load.
  514. jQuery.event.add(window, "load", jQuery.ready);
  515. }
  516. //为jquery对象增加常用的事件方法
  517. jQuery
  518. .each(
  519. ("blur,focus,load,resize,scroll,unload,click,dblclick,"
  520. + "mousedown,mouseup,mousemove,mouseover,mouseout,change,select," + "submit,keydown,keypress,keyup,error")
  521. .split(","), function(i, name) {
  522. jQuery.fn[name] = function(fn) {
  523. return fn ? this.bind(name, fn) : this.trigger(name);
  524. };
  525. });
  526. // Prevent memory leaks in IE
  527. // And prevent errors on refresh with events like mouseover in other browsers
  528. // Window isn't included so as not to unbind existing unload events
  529. jQuery(window).bind('unload', function() {
  530. for (var id in jQuery.cache)
  531. // Skip the window
  532. if (id != 1 && jQuery.cache[id].handle)
  533. jQuery.event.remove(jQuery.cache[id].handle.elem);
  534. });