Speed Up Your JavaScript


Uploaded by GoogleTechTalks on 04.06.2009

Transcript:
>> SOUDERS: Hi, my name is Steve Souders, I work here at Google on web performance and
I've started this new speaker's series, I call web exponents and the tagline is, "Raising
Web Technology to a Higher Level." And so Nicholas is one of the first speakers in the
officially named speaker series. I worked with Nicholas at Yahoo!, he's a JavaScript
guru, he is the author of "Professional JavaScript" and I think co-author of "Professional Ajax"
from Rox and he's also contributed a chapter to my next book, "Even Faster Websites" which
is coming out this month now, I have to start saying that. And he works on Yahoo! Front
Page and he does this a lot, go--goes around evangelizing JavaScript best practices especially
focusing on performance. I'm so excited to have him here today and to present him with
the official Google tech talk swag bag. >> ZAKAS: Nobody's taking a picture?
>> SOUDERS: So please help me welcome Nicholas Zakas.
>> ZAKAS: Hi. So that pretty much covers what I was going to say in my introduction. I usually
start with a who's this guy slide? But Steve kind of cover it all so, I'll just go through
it as if I'm saying it because it--don't really want to repeat, so anyways, here are the lovely
book covers. That's Steve's book over on the right which I contributed a chapter to, out
this month. Anyways, moving right along, the problem is that JavaScript is slow. Hey, I'm
just going to start out by saying that. And the secondary problem is that people notice
that it's slow. Because a lot of the web is about perceived performance and if your thing
is slow and people perceive it to be slow, then you're in a lot of trouble. And there
is a lot of complaints; I mean this is just one of the things that I came across when
I was looking for like complaining about JavaScript performance. But there's tons of stuff out
there and so we really need to be careful, especially now were people have really fast
Internet connections, you know 10 years ago or so people expected the Internet to be slow
and now they expect it to be fast and that's mostly our fault for becoming really good
JavaScript developers. But the browsers haven't really caught up. Okay. So why is JavaScript
slow? People who don't know anything about the web, they usually like, well, there must
be some sort of bad compilation going on, which of course isn't true. The problem is
that there is no compilation going on. And I have my little asterisk there in case anybody
will just going to interrupt and say, "But, but, but." We'll talk about that later. So,
really the issue is that browsers aren't going to help you with your code. Like, they don't
care that your code is running slow, they're not going to do anything to adapt for it.
So really if something has to be done to speed up your code, it pretty much has to be done
by you. And so, by show of hands, who thought that you would see a Smokey the Bear reference
today? Yes, okay. So we're talking about JavaScript performance issues, there's a bunch of stuff
to talk about but in this talk, I'm going to focus on just a few areas. And for now
I'll give you my regular disclaimer that these are things that you need to figure out, if
you need to do, I'm not stand up here and say, "Do this every single time regardless
of what you're doing." Because that does not make any sense, right. You need to take some
measurements, figure out where the bottlenecks are and then figure out if any of this stuff
is actually going to help you in your job. So, the few things we're going to look at
today, first thing is Scope management, which is something I think a lot people don't understand,
so we're going to dig into that a little bit. Data access is where you put your data and
where you're accessing it from, can make a really big difference. Loops, that's everybody's
favorite topic when it comes to performance really. And then no performance talk can really
be complete without talking about the DOM and how slow it is. So, to begin with, we'll
go with Scope management. So, this is a horrible, horrible function that I've written. Please
don't look at it too closely because it doesn't do anything interesting in it. It really is
just a bunch of stuff I threw together to--for an example. But it's a global function setup,
passing some items, do some stuff, setup in a band handler, gets some DOM references and
then move on. But, this is the basis for a lot of what I am going to talk about next.
So, Scope Chains. When you define a function and we are going to assume that this is the
global function called setup that we just saw. There is this internal property called
Scope, hence ECMA-262 has the little braces around it but I don't know how to say that
without it sounding weird. So, we are just going to call it scope. And when the function
is defined that property is filled with a scope chain that has a bunch of information
about the variables that should be available when that function gets executed. So, when
you have a global function like setup, there is only one item in there. And that's the
global object that has stuff like window and document and navigator. And a whole bunch
of other stuff, I just threw those in there to keep this reasonable. When a function executes
there's something called an execution context that's created. And that execution context
has a scope chain of its own that it uses for identifier resolution. And the way that
that's built-up is the execution context is created. It takes whatever was attached to
the function object's scope property, copies that into its own scope chain and then creates
an activation object that has all of the local variables in it and puts that at the front.
So, to take a look at what that looks like, hey I just said that. Let's take a look what
that looks like. So, we have an execution context that was created; let's assume that
setup is being called right now. So, execution context has its own scope chain. So, we start
out by putting what was in the function's scope property there first. So, that's the
global object. And then we'll have this new object come in and get pushed on to the front,
just this activation object. And that has the--all of the local variables in it, which
includes things like this and arguments, any named properties and then any local variables
that were declared in the function. So, what happens is when the function is being executed
and it comes across a variable this process of identifier resolution starts. And it starts
by looking at the first spot in the scope chain and then makes its way down to the back.
So, if it finds it in the first place, awesome, that's what it uses. If it's not found then
it goes on to the next place in the scope chain and so on and so forth until either
it's found or it's not found and then you end up getting an error. In all cases, the
last stop is always the global scope. So, why do we care about this? The reason is that
local variables are really fast to access; right to read from because they're in that
first location in the scope chain. The further you go into the scope chain the longer it's
going to take to resolve those identifiers and that can affect your script speed. So
charts, very fun. This is just a chart with some experiments I ran to see how long it
took based on how deep into the scope chain a variable was. And so across the bottom we
have how deep into the scope chain it is and then across this side is the amount of time.
And as you can see the general trend is that the deeper into the scope chain you go the
longer it's going to take in order to resolve your identifier. So, we have a couple of really
good performing browsers at the very bottom. You probably already know what those are but
we can discuss those later on. Then, we have some, you know, kind of iffy ones in the middle
and we have some really poor performing ones at the top. And I'm guessing you can probably
figure out what ones those are too so, later on as well. So, this is just for reading from
these variables. Writing from variables is not much different. The general trends are
the same. The deeper that you go into the scope chain the longer it's going to take
to resolve those identifiers. So, a little bit more about the scope chain is there's
a few things that will augment it for you. One thing is the with statement. And you're
probably hearing for years now, don't use the with statement. This is part of the reason.
And I'll give you an example of that in just a second. The catch clause of a try-catch
statement has the same effect that it artificially augments that scope chain. And they both do
that by adding an object to the front of the scope chain temporarily and then removing
it later. So, if we go back to the example from earlier, remember this is just the setup
function that I had earlier in this--in this presentation. And let's say that there was
a with statement in the middle of it. As soon as execution flows into the with statement,
you end up with a scope chain that looks like this. There's a variable object that's pushed
to the front. So, now that has all of your with block variables and that means that all
of the local variables to your function are now one step away in the scope chain and that
will affect your performance. So, hey, I said that too, local variables in the second slot
all the with variables are now in the first slot. So, this is why Doug Crockford, oh look,
there he is, always says, "with statement is considered harmful" we should avoid it.
Closures also enter into this conversation. The [[Scope]] property of closures always
begins with at least two objects because it's going to be the global object which every
functions has in there. But, also the scope of the function in which it was created. So,
calling the closure means, that at a minimum you're talking about three objects that are
going to be in the execution context scope chain when that's being run. So, here's the
closure in my earlier example, in a very typical event handler just thrown in there. And here's
what it looks like. So, that's my anonymous function. We have the scope chain, so push
the global object on there first because that's always in there. And, then we have the activation
object from the containing function. So, that has all the variables from the setup function
that gets pushed on there too. And this is just the anonymous function object that has
this. So then, when you go to the execution context, immediately you have three objects
in the scope chain. And that means anything that you are accessing from inside of the
closure that wasn't defined there is going to take longer to access than the others.
So, some recommendations for this. Store any out-of-scope variables in local variables.
It probably doesn't make much sense unless you're using it two or more times because
you have to take at least one hit to store in a local variable. But, if so, storing them
local variables can really speed things up and that goes especially for global variables
because those always take the longest to resolve since they're always the last spot in the
scope chain. Avoid the with statement, it adds another object to the scope chain and
that means local variables are going to be slower to resolve. And be careful with try-catch
because catch behaves the same as with. I'm not inclined to say, don't ever use it but
if you have a spot that's always throwing an error and you expect it to throw an error,
then there's probably some better way to handle this than taking the performance hit every
time. Use closure sparingly. If you can avoid using them, you can usually save yourself
some trouble and then don't forget to use the var statement when you are declaring variables
because accidentally creating a global variable means that's it's going to take longer to
resolve. There's a question in the back? >> Are you saying that in the--even in the
absence of any with statements or other scope changes in the [INDISTINCT] function, look
ups are not cached, every single means use of variable goes through the same--this only
procedure? >> ZAKAS: And so, the questions was, regardless
of with and try-catch am, I saying that these identifier resolutions aren't cached, correct?
I'm not. >> So, then why would caching other global
variable in a local variable matter at all? >> ZAKAS: So it's a good question. I always
say that my knowledge of JavaScript ends right where the interpreter begins. So, I'm not
entirely sure going across all the JavaScript engines. What they're doing behind the scenes,
all I am sure of is when I was running these tests, these were the results that came up.
If anybody here works on Chrome and wants to fix that? Please do. So, here's just an
example again in the setup function, where I took a global variable document and just
stored it in a local variable. And so I'm eliminating the number of global look ups
here which previously was three and now it's just one. And this is really over simplified
example but when you have a large amount of code that's accessing globals, you'll see
a difference there. So next, JavaScript Performance Issue, data access. There's four places from
which you can get data in JavaScript in general. One is your literal values so your strings,
your bullions, your numbers, no undefined. Variables, of course. Object properties and
then array items. And these all work kind of differently. So just generally speaking,
accessing data from a literal or a local variable is always the fastest. And in most cases the
difference between those two is negligible, so you should feel free to use local variables
whenever you want. The interesting thing is when you're accessing data from properties
or array items, that tends take longer. Exactly which one of those takes longer is really
up to the browser, different browsers have done different optimizations for this. And
another beautiful chart. As you can see the blue is a literal value. The red--if it turns
out to be red for other people looking, at least it for me, is local variables, yellow
are array items and the kind of greenish bluish thing is object properties. Okay. And for
the rest of you it would be the bar on the far right that I'm referring to. And you can
see the general trend in some browsers perform really well, some browses performed not so
well without naming names. But generally speaking the literal and the local variables, the literals
and the local variables tend to be pretty fast compared to property lookup and array
item lookup. Firefox has done some interesting things to really optimize array item lookup,
so that tends to be pretty fast compared to object properties. Some of the other ones
it's a lot closer, some not so much. But, in the general trend is that literals and
local variables are faster. Property Depth also matters. So, if you have object.name,
that will be faster to access than object.name.name, which kind of makes sense because you're doing
two property lookups there instead of just one. And the deeper the property the longer
it takes to retrieve. So, in little chart just to show as you--as you continue on going
further and for--further and further into objects, you know, .name.name.name, things
starts to get slower and slower. This is the question I got the last time I was talking
about performance was, if there is any difference between using dot notation and bracket notation
to access properties and there really isn't except for Safari which is kind of strange.
It's the only browser were dot notation, you can actually tell that there is a difference
between bracket notation. And that even goes up through like the latest Safari for betas.
But in general you really shouldn't have to choose between one or the other if you're
concerned about performance. Some recommendations for this, this type of stuff you should store
in local variables, so any object property that's accessed more than once. Any array
item that's accessed more than once, try to minimize your deep object property and array
item lookup. 3 That'll help as well. So again, another stupid little function, just to prove
a point. I have a data object being passed in and data.count has been referenced a couple
of times, data.item being referenced. I can change it around so that I'm eliminating property
lookups here. So, just by storing stuff in local variables. And if you want to see like
those do actually make any difference in a few the browsers here. Firefox not that that
big of a difference, it takes about a 5% less time to complete. Safari takes about 10% less
time to complete and IE about 33% less time to complete. Probably not surprising, you
tend to see the largest performance gains in IE when you're doing this sort of stuff.
So next part, Loops. Always very exciting. So, we have a bunch of loops that are defined
for us in ECMA-262, the 3rd edition, for, for-in, do-while, while, and then you can
throw the ECMAScript for XML for each that Mozilla has. But, reading and/or talking about
loops, we're not talking about for-in and for each, because for each, not used all that
much, not implemented everywhere, for-in, really inefficient if you're just iterating
over a bunch of values in an array. And so what we're really talking about in this context
is for, do-while, and while. And so, and basic examples, I'm sure you guys, all know how
to write these. So, which loop do you use if you want it to be the fastest? And I've
seen it's the bay pop-up every once in a while. And so the interesting thing is it really
doesn't matter which of these loops you use. So what does matter if the loop type doesn't,
what matters is the amount of work done per iteration. And that includes the terminal
condition evaluation in any incrementing, decrementing or anything else that you're
doing every time through the loop. And then the number of iterations. And these don't
change depending on which loop you're using. But, you still need to worry about these.
So if you want to fix loops, the really easy way is just decrease the amount of work per
iteration and then decrease the number of iterations. Right? Seems simple, in practice,
not always as simple but just for some examples here, again, the three different types of
loops here, if you want to take a look at the things that are being repeated for each
iteration, we have the terminal condition being evaluated each time, we have this incrementing
done each time and then of course the body that's actually processing whatever value
that happens to be. So some easy fixes. Eliminate object property in array item lookups. And
just talking about this, if you're doing those every time through the loop, it's going to
slow things down. So in this rewritten example, I've just moved, values.length into a local
variable, so that each of the loops no longer has to do that property lookup each time through.
So just replace those there. Next is try to combine the control condition in any control
variable changes into a single statement, because the best way to make things fast is
to do less stuff. Right? Work avoidance is primary when you're concerned about performance.
So, if you look at this, in the terminal condition here, you're actually doing two evaluations
saying is j < len, and is that equal to true. And so, if we can narrow that down so it's
just one evaluation, things will end up going faster. So, here I just replaced those terminal
conditions, basically flipped the loop so that they're going backwards and counting
down from the last item in the array to zero. And that means that I'm just going to have
one condition to evaluate, instead of two. Just is that equal to true or not. And that
tends to be faster. And, in this case, it was up to 50%. It took about 50% less time
when I rewrote it like this, than the original one did and that was across browsers. So things
to avoid for speed, for-in, because it's not actually just looking at the numeric items
in your array, it's also looking at the properties and it has to go up the scope, excuse me,
prototype chain that tends to be slow, for each, has similar issues and not all that
popular because not really implemented everywhere, yet. Fifth edition of ECMA-262 introduces
this for each method on arrays natively, that's also something to avoid it tends to be really
slow. And basically anything that is a function based iteration, all the JavaScript libraries
have them, these are just the few of the examples here. Those end up being really slow. So,
why? Most of them are set up kind of like this, this is the native implementation, where
you pass in a function to a method, that function gets executed for each item that's in the
array and it tends to pass in, you know, the value, the index and then maybe the array
object as well. Problem is that, you're introducing an additional function and with it comes all
the additional overhead of executing a function. And so, function requires execution, we have
to have an execution context created, destroyed, we're also creating an additional object in
the scope chain, in this case, because it's a closure. So, anything that you're accessing
from outside of that function again is going to be slower. And in most of the tests that
I ran, this ended up taking about eight times as long as just doing a regular four loop.
So, if you care about performance, you may want to double check any place that you're
using function-based iteration like this. So, the last thing I'm going to talk about
today is the DOM, which you really can't talk about JavaScript Performance without talking
about the DOM or as I like to call it, The DOM. Oh come on, come on, there's nothing
for that? That was great. The DOM is usually the slowest part of any of the JavaScripts
that you're executing. And there's a lot of reasons for this; unfortunately, we can't
go into all of them today. So I just thought I would pick a couple and show you what's
going on. So, the first evil part of the DOM that I want to talk about is the HTMLCollection
Object. And this is something that a lot of people just aren't aware of. So, HTMLCollection
Objects live everywhere in the DOM and they kind of do so insidiously. All of the collections
that are on the document object are HTMLCollection Objects. So, document.images, document.form,
document.links, all that stuff. It's also returned by getElementsByTagName and getelementsByClassName
so that everywhere hard to avoid. So, if you look at a DOM level one spec, this is the
section that defines HTMLCollection and you can find the evil part like right here in
the spec. And since it's really small up there, I'm just going to bring it up for you. It
says, Note: Collections in the HTML DOM are assumed to be live, meaning that they are
automatically updated when the underlying document is changed. So, okay. That means
that it's not a static snapshot. It means, it's going to continue to change even after
you have a reference to it. And this causes a lot of problems in a lot of ways. So, this
example here, I'm just getting a reference to all of the divs there in the document.
And then for each of those, I'm creating a new one. So, in theory, this should just double
the amount of divs that are on the page but in actuality this becomes an Infinite Loop.
And the problem is because HTMLCollections again are live and they're being updated even
after you've retrieve them. Divs.length is being incremented every time you're going
through the loop. So, you will never reach the end of that. So, i is being incremented,
that's great. But then I go in and I add a div that automatically gets picked up in the
HTMLCollection and that means it'll just continue going on. The i will never reach any limit
in this case. So, HTMLCollection Objects. Beware they look like arrays but they're really
not. Like you can access the items using bracket notation. You can also access a length property
but they are not arrays, they do not inherit from the native array. They represent the
results of a specific query that was run against the DOM. So, if you consider the last example,
we're basically saying, give me all the div elements that are in the document and if all
of the div elements in the document change then that's automatically going to be updated.
And the query for this tends to be re-run whenever that object is accessed. And so,
it tends to be very slow. Again, generalization. Actually there's... Yes, there's my exceptions.
So, Opera and Safari have done some optimization for this so that they aren't nearly as slow
doing some sort of caching. But other browsers still are not. And so, if we take a look at
these two loops, the top one is just accessing an array, like iterating over it and there's
nothing being done in the loop at all, right. I'm just iterating over the array. The bottom
one, I'm using an HTMLCollection to do exact same thing. Right. And there--besides that,
there is no difference between these two loops. So, if you look at the performance across
a few browsers here, what you'll find is that the bottom loop takes 15 times as long in
Firefox, takes 53 times as long in Chrome 2 and takes 68 times as long in IE. And so,
what is the difference here? That's the difference. Right. We're doing items.length against a
regular array or doing divs.length against an HTMLCollection. So in this example, I've
rewritten it so that I'm storing the length in a local variable first and then doing the
loop so little bit of a change there. But now if I run these in various browsers, they
end up being pretty much equivalent. That there's not much of a difference at this point
because we're just doing one property lookup in each and we've minimized the overall effect.
So HTMLCollection's bad, HTMLCollection's in loops, even worse. So what can you do?
Minimize property access as much as possible; store the length or the items in local variables
if you're going to be using them a lot. The thing that you don't want to do is have a
bunch of, you know, HTMLCollection dot all over your function, that's really going to
slow things down. If you need to access items in order from that HTMLCollection frequently
and do just like a snapshot, then you should store those in a regular array and access
them there because that will be faster. And this is just a function, I'm sure many of
you have written something similar over and over. This is just a function that will convert
something into an array. You can use the array prototype contact method in any browser except
IE for this. In IE that will throw an error and then you just go through and manually
copy over, you try them into an array. Once you have all those elements in an array, it's
going to be much faster for you to access that information. So, next evil part of the
DOM is Reflow. I didn't do the voice for that one because I didn't think you guys would
like it. So, reflow; the best explanation I found Chris Waterson from Mozilla; Reflow
is the process by which the geometry of the layout engine's formatting objects are computed.
So, what does that mean? It means, that's how we figure out how big stuff is when we're
trying to render it in the browser. And so, when does Reflow happen? Well, necessarily
it happens on initial page load to make sure that your page looks the way that it should.
It happens when you resize the browser, especially true if you have a layout that flows. It happens
when DOM nodes are added or removed because that may affect the layout in some way. It
happens when layout styles are applied. And it can happen when layout information is retrieved
from the DOM. So initial page load and browser resize, there's not a lot we can do about
that, right. That is going to happen regardless. So, let's skip over that and let's talk about
when DOM nodes are added or removed. So, another really bad example is just adding a bunch
of items to a list in the DOM. So creating a bunch of li elements, setting in your HTML
on them and then appending them to the overall parent. So, Reflow in this case will happen
every time that appendChild is being called. It's updating the document that you're seeing.
And so all of these calculations have to happen at that point in time to make sure that things
are updated appropriately. So to the rescue, my friend DocumentFragment. A DocumentFragment
is a document-like object, except it has no visual representation, right. You can never
add a DocumentFragment to your document and display it to the user. It's just a construct
that can be used behind the scenes to make your life easier. It's considered a child
of the document from which it was created. A child is probably the wrong word for that,
but the document that created it is considered to be the owner of that DocumentFragment.
And when you pass the DocumentFragment into addChild, instead of adding itself, it adds
all of its children. So, it's a really easy way to build up a bunch of stuff to be added
to the DOM and then add it in a later point. So I can rewrite this code to use a DocumentFragment,
you create it by saying document.createDocumentFragment. And here what I did is instead of adding each
of those items on to the list itself; I'm adding them on to the fragment inside of the
loop. And there'll be no Reflow there because you're not actually updating the visual representation
of the page. Where the refill will happen now is just once at the end when it go and
add all of those on to the list. So we've basically limited the number of Reflows in
this example to one, were before there was ten, not one that being much faster. So another
time when Reflow happens when layout styles are applied. And layout styles are usually
stuff like height and display and font size and width. You've changed something from a
block in line vice versa. All of these end up causing Reflow. So, what can you do about
this? You want to minimize the number of times that you touch the style object. Right. If
you need to make a bunch of changes all at once then the best thing to do is to define
the CSS class that has all of those defined. And then just change the className property.
So, if I rewrote that and I just defined a class called active that has all of that information
in it. And I changed className equal to active that causes one Reflow instead of the three
that we had before. And I'm just a big fan in general--especially for maintainability
purposes to do this as well because it can really get hairy if you're touching the style
object in a bunch of places in your code. So hey, Reflow right there. So, the last point
we're... Steve, do you have a question? >> SOUDERS: So, what if you didn't know the
style ahead of time so you couldn't define a rule like that? Could--is it bad to do all
the style changes thru CSS text? Is that [INDISTINCT]? >> ZAKAS: So, okay. The question for anybody
that didn't hear it, was if you didn't know all the styles ahead of time? Would it be
still okay to use CSS text to set it instead? So the answer is, yes. As long as you're grouping
things together, that will just cause one Reflow. What you just want to avoid is, you
know, style.left equals whatever style.right equals whatever. And doing them one by one
like that. But, CSS text is another valid way to go. Yes?
>> Does it mean if you change continuously like say you're moving an object across the
page? Is there any way to do that and actually write that code right now?
>> ZAKAS: So, the question was, if you're--if you're changing a style continuously such
as moving an object across the page? >> Oh, [INDISTINCT].
>> ZAKAS: Yes. Is there a really efficient way to do that? Unfortunately, I don't think
so. Because--you know, and I'm just going to just state my bias up front. I really don't
like animation on web pages. But it is one of those things where you really can't--if
you want to move something, you know, left to right. There's nothing else you can do
aside from setting left periodically at some regular interval that moves it across. What
I would try to minimize is the number of other dimensions that you're animating at the same
time. So, if it's possible to, you know, just go left or right and not need to go up and
down and in circles that will tend to be faster but in the--the more properties that you end
up changing then the slower it's going to end up being until browsers optimize that
sort of stuff. There are other questions about that? There is?
>> Yes. Well, actually it's about the previous thing Document Fragments. If you had the--if
you're getting manipulation with them like a table. It's just what I'm doing. And there's
like, you're inserting one element but then you might want to change the others. Yes,
like ultimate roll callers? Is it effective to then like copy the entire thing or put
it--take it out of the DOM and put it in a vacuum side and then put it back or how did
you...? >> ZAKAS: Okay. So the question was in the
case of like working with a table. If you're removing a row, adding a row what's the best
way to deal with that? DocumentFragment is just one way of doing that. Really, if you
remove an element from the DOM so that it's just not being visually represented anymore?
You can do all the changes that you want and then add it back. So you don't need to attach
something to a DocumentFragment in order to do that. If you wanted to--just say for an
example, you wanted to remove one of those rows, do a bunch of stuff to it and then insert
it back. You could say, you know, remove child, pull it out, do all of your--do all of your
work on the DOM node while it's not part of the DOM and then add it back. And then you
only end with one Reflow. >> [INDISTINCT].
>> ZAKAS: Yes, I'm sorry. Two Reflows. >> What, simply saying just [INDISTINCT].
>> ZAKAS: So setting display might now work as well. I tend to be more of a fan of just
removing things when I'm working on them but there's no reason why display:none wouldn't
work as well. The only thing to be careful of is how much that ends up shifting your
page. Which I guess would be the same for moving a DOM element anyways, so do that too.
>> What about visibility:none? >> ZAKAS: I don't think visibility:none would
work. Your visibility hidden? >> Hidden inside [INDISTINCT].
>> ZAKAS: Yes, if... >> Is there any way to Reflow the page but
you can also [INDISTINCT]... >> ZAKAS: Yes, it depends on what you're doing
because with visibility hidden, you're still reserving the space in the page, so if you
hid stuff but then like you change the height and it ended up pushing it up, then you would
still end up with the Reflow. I thought there was one more.
>> And so display:none would be the same as removing it. It would still prevent the Reflows
within the browser display element. >> ZAKAS: Yes, display:none, helps a lot when
you--yes. >> So, I discovered a few years ago in IE,
I forgot if it was 6 or 7 that we took the display:none approach and we made some changes
and even though elements have display:none, IE would still take action on it. In this
case, we were setting a background image and it would then--normally background images
aren't downloaded until their element is actually visible and even though the element was display:none,
IE would start downloading the background image. So we--our page was slow because we're
downloading all of these background images when we thought the elements actually weren't
going to have any effect, but they were in the DOM and IE ignored display:none. So I
think removing them and then adding them back is actually better.
>> ZAKAS: There you go from the performance guru.
>> Did you do any measurements of--across browsers of different length prototype chains?
>> ZAKAS: I did, but I didn't include it in this presentation. But generally the prototype--the
prototype chain issue is dissimilar to the scope chain issue, is the longer your prototype
chain is and the further into that prototype chain that you need to go, then the slower
the retrieval of the property is going to be.
>> Did you have the same pattern of winners and losers?
>> ZAKAS: Yes. That pattern didn't change very much regardless of what specific performance
issues I was measuring. Trying really hard not to pick on any particular browser.
>> Too late for that. >> ZAKAS: So the last time that Reflow may
occur is when layout information is retrieved and I say it may occur because a times during
the lifecycle, the browser may cache up a few Reflows and wait to do them and if you're
trying to access some layout information, that may trigger the Reflow immediately to
make sure that that layout information is correct. So a few of the things that may end
up triggering Reflow such as offsetWidth and scrollleft and then accessing information
from get computed style. And those can all end up causing Reflows if things are cached
up ahead of time. So, again, not every time, but something to watch out for. So, if you're
going to be using that sort of information, certainly if you're going to be using it multiple
times in the same function, you should try to minimize that by storing stuff in a local
variable and then using that instead just to make sure that you're not going to be triggering
Reflow by mistake. So, and some of the stuff to speed up your DOM, be careful when using
HTMLCollection objects. Perform the DOM manipulations outside of the document if possible, change
the CSS classes, not CSS styles and try to group those changes as much as possible. Be
careful when accessing layout information because that may trigger a Reflow as well
and really be the overall message is just be very careful when you're touching the DOM
because it can affect your performance. So, usually when we're talking about performance,
a lot of people are like, oh man. If browsers will only be better then we wouldn't have
to worry about this. Like is this going to be forever? And the happy answer is, No. So,
we have a few browsers making a lot of headway in this respect. So a bunch of browsers with
optimizing engines, you may have heard of this, Chrome browser, that has V8 in it, there's
also Safari 4 with its Nitro engine, there's Firefox now 3.5 with TraceMonkey. Opera is
also working on one, unclear if that's going to be Opera 10 or Opera 11. But, the cool
thing is that they all used a native code generation and Just-In-Time compiling to achieve
faster JavaScript. So, finally we'll have all of the optimizations that compiling languages
have and you already saw that in the charts that I was showing earlier that the more modern
browsers, their performance just blows away the older browsers. So, hang in there. Hopefully,
it shouldn't be too many more years before we're out of this mess. So, just to go over
what we talked about today, mind your scope, scope chain management is important. Local
variables are your friends; you can use them all over the place. Function execution comes
at a cost, so be careful of that. Keep your loops small as much as possible. And avoid
doing work which is what I tried to do not just in JavaScript but in life. Minimize the
DOM interaction as much as possible. And of course used a good browser and encourage others
to do so. So, questions anybody? Okay. That was a more of a statement, not a question
but that's okay. Yes? >> So I'm kind of wondering, you know, oh
sure. I have a booming voice though. >> ZAKAS: You should be up here.
>> So, I'm wondering--I'm wondering about the scale across the different things. So,
for example, I want to access this DOM object a lot. So, do I do it once globally as opposed
to doing in a bunch of times locally? >> ZAKAS: Can you sir--can you repeat that
again? >> So, let's say for example, I want to get
a DOM object. >> ZAKAS: Yes.
>> And I could do it once globally just at the top of my JavaScript, I could say, you
know, far fu equals get element by ID blah. And then in--and then in each function that
can access it or I can say, "Well, gee, you told me that I should use local variables,
so maybe I'll just do it locally and not keep accessing that global area but now I'm calling
the DOM 10 times or across 10 different functions, does that makes sense?
>> ZAKAS: Yes. So I mean, it really--it really matters where you're seeing the bottleneck.
So, that's what I started out with. Is...? >> I was assuming maybe a DOM access was a
magnitude slower than a global scope access. >> ZAKAS: Oh I see. So, we're really talking
about DOM access versus accessing global variable. Yes. Again, it kind of depends on what sort
of effects that you're seeing as to what approach you want to take. Usually I recommend that
if you're going to be accessing any DOM elements multiple times, that you store it somewhere
other than just on the DOM. Whether that be an object property or a local variable, that
can be up to you. I usually don't recommend storing it in a global variable. Just because
you still have to have that extra step up the scope chain in order to resolve that identifier.
Does that answer your question? Okay. Steve. >> SOUDERS: Yes. I had two questions. I'm
embarrassed to say I've never used create DocumentFragment before, is that supported
across all major browsers? >> ZAKAS: Yes, create DocumentFragment is
supported across all major browsers. >> SOUDERS: And then a bigger question is,
that comes up a lot with these types of performance optimizations, is a lot times it's hard to
get their correct prioritization of test like this compared to other things like UI changes
or new features, do you have any success stories or case study feedback about how these techniques
were applied and what kind of, you know, huge or marginal impact it had on the site?
>> ZAKAS: Yes. First, I don't have a lot of recent anecdotes I can add because of the
nature of my work recently. But, when I started the Yahoo!, I worked on my Yahoo!, just the
personalized homepage product. And we're rebuilding it from scratch. And we did have some performance
issues initially and a lot of this stuff ended up applying. The major things that we found
were cutting down on DOM interactions. We're--what helped to speed it up. And when I say speed
it up, what we considered to be the most important thing was really the user's perception of
how fast the app was. And so, initially, you know, we go out with a few beta users. People
say, "Oh, it's slow." And then we say, "Okay. So, what can we do?" So, we went back and
looked over the code and what we found one major thing was that we had a ton of event
handlers attached to the DOM. So, as you're going around the page, there was all kinds
of stuff that was happening. And that tended to slow down the overall page interaction
and that really annoyed people. So we went to event delegation, which unfortunately,
I did not have time to talk about today but it follows along with the touch the DOM as
lightly as possible theory, is that when we reduce the number of event handlers that we
had on the page, when we stopped doing animations, as the gentlemen mentioned earlier, you're
just doing style changes all the time. We removed what I considered to be gratuitous
animations. All of the performance complaints started to slow down. I mean, there's always
somebody that's accessing it over some modem somewhere that thinks it's slow. But, generally
speaking, the biggest bane that we got for the buck was managing our DOM more effectively.
We did end up going back and optimizing a bunch of loops that were in the product as
well, like there was a bunch that was going on when the app was first starting up. But,
and really biggest thing was just a dealing with all of the DOM issues. Yes.
>> So the moral is, put the gratuitous animations in the beta and then take them and everybody
will be so happy they won't bother you thereafter. >> ZAKAS: Yes. It's incredible how well that
works. >> One question about the global variables,
you know. Do you think it's a better idea to use some like static variables instead
of a global one? For example, you know, if I have like 20 global variables, I split them
into like three groups and for each group I create a, you know, global class but, you
know, for each variable, I--you know, define them as static members of, you know, each
individual class, you know. Will that improve the speed?
>> ZAKAS: I think the problem there is that you're then invoking the property axis performance
penalty, as opposed to just accessing a regular variable. So, I'd say whenever possible trying
to keep stuff that you're using frequently in variables is a better way to go versus
storing it on a property regardless if it--if it's, you know, a static property on a constructor
or a property that's on an object somewhere. Sure, any other questions? Yes?
>> Excuse me, how do you do your benchmarking for other cross browsers you...?
>> ZAKAS: So, I built a page that is essentially just a performance test suite and just go
in and I have a bunch of different tests to run and then I specify how many tests and
it just kind of goes off automatically times it, you know, with all the caveats to come
with using a data object for timing, per number of iterations and then it does that a number
of times and then takes the average. I can--after this is over, I can send some URLs to Steve
if he wants to distribute out, if you want to play with it yourself.
>> And then the other question I had was it sounds like four each and when you're talking
about the four loops that all of these things are coming in future versions of JavaScript
or ECMAScript and they slow everything down. So like, why they are putting them in or why
do they encourage people to use them if it slows it down, if it naturally slows them
down... >> ZAKAS: Well, I think that there's a difference
between encouraging people to use them and providing them for people to use. Because
there are a lot of people that write JavaScript that aren't all that interested in performance
and then these become convenience methods for them. You know, we can get into a long
conversation about foreach and why that was interesting and necessary for an ECMAScript
for XML. But the array.foureach, you know, it's one of those things that people seem
to have been clamoring for and I think the evidence of that is that it's in every major
JavaScript library and if we implement it natively in a browser, that's going to end
up being faster than any of the library implementations. So you could argue that we're doing a little
bit of help to the JavaScript library in terms of performance but I don't think that you
can say just because it's there that we're encouraging people to do something that's
slow because if what you're really care about is usefulness then they're incredibly useful.
If what you care about is performance, then not so much. But, you know, you could say
the same thing about like why was the with statement ever put in if it is so horrible
and people have been saying not to use it for so long. At some point, somebody thought
it was a convenient thing to put in and so it's there.
>> Hi. Are there any good JavaScript profiling tools that will tell you which parts of your
code are taking most time? >> ZAKAS: Well, Firebug is of course the obvious
one, that has pretty well. WebKit has a new profiler that is kind of slick. You should
check that one out. Why-why has a profiler utility but it's targeted so you need to say
specifically, you know, I want to profile this function and then go and run that versus
just a general profiler. But those are the three that I tend to use. I haven't messed
around with the IE-8 one yet. Think I'll go home and do that today. But generally those
are the three that I use. Okay. So I get my closing, if you want--if you want to contact
to me at all. There are the various places to do it. And then all the beautiful images
here, creative comments, so. That's it.