Welcome!

Javascript Performance Optimizations

Josh Powell

Subscribe to Josh Powell: eMailAlertsEmail Alerts
Get Josh Powell via: homepageHomepage mobileMobile rssRSS facebookFacebook twitterTwitter linkedinLinkedIn


Article

Does Netflix Know Javascript?

Javascript Best Practices

While wandering around the netflix.com, I came across an irritant. The dialog popups that happen when hovering over the movies react very slow. I had to hover and wait and hover and wait and hover and wait. Suspecting poorly written javascript, I decided to profile the site to see what was wrong. Since their javascript is concatenated and minified, I had to spend a few hours finding and deminifying the pertinent javascript by hand.

Delving into the source, the first thing I notice is a ton of extra whitespace. Right at the top even, between the doctype and the opening html tag. This sort of thing is, in my experience, an artifact of working with an older java backend/middle tier or jsp environment. It hasn’t happened when I’ve been using Spring MVC and can be usually tracked down to template files like so:

<% do some java/jsp %>

<% do some other java/jsp %>

That generates a line break, even if it is from two separate files being included one after the other. Yuck.

The only way to fix it is to comb through your jsp files and make them ugly instead:

<% do some java/jsp %><%

do some other java/jsp %>

or

<% do some java/jsp

%><% do some other java/jsp %>

But that is ugly code and the issue is 99% cosmetic, it only has an impact on the download size and only if it isn’t gzipped. Although, up to 15% of requests on sites that have enabled gzip, are from sources that do not permit gzipping, so this will still have an impact on the bandwidth usage and on users without gzip (see Even Faster Web Sites by Steve Souders).

CSS

 

Delving deeper, Netflix can concatenate all of their css files. There are three css files on the home page: pkg-common, pkg-MemberHome2, pkg-rateMoviewRow. It looks like they’ve done some concatenating here, but these three files took 651ms in my browser to figure out they were already cached and move on. A half second is huge on a site like this. Now, they do expire the css files far into the future, April 15th, 2020, which means that the browser will never check for new versions until then. So how do they push updates? It looks like they rename the css files dynamically with every update, I’m not sure if passing in a different variable to the file would be enough to trigger the browser to update that cache or not, but that isn’t theonly trick they use. The full name of the css files are

pkg-Common2-f83b07f7c21bff8-312460.css?v=312460

pkg-MemberHome2-b70-312460.css?v=312460

pkg-rateMoviesRow-3-312460.css?v=312460

I suspect they have a build process that takes their development css files and concatenates them down into these three css files. pkg-Common2 and Pkg-MemberHome2 makes it sound like there was a complete redevelopment of that file which needed to occur at the same time that pkg-Common and Pkg-MemberHome was around and live. The 312460 looks like some sort of versioning number, I infer that from the ‘v’ chosen as the variable name. MemberHome has a b70, which could indicate a build number of just be a random string. f83b07f7c21bff8 on the common file, looks like a hash of some kind, possibly to update the cached files. The css files are minified by removing all of the whitespace, so that will save them some download size. Everything is gzipped as well, which will further reduce the size. All in all, I give them a B+ for CSS. Good job on the minifying and gzipping, the concatenating could be better.

Javascript

Netflix could also concatenate their js files better. They do concatenate many of their files, but 2 didn’t make it in. The three files took 1.871 seconds to realize they were cached and use the cached files when I accessed the site. Combined with the css, that’s almost 2.5 seconds waiting for cache because the files were not all concatenated. The javascript files follow the same pattern as their css files do. Almost all are concatenated, they are minified and they are gzipped. Caching is set to a far future date, and file names are used to update the cache. Pkg-MemberTilesPkg seems to be the main js file, it contains jQuery and Sizzle and immediately calls jQuery.noConflict() to release the $. This seems to have been done for forwards compatibility instead of backwards because I do not see use of the $ elsewhere. Either that, or prototype, mootools, or dojo was once used on the site and no longer is.

The scripts are included are at the bottom of the page, which is good because when the scripts are included at the top they will block loading of other content and make the page appear to load more slowly. It still takes the same amount of time to load everything, but the visible content loads first when the scripts are at the bottom and while people are looking around the page for something to click on, the script files get a chance to load.

There is also some javascript loose on the main page. This can be bad because it does not cache. However, the code that is embedded is code that controls the content of the sliders. They probably don’t want that to cache. Declarations are embedded at the top of the page, which I think is because there is javascript embedded in the html. Embedding javascript in the HTML, I generally consider a bad practice, but since google came out with closure, the Closure developers have openly stated that they prefer to do it this way because the javascript events will be available immediately when the HTML is and does not have to wait for everything to be loaded. In Netflix case, however, the functions that are called in the html are not available until the scripts at the bottom of the page load, so this advantage is minimal.

There are 8 calls to jQuery.layoutReady, one call to the shortcut $(function () { and a call to jQuery(window).load Those are three different event timings, so this should be a maximum of three calls, though I suspect the difference in when to execute the code (layout ready, window load, and document ready) is inconsequential and this should really be one call instead of ten. The javascript seems to be added to the bottom of the page when the slider component gets included. Each slider component embeds its own jQuery.layoutReady call. This problem is solvable. The strings are surrounded with double quotes instead of single quotes, which forces them to escape the double quotes in the string.

{"fallbackHtml": "<div class=\"foo\”></div>”}

could more easily have been written:

{‘fallbackHtml’: ‘<div class="foo”></div>’}

which is much more legible and less error prone. Surrounding the attribute name in quotes of either kind is a good practice because if you do not, IE will break on certain keywords. They followed that best practice here but neglected it in other parts of the js.

Netflix does an excellent job of namespacing and keeping variables out of the global scope. By wrapping pre-concatenated files in an immediately evaluated anonymous function and passing in the objects needed:

(function (a,b,c) {

})(jQuery, foo, bar)

The javascript embedded in the html are onmouseover=”” which all execute dB(this); I count 229 instances of this. That’s quite a few events on the page. These are potential sources of memory leaks in IE, especially IE6. Since they use jQuery to append to and remove from the page, it probably will not leak. jQuery cleans up DOM nodes before removing them, in a very safe but slow process. An alternative to this approach is to use event delegation or jQuery’s .live() functionality. This could cause a slowdown during execution, since it is a onmouseover event, but I suspect that it will not. In the case that it does, a custom event delegation could be constrained and would not slow it down. In any case, the next version of jQuery will allow live events to be constrained and so performance will not be as much of an issue.

Netflix adds some methods to the prototype of the native string object. It adds the methods add, trim, truncWithEllipis, and stripHTML. They do what you’d expect and are probably pretty handy. However, it is considered a bad practice by many in the javascript community to add methods to native objects prototypes and they could have easily have accomplished the same thing with a namespaced object or function. It’s considered a bad idea because it could create conflicts should you use any libraries that also add methods to these prototypes, future releases of javascript may include these itself and those native methods could be overwritten with these methods. Given netflix otherwise strict adherence to containing global variables, it is rather surprising these methods exist. I suspect legacy code.

The objects created in a json format do not surround object attribute names with quotes, in certain circumstances this can result in hard to diagnose bugs in ie, because ie does not allow some keywords to be used as attribute names unless they are surrounded with quotes, so it is better to be in the practice of doing so. There are also many cases of == and != instead of === and !==. For reasons why this is a bad thing, check out jslint.com or read Javascript: The Good Parts by Doug Crockford.

After that cursory review of the general state of css and javascript on netflix, I returned to the part that got me interested in looking at the code to begin with: the movie dialog boxes. These are launched with the onmouseover=”dB(this)” code embedded in the html. The dB function takes the a tag node as its only parameter and then removes the onmouseover and calls the attach and show methods of an object called BobMovieManager. I’m not sure who Bob is, but it’s got me thinking about putting some Josh into my code.

The BobMovieManager is created on document ready and is created by the BobFactory. At this point I assumed that the BobFactory is used to create lots of BobMovieManager objects. Inside of the BobFactory, is a very interesting pattern. Instead of using the standard pattern of building an HTML string using += to concatenate various variables, netflix creates the HTML string all at once and has $domId in the place where the variable would go. It then uses regular expression to replace $domId with the domIdPrefix. This got me wondering if this is fast enough to replace += or array.join patterns to build long strings with repetitive variables, so I tested it. It turns out to be browser dependent and between 5% and 10% faster. A useful pattern for circumstances when speed is a major issue and the same variable is being used multiple times in the string, but in this case not it probably isn’t necessary as I found that BobFactory is actually only called once when the page is loaded to create the BobMovieManager.

The BobFactory call passes in an anonymous function that is used to get the url of the movie and record if that url has been cached in the javascript. The anonymous function also passes in an options object that defines all sorts of information about the dialog, information about positioning mostly, but also a cache object. It looks like they are caching the json response, which includes html for the dialog and the id of the movie. This is good because the ajax call doesn’t get sent on the second rollover, but this is not as good as it could be because the cached html string is still inserted via jquery’s html method, overwriting previous content and moving the dialog around the page.

A faster implementation would not just cache the html string, but save the rendered DOM nodes for future use. An even faster implementation would also save the DOM nodes in DocumentFragments to append and remove from the DOM. An even faster implementation would leave the already inserted HTML on the page and hide it, and then show it again on the next rollover.

I thought I had found the source of the delay, when I found the real source. Netflix has wrapped the calls to fetch the movie information and show the dialog in a delayManager that calls them after 600 and 100 milliseconds respectively. The call to show wrapped inside the delayed fetch. The delay is on purpose! It’s there, I assume, to reduce the number of ajax calls created by unintended roll overs and prevent wild popups from appearing all over the place when the user moves his cursor across the screen. Because there is this built in delay, there probably isn’t a need to optimize the creation of the dialog, and the implemented solution is good enough.

Personally, I do not like the 700 millisecond delay on the rollover, I think it would be just fine cutting the fetch delay in half. I don’t like rolling over a movie and waiting for the dialog. I want to see it much quicker!

Overall, I have to give the netflix site an A- in the way they handle their javascript/css. The do almost everything right, and are strongly optimized. It’s a site done well, much better then 90% of the websites out there, even large well known ones. My hats off to the developers.

After writing this article, I shot Bill Scott an email. He confirms that the delay is on purpose from a user experience perspective to prevent “land mines or mouse traps” and that he talks about it in his book, Designing Web Interfaces: Principles and Patterns for Rich Interactions and Jacob Nielsen wrote about it in his work on Mega Menus (Google it). The mysterious Bob mentioned in the javascript, stands for Back of the Box, not the developer who created it. He also surprised me by saying that changing the popup to happen after 400ms instead of 700ms might be a good idea. I didn’t expect that it would actually get changed, so this might make my Netflix experience a smoother one. I’m happy to have had an impact, even a brief 300ms in passing one.

More Stories By Josh Powell

Josh Powell has a decade of web development experience. He dreams in JavaScript, and loves tweaking code to optimize performance. In his spare time, he rips open code from popular sites to analyze and evaluate.