(All data in this blog post is based on profiling on an iPhone5 with iOS 7.1)
TL;DR: AngularJS's JQLite has significantly faster .data() and .text()
In jQuery makes AngularJS 50% slower on an iPhone I showed the performance difference between AngularJS with jQuery and AngularJS without jQuery. That 50% was with AngularJS 1.2 and jQuery 1.11 - with AngularJS 1.3 (beta6) and jQuery 2.1 the difference was 35%.
I did a bit of testing to see if there was a simple reason for the slowdown, and turns out that there is. With two changes I got the difference down to 8% (created using The Mobile HTML5 Rendering Profiler; click for a large view):
The changes were to comment out most uses of .data() from AngularJS and changing ng-bind to use a simple inlined code to change the element text instead of jQuery's .text(). Note that these are not 'fixes' that can be applied to AngularJS - they don't preserve all the functionality. What they are is a way to see what is causing the slowdown. Here's the diff:
--- angular-1.3.0-beta.6.js 2014-04-26 16:02:35.000000000 +0300 +++ angular-1.3.0-beta.6-hacked.js 2014-04-26 15:57:23.000000000 +0300 @@ -6007,7 +6007,7 @@ : $compileNodes; forEach(transcludeControllers, function(instance, name) { - $linkNode.data('$' + name + 'Controller', instance); + //$linkNode.data('$' + name + 'Controller', instance); }); // Attach scope only to non-text nodes. @@ -6015,7 +6015,7 @@ var node = $linkNode[i], nodeType = node.nodeType; if (nodeType === 1 /* element */ || nodeType === 9 /* document */) { - $linkNode.eq(i).data('$scope', scope); + // $linkNode.eq(i).data('$scope', scope); } } @@ -6105,7 +6105,7 @@ if (nodeLinkFn) { if (nodeLinkFn.scope) { childScope = scope.$new(); - $node.data('$scope', childScope); + // $node.data('$scope', childScope); } else { childScope = scope; } @@ -6572,9 +6572,9 @@ isolateScope = scope.$new(true); if (templateDirective && (templateDirective === newIsolateScopeDirective.$$originalDirective)) { - $linkNode.data('$isolateScope', isolateScope) ; + // $linkNode.data('$isolateScope', isolateScope) ; } else { - $linkNode.data('$isolateScopeNoTemplate', isolateScope); + // $linkNode.data('$isolateScopeNoTemplate', isolateScope); } @@ -6677,7 +6677,7 @@ // later, once we have the actual element. elementControllers[directive.name] = controllerInstance; if (!hasElementTranscludeDirective) { - $element.data('$' + directive.name + 'Controller', controllerInstance); + // $element.data('$' + directive.name + 'Controller', controllerInstance); } if (directive.controllerAs) { @@ -18914,12 +18914,13 @@ </example> */ var ngBindDirective = ngDirective(function(scope, element, attr) { - element.addClass('ng-binding').data('$binding', attr.ngBind); + element.addClass('ng-binding'); // .data('$binding', attr.ngBind); scope.$watch(attr.ngBind, function ngBindWatchAction(value) { // We are purposefully using == here rather than === because we want to // catch when value is "null or undefined" // jshint -W041 - element.text(value == undefined ? '' : value); + element[0].textContent = (value == undefined ? '' : value); + // element.text(value == undefined ? '' : value); }); });
The test is comparing The version without jQuery to the version that loads jQuery before AngularJS; both with the above changes to use of .text() and .data(). Commenting out .data() took the difference from 35% to 20%, and changing .text() from 20% to 8%.
This simple test only really uses ng-repeat and ng-bind. Although these are important parts of AngularJS, a typical application will quite likely use ng-class, ng-if, ng-switch, ng-show etc. These might also show similar slowdowns with jQuery.
It would be interesting to try and optimize these two methods in jQuery, implementing them along the lines of what AngularJS does. I'm not really familiar enough with jQuery to know if there are some real semantic differences in how they are supposed to work (in comparison to AngularJS).
Oh, and I did first think there might be a difference in Garbage Collection, but that was clearly not the case (this is a Timeline image from running with jQuery, there is no GC drop in the memory usage during the rendering, only on the page reload):
Note that the Profiler lets you see Timelines from the iPhone in the (better) Chrome Developer Tools.