From d1536e7c8bf60549096138d08953a43190c7b1a6 Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Mon, 4 Aug 2014 20:05:55 -0700 Subject: [PATCH 01/10] perf(jqLite): don't recreate the Node.contains polyfill --- src/jqLite.js | 48 +++++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/src/jqLite.js b/src/jqLite.js index 12d60940c497..795a65045205 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -156,6 +156,30 @@ wrapMap.optgroup = wrapMap.option; wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; wrapMap.th = wrapMap.td; + +var elementContains = document.head.contains || + document.head.compareDocumentPosition + ? function( a, b ) { + // jshint bitwise: false + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } + : function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + function jqLiteIsTextNode(html) { return !HTML_REGEXP.test(html); } @@ -782,28 +806,6 @@ forEach({ if (!eventFns) { if (type == 'mouseenter' || type == 'mouseleave') { - var contains = document.body.contains || document.body.compareDocumentPosition ? - function( a, b ) { - // jshint bitwise: false - var adown = a.nodeType === 9 ? a.documentElement : a, - bup = b && b.parentNode; - return a === bup || !!( bup && bup.nodeType === 1 && ( - adown.contains ? - adown.contains( bup ) : - a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - )); - } : - function( a, b ) { - if ( b ) { - while ( (b = b.parentNode) ) { - if ( b === a ) { - return true; - } - } - } - return false; - }; - events[type] = []; // Refer to jQuery's implementation of mouseenter & mouseleave @@ -815,7 +817,7 @@ forEach({ var target = this, related = event.relatedTarget; // For mousenter/leave call the handler if related is outside the target. // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || (related !== target && !contains(target, related)) ){ + if ( !related || (related !== target && !elementContains(target, related)) ){ handle(event, type); } }); From 6c4d601ff610cb02943cd06a9696e41424b4eee5 Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Mon, 4 Aug 2014 20:07:48 -0700 Subject: [PATCH 02/10] refactor(jqLite): drop Node.contains polyfill Node.contains is supported on IE5+ and all the other browsers we care about. --- src/jqLite.js | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/src/jqLite.js b/src/jqLite.js index 795a65045205..d11360ddd2ee 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -157,29 +157,6 @@ wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.the wrapMap.th = wrapMap.td; -var elementContains = document.head.contains || - document.head.compareDocumentPosition - ? function( a, b ) { - // jshint bitwise: false - var adown = a.nodeType === 9 ? a.documentElement : a, - bup = b && b.parentNode; - return a === bup || !!( bup && bup.nodeType === 1 && ( - adown.contains ? - adown.contains( bup ) : - a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - )); - } - : function( a, b ) { - if ( b ) { - while ( (b = b.parentNode) ) { - if ( b === a ) { - return true; - } - } - } - return false; - }; - function jqLiteIsTextNode(html) { return !HTML_REGEXP.test(html); } @@ -817,7 +794,7 @@ forEach({ var target = this, related = event.relatedTarget; // For mousenter/leave call the handler if related is outside the target. // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || (related !== target && !elementContains(target, related)) ){ + if ( !related || (related !== target && !target.contains(related)) ){ handle(event, type); } }); From 2a5dbbdc52c56bb738d846a63b29098783e5b389 Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Mon, 4 Aug 2014 20:13:15 -0700 Subject: [PATCH 03/10] refactor(jqLite): don't recreate mouse event map --- src/jqLite.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jqLite.js b/src/jqLite.js index d11360ddd2ee..6355187596c8 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -122,6 +122,7 @@ function jqNextId() { return ++jqId; } var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; var MOZ_HACK_REGEXP = /^moz([A-Z])/; +var MOUSE_EVENT_MAP= { mouseleave : "mouseout", mouseenter : "mouseover"}; var jqLiteMinErr = minErr('jqLite'); /** @@ -788,9 +789,8 @@ forEach({ // Refer to jQuery's implementation of mouseenter & mouseleave // Read about mouseenter and mouseleave: // http://www.quirksmode.org/js/events_mouse.html#link8 - var eventmap = { mouseleave : "mouseout", mouseenter : "mouseover"}; - onFn(element, eventmap[type], function(event) { + onFn(element, MOUSE_EVENT_MAP[type], function(event) { var target = this, related = event.relatedTarget; // For mousenter/leave call the handler if related is outside the target. // NB: No relatedTarget if the mouse left/entered the browser window From 01b10d7a24b371e667dfd5b081e98a690b0fc426 Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Mon, 4 Aug 2014 20:14:15 -0700 Subject: [PATCH 04/10] refactor(jqLite): remove code duplication --- src/jqLite.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/jqLite.js b/src/jqLite.js index 6355187596c8..00fe1cc4d15d 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -783,9 +783,9 @@ forEach({ var eventFns = events[type]; if (!eventFns) { - if (type == 'mouseenter' || type == 'mouseleave') { - events[type] = []; + events[type] = []; + if (type == 'mouseenter' || type == 'mouseleave') { // Refer to jQuery's implementation of mouseenter & mouseleave // Read about mouseenter and mouseleave: // http://www.quirksmode.org/js/events_mouse.html#link8 @@ -801,7 +801,6 @@ forEach({ } else { addEventListenerFn(element, type, handle); - events[type] = []; } eventFns = events[type]; } From 960a8410515b2d7d461d7c95e8a2ca3d75129087 Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Mon, 4 Aug 2014 20:16:06 -0700 Subject: [PATCH 05/10] perf(jqLite): don't use forEach in #off off() is called on each element removal, so we want to make it as fast as possible --- src/jqLite.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/jqLite.js b/src/jqLite.js index 00fe1cc4d15d..0bdc6f15df07 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -262,16 +262,21 @@ function jqLiteDealoc(element, onlyDescendants){ function jqLiteOff(element, type, fn, unsupported) { if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument'); - var events = jqLiteExpandoStore(element, 'events'), - handle = jqLiteExpandoStore(element, 'handle'); + var events = jqLiteExpandoStore(element, 'events'); + var handle = jqLiteExpandoStore(element, 'handle'); + var i; + var types; if (!handle) return; //no listeners registered if (isUndefined(type)) { - forEach(events, function(eventHandler, type) { - removeEventListenerFn(element, type, eventHandler); + types = Object.keys(events); + i = types.length; + while (i--) { + type = types[i]; + removeEventListenerFn(element, type, events[type]); delete events[type]; - }); + } } else { forEach(type.split(' '), function(type) { if (isUndefined(fn)) { From e9cd6dc055cb7bd80ae9232d8985b2bc3999135e Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Mon, 4 Aug 2014 21:01:41 -0700 Subject: [PATCH 06/10] perf(jqLite): improve createEventHandler method by switching from forEach to for loop --- src/jqLite.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/jqLite.js b/src/jqLite.js index 0bdc6f15df07..65bfded58d07 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -740,9 +740,9 @@ function createEventHandler(element, events) { // Copy event handlers in case event handlers array is modified during execution. var eventHandlersCopy = shallowCopy(events[type || event.type] || []); - forEach(eventHandlersCopy, function(fn) { - fn.call(element, event); - }); + for (var i = 0, ii = eventHandlersCopy.length; i < ii; i++) { + eventHandlersCopy[i].call(element, event); + } // Remove monkey-patched methods (IE), // as they would cause memory leaks in IE8. From 274e9c4ddfd64138d39fcf84047aabc3ccde2f0b Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Mon, 4 Aug 2014 21:02:55 -0700 Subject: [PATCH 07/10] perf($compile): optimize publicLinkFn --- src/ng/compile.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/ng/compile.js b/src/ng/compile.js index 175efc13211e..95c504502b86 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -877,9 +877,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { ? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!! : $compileNodes; - forEach(transcludeControllers, function(instance, name) { - $linkNode.data('$' + name + 'Controller', instance); - }); + if (transcludeControllers) { + var names = Object.keys(transcludeControllers); + var i = names.length; + var name; + + while (i--) { + name = names[i]; + $linkNode.data('$' + name + 'Controller', transcludeControllers[name]); + } + } $linkNode.data('$scope', scope); From 566f1015d27118d259e0886910d6b73b3cb0eb10 Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Mon, 4 Aug 2014 21:39:17 -0700 Subject: [PATCH 08/10] perf(jqLite): optimize event listener registration --- src/jqLite.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/jqLite.js b/src/jqLite.js index 65bfded58d07..f7552a307724 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -784,13 +784,17 @@ forEach({ if (!events) jqLiteExpandoStore(element, 'events', events = {}); if (!handle) jqLiteExpandoStore(element, 'handle', handle = createEventHandler(element, events)); - forEach(type.split(' '), function(type){ + var types = type.split(' '); + var i = types.length; + + while (i--) { + type = types[i]; var eventFns = events[type]; if (!eventFns) { events[type] = []; - if (type == 'mouseenter' || type == 'mouseleave') { + if (type === 'mouseenter' || type === 'mouseleave') { // Refer to jQuery's implementation of mouseenter & mouseleave // Read about mouseenter and mouseleave: // http://www.quirksmode.org/js/events_mouse.html#link8 @@ -810,7 +814,7 @@ forEach({ eventFns = events[type]; } eventFns.push(fn); - }); + } }, off: jqLiteOff, From 8f10dca300c7bcc55ff1613326b84b9c460ca7c2 Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Mon, 4 Aug 2014 22:23:55 -0700 Subject: [PATCH 09/10] refactor(jqLite): remove legacy code to support IE8 and older --- src/jqLite.js | 111 ++++++++++---------------------------------------- 1 file changed, 21 insertions(+), 90 deletions(-) diff --git a/src/jqLite.js b/src/jqLite.js index f7552a307724..a3442e6cde34 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -102,17 +102,17 @@ JQLite.expando = 'ng339'; var jqCache = JQLite.cache = {}, jqId = 1, - addEventListenerFn = (window.document.addEventListener - ? function(element, type, fn) {element.addEventListener(type, fn, false);} - : function(element, type, fn) {element.attachEvent('on' + type, fn);}), - removeEventListenerFn = (window.document.removeEventListener - ? function(element, type, fn) {element.removeEventListener(type, fn, false); } - : function(element, type, fn) {element.detachEvent('on' + type, fn); }); + addEventListenerFn = function(element, type, fn) { + element.addEventListener(type, fn, false); + }, + removeEventListenerFn = function(element, type, fn) { + element.removeEventListener(type, fn, false); + }; /* * !!! This is an undocumented "private" function !!! */ -var jqData = JQLite._data = function(node) { +JQLite._data = function(node) { //jQuery always returns an object on cache miss return this.cache[node[this.expando]] || {}; }; @@ -169,7 +169,7 @@ function jqLiteAcceptsData(node) { } function jqLiteBuildFragment(html, context) { - var elem, tmp, tag, wrap, + var tmp, tag, wrap, fragment = context.createDocumentFragment(), nodes = [], i; @@ -319,7 +319,7 @@ function jqLiteExpandoStore(element, key, value) { } expandoStore[key] = value; } else { - return expandoStore && expandoStore[key]; + return key ? expandoStore && expandoStore[key] : expandoStore; } } @@ -456,23 +456,11 @@ function jqLiteRemove(element, keepData) { ////////////////////////////////////////// var JQLitePrototype = JQLite.prototype = { ready: function(fn) { - var fired = false; - - function trigger() { - if (fired) return; - fired = true; - fn(); - } - // check if document already is loaded if (document.readyState === 'complete'){ - setTimeout(trigger); + setTimeout(fn); } else { - this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 - // we can not use jqLite since we are not done loading and jQuery could be loaded later. - // jshint -W064 - JQLite(window).on('load', trigger); // fallback to window.onload for others - // jshint +W064 + this.on('DOMContentLoaded', fn); } }, toString: function() { @@ -562,22 +550,7 @@ forEach({ if (isDefined(value)) { element.style[name] = value; } else { - var val; - - if (msie <= 8) { - // this is some IE specific weirdness that jQuery 1.6.4 does not sure why - val = element.currentStyle && element.currentStyle[name]; - if (val === '') val = 'auto'; - } - - val = val || element.style[name]; - - if (msie <= 8) { - // jquery weirdness :-/ - val = (val === '') ? undefined : val; - } - - return val; + return element.style[name]; } }, @@ -708,33 +681,10 @@ forEach({ function createEventHandler(element, events) { var eventHandler = function (event, type) { - if (!event.preventDefault) { - event.preventDefault = function() { - event.returnValue = false; //ie - }; - } - - if (!event.stopPropagation) { - event.stopPropagation = function() { - event.cancelBubble = true; //ie - }; - } - - if (!event.target) { - event.target = event.srcElement || document; - } - - if (isUndefined(event.defaultPrevented)) { - var prevent = event.preventDefault; - event.preventDefault = function() { - event.defaultPrevented = true; - prevent.call(event); - }; - event.defaultPrevented = false; - } + // jQuery specific api event.isDefaultPrevented = function() { - return event.defaultPrevented || event.returnValue === false; + return event.defaultPrevented; }; // Copy event handlers in case event handlers array is modified during execution. @@ -743,21 +693,10 @@ function createEventHandler(element, events) { for (var i = 0, ii = eventHandlersCopy.length; i < ii; i++) { eventHandlersCopy[i].call(element, event); } - - // Remove monkey-patched methods (IE), - // as they would cause memory leaks in IE8. - if (msie <= 8) { - // IE7/8 does not allow to delete property on native object - event.preventDefault = null; - event.stopPropagation = null; - event.isDefaultPrevented = null; - } else { - // It shouldn't affect normal browsers (native methods are defined on prototype). - delete event.preventDefault; - delete event.stopPropagation; - delete event.isDefaultPrevented; - } }; + + // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all + // events on `element` eventHandler.elem = element; return eventHandler; } @@ -778,8 +717,9 @@ forEach({ return; } - var events = jqLiteExpandoStore(element, 'events'), - handle = jqLiteExpandoStore(element, 'handle'); + var expandoStore = jqLiteExpandoStore(element); + var events = expandoStore && expandoStore.events; + var handle = expandoStore && expandoStore.handle; if (!events) jqLiteExpandoStore(element, 'events', events = {}); if (!handle) jqLiteExpandoStore(element, 'handle', handle = createEventHandler(element, events)); @@ -919,16 +859,7 @@ forEach({ }, next: function(element) { - if (element.nextElementSibling) { - return element.nextElementSibling; - } - - // IE8 doesn't have nextElementSibling - var elm = element.nextSibling; - while (elm != null && elm.nodeType !== 1) { - elm = elm.nextSibling; - } - return elm; + return element.nextElementSibling; }, find: function(element, selector) { From 6251751ad7bc2f3621db538edb5a9d7313a4ce6d Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Mon, 4 Aug 2014 23:32:03 -0700 Subject: [PATCH 10/10] perf(jqLite): don't register DOM listener for $destroy event This even is fired purely within jqLite/jQuery so it doesn't make sense to register DOM listener here. 6% improvement in large table benchmark for both creation and destruction --- src/jqLite.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/jqLite.js b/src/jqLite.js index a3442e6cde34..fa943922e4fe 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -274,7 +274,9 @@ function jqLiteOff(element, type, fn, unsupported) { i = types.length; while (i--) { type = types[i]; - removeEventListenerFn(element, type, events[type]); + if (type !== '$destroy') { + removeEventListenerFn(element, type, events[type]); + } delete events[type]; } } else { @@ -749,7 +751,9 @@ forEach({ }); } else { - addEventListenerFn(element, type, handle); + if (type !== '$destroy') { + addEventListenerFn(element, type, handle); + } } eventFns = events[type]; }