Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Performance: replace several jqmData() function calls with getAttribute() to improve performance. #5466

Closed
wants to merge 1 commit into from

4 participants

Changsuk Jeroen van Warmerdam Gabriel "_|Nix|_" Schulhof Alexander Schmitz
Changsuk

Hi, All.
Actually, I tried to do a PR last week but I couldn't because there were many issues on Tizen project, so I'm sorry that I couldn't have enough time to do it. I'm opening the third PR regarding performance improvement now.

My team found another performance issue about using jqmData() function.
As you know, jqmData() calls jQuery data() function. "data()" function is complicated and not small. Also it takes time to call the function because many cases call jQuery.extend() function in data() and extend() calls other functions and has for loop and does a lot of things. Hence, my team found several jqmData() function calls which can be replaced with getAttribute() JavaScript(JS) function. (Some cases can not do like that.)
After replacing jqmData() function calls with getAttribute(), my team checked Web App.'s launching time and got a big performance improvement. Web App.'s average launching time decreased to 130ms on GalaxyS2 class devices.
My team has been looking for other codes which are able to replace jqmData() function call since then and if my team finds other codes to replace, I'll do a PR again.

Additionally, some codes using jqmData() return number or boolean value, hence some of committed codes on jquery.mobile.buttonMarkup.js are modified according to the return value's type.

  • Tests : The code has been tested on several jQM widgets using qunit.js.
  • Modified code is followed the jQuery Core Style guide.
  • Scope : Performance improvement.

If there are any problems or mistakes on that PR, please let me know. Thanks in advance.

Jeroen van Warmerdam

Should the false result of an getAttribute method result in an undefined?
What is jqmData doing in those cases when the attribute is not found?
Shouldn't those result in an empty string?

You could make this into an internal function.

Gabriel "_|Nix|_" Schulhof
Collaborator

.jqmData() actually calls .data() after adding the $.mobile.ns namespace to the key.

I'm not sure whether the replacements provided in this PR are equivalent to .jqmData(), because they merely set attributes on the DOM element, whereas .jqmData() also sets jQuery data items. Thus, is the corresponding retrieval is made using .jqmData() rather than .attr(), then the data will not get retrieved correctly.

@Changsuk, did the unit tests complete successfully with this change in place?

Alexander Schmitz
Owner

@Changsuk in addition to @gabrielschulhof concerns above, while it is true using getAttribute may be faster getAttribute is not safe for cross browser use it does not always work properly in IE.

Also we use jquery's data functions to prevent potential problems with with circular references and memory leaks. data ensures all data is properly set and retrieved. it also ensure all data is removed when an element is removed from the dom.

Thank your for your PR though we really appreciate all the great suggestions for improving performance.

Changsuk

@jerone,
Should the false result of an getAttribute method result in an undefined?
-> Yes, If jqmData() can't find matched attribute or a data in an array, jqmData() returns undefined. The committed codes with undefined can meet the jqmData()'s return value.

What is jqmData doing in those cases when the attribute is not found?
-> As you may know, jqmData() calls jQuery.fn.extend.data() hence, if jQuery.fn.extend.data() doesn't find the attribute, it returns undefined. You can understand it with the jQuery codes and my comments below.

return data === undefined && parts[1] ? // If jQuery.data() doesn't find the attribute, the data is undefined.
this.data( parts[0] ) :
data; // It returns undefined.

Shouldn't those result in an empty string?
-> As you know, empty string and undefined are different. jQuery.fn.extend.data() returns undefined so I think that we should match the return value.

Changsuk

@gabrielschulhof,
Yes, my team did all of the unit test twice and all test cases are passed. Also my team verified all of jQuery Mobile widgets and Tizen widgets apparently. :)

@gabrielschulhof and @arschmitz,
As you can guess, the codes I modified doesn't need to store cache data on jQuery.cache because the cases are just retrieve the DOM elementary's attribute. Hence, my team thought that we could use JS API. As you can see the jQuery codes, jQuery.fn.extend.data() and jQuery.data() functions' call stacks are deep and do many things. Hence, the methods' call time are much longer. After my team adapted the codes on real devices using WebKit based browsers and analyzed launching time in detail with WebKit source code, we got a big improvement from the test.

@arschmitz,
My team check getAttribute() API on IE browser. It works OK. Also some of the modules in jQuery Mobile use setAttribute() JS API. I think that setAttribute() is the same case.

@All,
I should like to end by saying that, as you already know, jQuery Mobile is for mobile devices and performance issues are very important. So If you can, please consider that PR again. Thanks in advance.

Gabriel "_|Nix|_" Schulhof
Collaborator

Another problem: jQuery's dataAttr() function, which is a jQuery internal function and which ultimately retrieves the value of the data-* attribute does a lot more than just retrieve the value. For example, it converts the value to boolean. Now, while we could do that work ourselves, we don't want to diverge too much from what jQuery does.

I did, however, achieve a performance boost by replacing the set call to .jqmData() inside mapToDataAttr() with $.removeData():http://jsperf.com/jqmdatavsremovedata/3

This basically has the effect that the data will not be cached, but it will always be gotten from/set on the DOM element.

Gabriel "_|Nix|_" Schulhof
Collaborator

Well, it looks like we might have to look into doing things the native way, after all. The benefits are hard to overlook ...

Jeroen van Warmerdam

On mobile every profit is a 'big' difference.
Just run that latest jspref on every browser on my device (8 atm :) ).

Gabriel "_|Nix|_" Schulhof gabrielschulhof closed this pull request from a commit
Gabriel "_|Nix|_" Schulhof gabrielschulhof buttonMarkup: Use native js more often to improve performance. Fixes #…
…5466. Thanks Changsuk.

Performance analysis: http://jsperf.com/jqmdatavsremovedata/7

I used a third option where I restore the popup-handling code to the original, but that's unnecessary because plain optimization achies the highest performance.
1101629
Gabriel "_|Nix|_" Schulhof
Collaborator

Oops! There were other files in this PR as well. The above commit fixes only buttonMarkup. I'll reopen this so I don't forget the other files.

Changsuk

@All, Thanks for accepting my team's idea.
@gabrielschulhof I saw the test - http://jsperf.com/jqmdatavsremovedata/7. Thanks for your effort to make a new method and modify the other codes. After you modify the other files with new method - getAttrFixed, I'll do another PR to improve performance by using the method.

Gabriel "_|Nix|_" Schulhof gabrielschulhof referenced this pull request from a commit
Gabriel "_|Nix|_" Schulhof gabrielschulhof page.sections: Use getAttribute() to access data-url attribute to dec…
…rease startup time. Applying by hand from #5466.
a9e1981
Gabriel "_|Nix|_" Schulhof
Collaborator

I have now applied all the changes in this PR by hand.

Changsuk

@gabrielschulhof Thanks so much, Gabriel. I'll put another PR to complete replacing jqmData() to get/setAttribute. :)

Changsuk Changsuk referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Changsuk Changsuk referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Changsuk Changsuk referenced this pull request from a commit
Changsuk Changsuk Replace several jqmData() function calls with getAttribute() to impro…
…ve performance. It is the same as #5466.
149b3f2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jan 17, 2013
  1. Changsuk
This page is out of date. Refresh to see the latest.
19 js/jquery.mobile.buttonMarkup.js
View
@@ -11,6 +11,7 @@ define( [ "jquery", "./jquery.mobile.core", "./jquery.mobile.vmouse" ], function
$.fn.buttonMarkup = function( options ) {
var $workingSet = this,
+ prefix = "data-" + $.mobile.ns,
mapToDataAttr = function( key, value ) {
e.setAttribute( "data-" + $.mobile.ns + key, value );
el.jqmData( key, value );
@@ -22,14 +23,14 @@ $.fn.buttonMarkup = function( options ) {
var el = $workingSet.eq( i ),
e = el[ 0 ],
o = $.extend( {}, $.fn.buttonMarkup.defaults, {
- icon: options.icon !== undefined ? options.icon : el.jqmData( "icon" ),
- iconpos: options.iconpos !== undefined ? options.iconpos : el.jqmData( "iconpos" ),
- theme: options.theme !== undefined ? options.theme : el.jqmData( "theme" ) || $.mobile.getInheritedTheme( el, "c" ),
- inline: options.inline !== undefined ? options.inline : el.jqmData( "inline" ),
- shadow: options.shadow !== undefined ? options.shadow : el.jqmData( "shadow" ),
- corners: options.corners !== undefined ? options.corners : el.jqmData( "corners" ),
- iconshadow: options.iconshadow !== undefined ? options.iconshadow : el.jqmData( "iconshadow" ),
- mini: options.mini !== undefined ? options.mini : el.jqmData( "mini" )
+ icon: options.icon !== undefined ? options.icon : ( e.getAttribute( prefix + "icon" ) || undefined ),
+ iconpos: options.iconpos !== undefined ? options.iconpos : ( e.getAttribute( prefix + "iconpos" ) || undefined ),
+ theme: options.theme !== undefined ? options.theme : e.getAttribute( prefix + "theme" ) || $.mobile.getInheritedTheme( el, "c" ),
+ inline: options.inline !== undefined ? options.inline : /^true$/i.test( e.getAttribute( prefix + "inline" ) ),
+ shadow: options.shadow !== undefined ? options.shadow : !/^false$/i.test( e.getAttribute( prefix + "shadow" ) ),
+ corners: options.corners !== undefined ? options.corners : !/^false$/i.test( e.getAttribute( prefix + "corners" ) ),
+ iconshadow: options.iconshadow !== undefined ? options.iconshadow : !/^false$/i.test( e.getAttribute( prefix + "iconshadow" ) ),
+ mini: options.mini !== undefined ? options.mini : /^true$/i.test( e.getAttribute( prefix + "mini" ) )
}, options ),
// Classes Defined
@@ -46,7 +47,7 @@ $.fn.buttonMarkup = function( options ) {
$.each( o, mapToDataAttr );
- if ( el.jqmData( "rel" ) === "popup" && el.attr( "href" ) ) {
+ if ( e.getAttribute( prefix + "rel" ) === "popup" && el.attr( "href" ) ) {
e.setAttribute( "aria-haspopup", true );
e.setAttribute( "aria-owns", e.getAttribute( "href" ) );
}
2  js/jquery.mobile.init.js
View
@@ -68,7 +68,7 @@ define([
var $this = $( this );
// unless the data url is already set set it to the pathname
- if ( !$this.jqmData( "url" ) ) {
+ if ( !$this[0].getAttribute( "data-" + $.mobile.ns + "url" ) ) {
$this.attr( "data-" + $.mobile.ns + "url", $this.attr( "id" ) || location.pathname + location.search );
}
});
9 js/widgets/page.sections.js
View
@@ -21,7 +21,8 @@ $.mobile.page.prototype.options.contentTheme = null;
$.mobile.document.bind( "pagecreate", function( e ) {
var $page = $( e.target ),
o = $page.data( "mobile-page" ).options,
- pageRole = $page.jqmData( "role" ),
+ prefix = "data-"+$.mobile.ns,
+ pageRole = $page[0].getAttribute( prefix + "role" ) || undefined,
pageTheme = o.theme;
$( ":jqmData(role='header'), :jqmData(role='footer'), :jqmData(role='content')", $page )
@@ -29,8 +30,8 @@ $.mobile.document.bind( "pagecreate", function( e ) {
.each(function() {
var $this = $( this ),
- role = $this.jqmData( "role" ),
- theme = $this.jqmData( "theme" ),
+ role = $this[0].getAttribute( prefix + "role" ) || undefined,
+ theme = $this[0].getAttribute( prefix + "theme" ) || undefined,
contentTheme = theme || o.contentTheme || ( pageRole === "dialog" && pageTheme ),
$headeranchors,
leftbtn,
@@ -65,7 +66,7 @@ $.mobile.document.bind( "pagecreate", function( e ) {
if ( o.addBackBtn &&
role === "header" &&
$( ".ui-page" ).length > 1 &&
- $page.jqmData( "url" ) !== $.mobile.path.stripHash( location.hash ) &&
+ $page[0].getAttribute( prefix + "url" ) !== $.mobile.path.stripHash( location.hash ) &&
!leftbtn ) {
backBtn = $( "<a href='javascript:void(0);' class='ui-btn-left' data-"+ $.mobile.ns +"rel='back' data-"+ $.mobile.ns +"icon='arrow-l'>"+ o.backBtnText +"</a>" )
Something went wrong with that request. Please try again.