KEMBAR78
#NewMeetup Performance | KEY
Meetup Performance
    Justin Cataldo, Lead UI Engineer (@jcataldo)
Why do we care?
Why do we care?
     Slow Site
Why do we care?
       Slow Site


  Bad User Experience
Why do we care?
        Slow Site


  Bad User Experience


 Drop in Member Activity
       (Less people meeting up)
So what should we do?
Reduce as much as
    possible!
But what are we reducing?
But what are we reducing?

 • JavaScript requests
But what are we reducing?

 • JavaScript requests
 • Image requests
But what are we reducing?

 • JavaScript requests
 • Image requests
 • DOM
But what are we reducing?

 •   JavaScript requests
 •   Image requests
 •   DOM
 •   CSS
Reducing JavaScript

• Externalize
• Concatenate
• Load only what you need upfront
• Position accordingly
Blast from the past
    (November 2009)
Pre JS Reduction
   (November 2009)
Why externalize?

• Inline scripts block
• Lose the benefit of caching
• Code reusability
Concatenate

• Sprockets (www.getsprockets.com)
• Ruby library that preprocesses and
  concatenates JavaScript files
• Baked into our build process
How Sprockets works

//******
// Sprockets Directive inside details.js
//******
//= require <templates/Meetup>
//= require <Meetup/Validator>
//= require <plug-in/lazyImage>            ANT process   EventDetails.js
//= require <plug-in/expando>
//= require <plug-in/actionDropdown>
//= require <Meetup/i18N>
...
How Sprockets works


/******* Build Process *******/
<exec executable="sprocketize" failonerror="true" output="${image.dir}/script/Meetup/packed/EventDetails.js">
   <arg value="-I"/>
   <arg path="${image.dir}/script/Meetup/"/>
   <arg path="${image.dir}/script/jquery/Meetup/Event/details.js"/>
</exec>
One gotcha
Caching
        Split your files up!

You will lose the benefit of caching
 if constantly changing a large file
Lazy Load

• Defer the loading of JavaScript files until
  they are needed
• Reduces the initial upfront requests
• Allows files to download asynchronously
• Use it to precache
How it works
  LABjs (www.labjs.com) example

<script>
   $LAB
   .script("framework.js").wait()
   .script("plugin.framework.js")
   .script("myplugin.framework.js")
   .wait(function(){
       myplugin.init();
       framework.init();
       framework.doSomething();
   });
</script>
Defer loading and precache
            I don’t need this every time I visit

           On first visit, lazy load the file in so I
            have it in cache for when I need it

           Load/execute the JavaScript when I
                   click on the link
Scripts at the bottom

• Custom tag called web:script
• Moves all scripts to the bottom
• Allows for lazy loading
• Compresses inline scripts using
  YUICompressor
web:script


   /***** Load External Script *****/
<web:script src="/script/Meetup/packed/EventDetails.js" />

/***** Load Inline Script *****/
<web:script>
      Meetup.Copy.noMembersMarkedAttended = "<trn:message key="event.attendance.noMembersMarkedAttended">No
members have been marked attended</trn:message>";
      Meetup.Copy.noMembersMarkedAttendedDynam = '<trn:message
key="event.attendance.noMembersMarkedAttendedDynam"><trn:param name="GROUPING">__GROUPING__</
trn:param>No members in "{GROUPING}" have been marked attended</trn:message>';
      Meetup.Copy.noMembersMarkedAbsent = "<trn:message key="event.attendance.noMembersMarkedAbsent">No
members have been marked absent</trn:message>";
</web:script>
If you can’t move to the
    bottom, lazy load
Post JS Reduction
    (November 2009)
Post JS Reduction
     (January 2011)
Defer images
Lazy loading for images
Do you really need all those photos to load?

  • Only load what’s in the viewport
  • Load the rest later
    •   On scroll

    •   After page loaded
How it works
    return elements.each(function () {


   
  
   var self = $(this),

   
  
   
   src = self.attr('data-src'); // We set a data attribute for the image

   
  
   self.one('appear',function() {

   
  
   
   $('<img />').bind('load', function() {

   
  
   
   
   self.hide().attr('src', src)[options.effect]
(options.effectspeed);

   
  
   
   
   self.data('loaded', true);

   
  
   
   
   var temp = $.grep(elements, function (item) {

   
  
   
   
   
   return !$(item).data('loaded');

   
  
   
   
   });

   
  
   
   
   if (temp.length == 0) {

   
  
   
   
   
   $window.unbind('scroll', load);

   
  
   
   
   }

   
  
   
   }).attr('src', src);

   
  
   });

   
  
   if (!isBelowFold(self, options)) {

   
  
   
   self.trigger('appear'); // In viewport then show

   
  
   } else {

   
  
   
   self.data('loaded', false); // Else set loaded to false

   
  
   }

   });
Embed images
                 (If you can)




• Use Data-uri/MHTML
• Embeds the images in your code
• Reduces requests
 • but increases file size
Use vector images
                  (If you can)




• Supported in all browsers
• raphael.js makes it easy
• Uses SVG and VML
Oh and, Smush those images!
Now tame that DOM
Reduce your DOM

• Larger the page, the longer it takes to
  download
• Heavily nested elements take longer to
  render
• DOM and CSS go hand and hand
http://mir.aculo.us/dom-monster/
DOM Monster
“DOM Monster is a cross-platform, cross-
browser bookmarklet that will analyze the
DOM & other features of the page you're on,
and give you its bill of health.”
We have a case of divitis and
 DOM Monster is the cure
Clean up your CSS

• Remove unused CSS
• Use efficient selectors
• Reduce CSS
Write efficient selectors
•   Avoid a universal selector
     • Uses classes or allow elements to inherit from ancestors
•   Make your rules as specific as possible
     • Use classes or IDs over tag selectors, allows for less traversal
•   Remove redundant qualifiers
     • body ul li a {...} - everything is always under body so we don’t need it
     • form#myForm {...} ---> #myForm {...}
•   Use classes instead of descendant selectors
     • ul li {color: red} ---> .list-item-red {color:red}
•   Avoid :hover on non-link elements for IE
     • Use JS mouseover

     http://code.google.com/speed/page-speed/docs/rendering.html#UseEfficientCSSSelectors
Use PageSpeed




  http://code.google.com/speed/page-speed/
Reducing is a start,
  so now what?
Profile your code
    Every ms count
Tools for profiling

• Firebug - Firefox
• DynaTrace AJAX Edition - IE
• Web Inspector - Chrome/Safari
Speed up that JavaScript
Let’s optimize this


function renderComments(data) {

     for(var i = 0; i < data.length; i++) {
          var comment = $(‘<li class=”comment” id=”’+data[i].id+’”>‘+data[i].comment+’</li>’);
          $(“#commentList”).append(comment);
     };

};
Time it
function renderComments(data) {
    console.time(“myloop”);
    for(var i = 0; i < data.length; i++) {
          var comment = $(‘<li class=”comment” id=”’+data[i].id+’”>‘+data[i].comment+’</li>’);
          $(“#commentList”).append(comment);
    };
    console.timeEnd(“myloop”);
};
Time it
function renderComments(data) {
    console.time(“myloop”);
    for(var i = 0; i < data.length; i++) {
          var comment = $(‘<li class=”comment” id=”’+data[i].id+’”>‘+data[i].comment+’</li>’);
          $(“#commentList”).append(comment);
    };
    console.timeEnd(“myloop”);
};




                                                  3ms
Optimize
function renderComments(data) {
    console.time(“myloop”);
    var i = 0,
         length = data.length;

     for(i; i < length; i++) { // Evaluates data.length every time
           var comment = $(‘<li class=”comment” id=”’+data[i].id+’”>‘+data[i].comment+’</li>’);
           $(“#commentList”).append(comment);
     };
     console.timeEnd(“myloop”);
};
Optimize
function renderComments(data) {
    console.time(“myloop”);
    var i = 0,
         length = data.length;

     for(i; i < length; i++) { // Evaluates data.length every time
           var comment = $(‘<li class=”comment” id=”’+data[i].id+’”>‘+data[i].comment+’</li>’);
           $(“#commentList”).append(comment);
     };
     console.timeEnd(“myloop”);
};




                                                  2ms
Stop hitting the DOM!
function renderComments(data) {
    console.time(“myloop”);
    var i = 0,
         length = data.length;

     for(i; i < length; i++) {
           var comment = $(‘<li class=”comment” id=”’+data[i].id+’”>‘+data[i].comment+’</li>’);
           $(“#commentList”).append(comment); // BAD
     };
     console.timeEnd(“myloop”);
};
Stop hitting the DOM!
function renderComments(data) {
    console.time(“myloop”);
    var i = 0,
         length = data.length,
         frag = document.createDocumentFragment();

     for(i; i < length; i++) {
           // Append off the DOM
           frag.appendChild($(‘<li class=”comment” id=”’+data[i].id+’”>‘+data[i].comment+’</li>’));
     };

     $(“#commentList”).append(frag); // GOOD
     console.timeEnd(“myloop”);
};
Stop hitting the DOM!
function renderComments(data) {
    console.time(“myloop”);
    var i = 0,
         length = data.length,
         frag = document.createDocumentFragment();

     for(i; i < length; i++) {
           // Append off the DOM
           frag.appendChild($(‘<li class=”comment” id=”’+data[i].id+’”>‘+data[i].comment+’</li>’));
     };

     $(“#commentList”).append(frag); // GOOD
     console.timeEnd(“myloop”);
};




                                                   2ms
Use a templating framework
Mustache

       • Use on the client or server side
       • Makes generating html simple
       • Logic-less

                                 http://mustache.github.com/
http://net.tutsplus.com/tutorials/javascript-ajax/quick-tip-using-the-mustache-template-library/
Your template
var commentTemplate = '{{#results}}<li class="feed-item-small clearfix last" id="comment_{{id}}_{{type}}">
           <a href="{{url}}" class="mem-photo-small" title="{{name}}"><img src="{{photo}}" alt="{{name}}" /></a>
           <div class="feed-item-content-small">
                <a href="{{url}}" title="{{name}}">{{name}}</a>
                <p>{{#dontescape}}{{{comment}}}{{/dontescape}}{{^dontescape}}{{comment}}{{/dontescape}}</p>
                <div class="feed-item-action D_empty D_less">
                      Posted {{posted}} <span id="likewidget_{{id}}" class="commentCountBadge">{{{likes}}}</
span> {{#ismember}}{{{liked}}} {{#iscomment}}| <a href="#" class="deleter" title="Delete this comment"
id="delete_{{id}}_{{type}}">Delete</a>{{/iscomment}}{{/ismember}}
                </div>
           </div>
   </li>{{/results}}';
Your data
var view = {
      results: {
          name: item.member_name,
          url: Chapter.groupUrl + "members/" + item.member_id,
          photo: item.photo_url,
          comment: item.comment_text,
          id: item.id,
          table: item.table_name,
          posted: item.date,
          likes: function() {
                 var length = item.likes.length;
                 return (length > 0 ? '| <a href="#" class="likedialog" data-type="' + item.table_name + '" id="likedialog_' +
item.id + '"><span class="bold">' + length + ' likes</span></a> |' : '|');
          },
          liked: function() {
                 var ids = $.map(item.likes, function(id) {
                      return id.member_id;
                 });
                 return $.inArray(Member.id, ids) > -1 ? '<a href="#" class="cvoter" data-type="' + item.table_name + '"
title="Unlike this comment" id="cvoter_' + item.id + '">Unlike</a>' : '<a href="#" class="cvoter" data-type="' +
item.table_name + '" title="Like this comment" id="cvoter_' + item.id + '">Like</a>';
          },
          type: item.is_suggestion_comment ? "idea" : "event",
          ismember: Member.isMember,
          iscomment: Member.id === item.member_id && item.table_name != 'event_diff',
          dontescape: item.table_name == 'event_diff'
      }
};
Put it together


var comment = $.mustache(commentTemplate, view);

        You now have generated html you
            can insert into the DOM
Tip: Use event delegation with templates or don’t
                forget to unbind!
    $(“#myid”).delegate(“.someclass”,”click”,function{...});
Quick tips
Cache variables

Don’t re-evaluate throughout the code

var savedGuestCount = 0,
         savedPolicyState = false,
         suggestParamValue = null, //'date' or 'venue'
         isLoaded = false,
         hours12 = ["7", "8", "9", "10", "11", "12", "1", "2", "3", "4", "5", "6"],
         hours24 = ["19", "20", "21", "22", "23", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13",
"14", "15", "16", "17", "18"],
         minutes = ["00", "15", "30", "45"],
         evtdets = $("#eventdets"),
         templates = Meetup.Templates,.....
Add context to selectors and cache them

Context
$("a.clickme").addClass("red"); //Not bad

$("#container").find("a.clickme").addClass("red"); //Better
$("#container a.clickme").addClass("red"); //Better


Cache Selectors
var clickmes = $("#container").find("a.clickme");

clickmes.addClass("red");
So what’s the outcome?
Waterfall
(November 2009)




         Thats a big waterfall

                  4.643s
Waterfall
(January 2011)




                 Thats better

                   3.826s

        18% decrease in load time
Read!



• JavaScript Performance Rocks!
• High Performance Web Sites
• Even Faster Web Sites
• JavaScript Patterns
Thanks
Email:       justin@meetup.com
Twitter:     @jcataldo
LinkedIn:     linkedin.com/in/jcataldo

#NewMeetup Performance

  • 1.
    Meetup Performance Justin Cataldo, Lead UI Engineer (@jcataldo)
  • 2.
    Why do wecare?
  • 3.
    Why do wecare? Slow Site
  • 4.
    Why do wecare? Slow Site Bad User Experience
  • 5.
    Why do wecare? Slow Site Bad User Experience Drop in Member Activity (Less people meeting up)
  • 6.
  • 7.
    Reduce as muchas possible!
  • 8.
    But what arewe reducing?
  • 9.
    But what arewe reducing? • JavaScript requests
  • 10.
    But what arewe reducing? • JavaScript requests • Image requests
  • 11.
    But what arewe reducing? • JavaScript requests • Image requests • DOM
  • 12.
    But what arewe reducing? • JavaScript requests • Image requests • DOM • CSS
  • 13.
    Reducing JavaScript • Externalize •Concatenate • Load only what you need upfront • Position accordingly
  • 14.
    Blast from thepast (November 2009)
  • 15.
    Pre JS Reduction (November 2009)
  • 16.
    Why externalize? • Inlinescripts block • Lose the benefit of caching • Code reusability
  • 17.
    Concatenate • Sprockets (www.getsprockets.com) •Ruby library that preprocesses and concatenates JavaScript files • Baked into our build process
  • 18.
    How Sprockets works //****** //Sprockets Directive inside details.js //****** //= require <templates/Meetup> //= require <Meetup/Validator> //= require <plug-in/lazyImage> ANT process EventDetails.js //= require <plug-in/expando> //= require <plug-in/actionDropdown> //= require <Meetup/i18N> ...
  • 19.
    How Sprockets works /*******Build Process *******/ <exec executable="sprocketize" failonerror="true" output="${image.dir}/script/Meetup/packed/EventDetails.js"> <arg value="-I"/> <arg path="${image.dir}/script/Meetup/"/> <arg path="${image.dir}/script/jquery/Meetup/Event/details.js"/> </exec>
  • 20.
  • 21.
    Caching Split your files up! You will lose the benefit of caching if constantly changing a large file
  • 22.
    Lazy Load • Deferthe loading of JavaScript files until they are needed • Reduces the initial upfront requests • Allows files to download asynchronously • Use it to precache
  • 23.
    How it works LABjs (www.labjs.com) example <script> $LAB .script("framework.js").wait() .script("plugin.framework.js") .script("myplugin.framework.js") .wait(function(){ myplugin.init(); framework.init(); framework.doSomething(); }); </script>
  • 24.
    Defer loading andprecache I don’t need this every time I visit On first visit, lazy load the file in so I have it in cache for when I need it Load/execute the JavaScript when I click on the link
  • 25.
    Scripts at thebottom • Custom tag called web:script • Moves all scripts to the bottom • Allows for lazy loading • Compresses inline scripts using YUICompressor
  • 26.
    web:script /***** Load External Script *****/ <web:script src="/script/Meetup/packed/EventDetails.js" /> /***** Load Inline Script *****/ <web:script> Meetup.Copy.noMembersMarkedAttended = "<trn:message key="event.attendance.noMembersMarkedAttended">No members have been marked attended</trn:message>"; Meetup.Copy.noMembersMarkedAttendedDynam = '<trn:message key="event.attendance.noMembersMarkedAttendedDynam"><trn:param name="GROUPING">__GROUPING__</ trn:param>No members in "{GROUPING}" have been marked attended</trn:message>'; Meetup.Copy.noMembersMarkedAbsent = "<trn:message key="event.attendance.noMembersMarkedAbsent">No members have been marked absent</trn:message>"; </web:script>
  • 27.
    If you can’tmove to the bottom, lazy load
  • 28.
    Post JS Reduction (November 2009)
  • 29.
    Post JS Reduction (January 2011)
  • 30.
  • 31.
    Do you reallyneed all those photos to load? • Only load what’s in the viewport • Load the rest later • On scroll • After page loaded
  • 32.
    How it works return elements.each(function () { var self = $(this), src = self.attr('data-src'); // We set a data attribute for the image self.one('appear',function() { $('<img />').bind('load', function() { self.hide().attr('src', src)[options.effect] (options.effectspeed); self.data('loaded', true); var temp = $.grep(elements, function (item) { return !$(item).data('loaded'); }); if (temp.length == 0) { $window.unbind('scroll', load); } }).attr('src', src); }); if (!isBelowFold(self, options)) { self.trigger('appear'); // In viewport then show } else { self.data('loaded', false); // Else set loaded to false } });
  • 33.
    Embed images (If you can) • Use Data-uri/MHTML • Embeds the images in your code • Reduces requests • but increases file size
  • 34.
    Use vector images (If you can) • Supported in all browsers • raphael.js makes it easy • Uses SVG and VML
  • 35.
    Oh and, Smushthose images!
  • 36.
  • 37.
    Reduce your DOM •Larger the page, the longer it takes to download • Heavily nested elements take longer to render • DOM and CSS go hand and hand
  • 38.
  • 39.
    DOM Monster “DOM Monsteris a cross-platform, cross- browser bookmarklet that will analyze the DOM & other features of the page you're on, and give you its bill of health.”
  • 41.
    We have acase of divitis and DOM Monster is the cure
  • 42.
    Clean up yourCSS • Remove unused CSS • Use efficient selectors • Reduce CSS
  • 43.
    Write efficient selectors • Avoid a universal selector • Uses classes or allow elements to inherit from ancestors • Make your rules as specific as possible • Use classes or IDs over tag selectors, allows for less traversal • Remove redundant qualifiers • body ul li a {...} - everything is always under body so we don’t need it • form#myForm {...} ---> #myForm {...} • Use classes instead of descendant selectors • ul li {color: red} ---> .list-item-red {color:red} • Avoid :hover on non-link elements for IE • Use JS mouseover http://code.google.com/speed/page-speed/docs/rendering.html#UseEfficientCSSSelectors
  • 44.
    Use PageSpeed http://code.google.com/speed/page-speed/
  • 45.
    Reducing is astart, so now what?
  • 46.
    Profile your code Every ms count
  • 47.
    Tools for profiling •Firebug - Firefox • DynaTrace AJAX Edition - IE • Web Inspector - Chrome/Safari
  • 48.
    Speed up thatJavaScript
  • 49.
    Let’s optimize this functionrenderComments(data) { for(var i = 0; i < data.length; i++) { var comment = $(‘<li class=”comment” id=”’+data[i].id+’”>‘+data[i].comment+’</li>’); $(“#commentList”).append(comment); }; };
  • 50.
    Time it function renderComments(data){ console.time(“myloop”); for(var i = 0; i < data.length; i++) { var comment = $(‘<li class=”comment” id=”’+data[i].id+’”>‘+data[i].comment+’</li>’); $(“#commentList”).append(comment); }; console.timeEnd(“myloop”); };
  • 51.
    Time it function renderComments(data){ console.time(“myloop”); for(var i = 0; i < data.length; i++) { var comment = $(‘<li class=”comment” id=”’+data[i].id+’”>‘+data[i].comment+’</li>’); $(“#commentList”).append(comment); }; console.timeEnd(“myloop”); }; 3ms
  • 52.
    Optimize function renderComments(data) { console.time(“myloop”); var i = 0, length = data.length; for(i; i < length; i++) { // Evaluates data.length every time var comment = $(‘<li class=”comment” id=”’+data[i].id+’”>‘+data[i].comment+’</li>’); $(“#commentList”).append(comment); }; console.timeEnd(“myloop”); };
  • 53.
    Optimize function renderComments(data) { console.time(“myloop”); var i = 0, length = data.length; for(i; i < length; i++) { // Evaluates data.length every time var comment = $(‘<li class=”comment” id=”’+data[i].id+’”>‘+data[i].comment+’</li>’); $(“#commentList”).append(comment); }; console.timeEnd(“myloop”); }; 2ms
  • 54.
    Stop hitting theDOM! function renderComments(data) { console.time(“myloop”); var i = 0, length = data.length; for(i; i < length; i++) { var comment = $(‘<li class=”comment” id=”’+data[i].id+’”>‘+data[i].comment+’</li>’); $(“#commentList”).append(comment); // BAD }; console.timeEnd(“myloop”); };
  • 55.
    Stop hitting theDOM! function renderComments(data) { console.time(“myloop”); var i = 0, length = data.length, frag = document.createDocumentFragment(); for(i; i < length; i++) { // Append off the DOM frag.appendChild($(‘<li class=”comment” id=”’+data[i].id+’”>‘+data[i].comment+’</li>’)); }; $(“#commentList”).append(frag); // GOOD console.timeEnd(“myloop”); };
  • 56.
    Stop hitting theDOM! function renderComments(data) { console.time(“myloop”); var i = 0, length = data.length, frag = document.createDocumentFragment(); for(i; i < length; i++) { // Append off the DOM frag.appendChild($(‘<li class=”comment” id=”’+data[i].id+’”>‘+data[i].comment+’</li>’)); }; $(“#commentList”).append(frag); // GOOD console.timeEnd(“myloop”); }; 2ms
  • 57.
  • 58.
    Mustache • Use on the client or server side • Makes generating html simple • Logic-less http://mustache.github.com/ http://net.tutsplus.com/tutorials/javascript-ajax/quick-tip-using-the-mustache-template-library/
  • 59.
    Your template var commentTemplate= '{{#results}}<li class="feed-item-small clearfix last" id="comment_{{id}}_{{type}}"> <a href="{{url}}" class="mem-photo-small" title="{{name}}"><img src="{{photo}}" alt="{{name}}" /></a> <div class="feed-item-content-small"> <a href="{{url}}" title="{{name}}">{{name}}</a> <p>{{#dontescape}}{{{comment}}}{{/dontescape}}{{^dontescape}}{{comment}}{{/dontescape}}</p> <div class="feed-item-action D_empty D_less"> Posted {{posted}} <span id="likewidget_{{id}}" class="commentCountBadge">{{{likes}}}</ span> {{#ismember}}{{{liked}}} {{#iscomment}}| <a href="#" class="deleter" title="Delete this comment" id="delete_{{id}}_{{type}}">Delete</a>{{/iscomment}}{{/ismember}} </div> </div> </li>{{/results}}';
  • 60.
    Your data var view= { results: { name: item.member_name, url: Chapter.groupUrl + "members/" + item.member_id, photo: item.photo_url, comment: item.comment_text, id: item.id, table: item.table_name, posted: item.date, likes: function() { var length = item.likes.length; return (length > 0 ? '| <a href="#" class="likedialog" data-type="' + item.table_name + '" id="likedialog_' + item.id + '"><span class="bold">' + length + ' likes</span></a> |' : '|'); }, liked: function() { var ids = $.map(item.likes, function(id) { return id.member_id; }); return $.inArray(Member.id, ids) > -1 ? '<a href="#" class="cvoter" data-type="' + item.table_name + '" title="Unlike this comment" id="cvoter_' + item.id + '">Unlike</a>' : '<a href="#" class="cvoter" data-type="' + item.table_name + '" title="Like this comment" id="cvoter_' + item.id + '">Like</a>'; }, type: item.is_suggestion_comment ? "idea" : "event", ismember: Member.isMember, iscomment: Member.id === item.member_id && item.table_name != 'event_diff', dontescape: item.table_name == 'event_diff' } };
  • 61.
    Put it together varcomment = $.mustache(commentTemplate, view); You now have generated html you can insert into the DOM
  • 62.
    Tip: Use eventdelegation with templates or don’t forget to unbind! $(“#myid”).delegate(“.someclass”,”click”,function{...});
  • 63.
  • 64.
    Cache variables Don’t re-evaluatethroughout the code var savedGuestCount = 0, savedPolicyState = false, suggestParamValue = null, //'date' or 'venue' isLoaded = false, hours12 = ["7", "8", "9", "10", "11", "12", "1", "2", "3", "4", "5", "6"], hours24 = ["19", "20", "21", "22", "23", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18"], minutes = ["00", "15", "30", "45"], evtdets = $("#eventdets"), templates = Meetup.Templates,.....
  • 65.
    Add context toselectors and cache them Context $("a.clickme").addClass("red"); //Not bad $("#container").find("a.clickme").addClass("red"); //Better $("#container a.clickme").addClass("red"); //Better Cache Selectors var clickmes = $("#container").find("a.clickme"); clickmes.addClass("red");
  • 66.
  • 67.
    Waterfall (November 2009) Thats a big waterfall 4.643s
  • 68.
    Waterfall (January 2011) Thats better 3.826s 18% decrease in load time
  • 69.
    Read! • JavaScript PerformanceRocks! • High Performance Web Sites • Even Faster Web Sites • JavaScript Patterns
  • 70.
    Thanks Email: justin@meetup.com Twitter: @jcataldo LinkedIn: linkedin.com/in/jcataldo