Another conference morning, another tutorial. This time, John Resig of Mozilla is talking about JavaScript libraries. He’s the creator of jQuery as well as having done a lot of work on Firefox and standards. He’s written one book and working on another. So, I’m eagerly anticipating the session.
Libraries have in common: advanced use of JavaScript, cross-browser support, best practices. He likes Prototype, jQuery (surprise!), and base2 (which I hadn’t heard of; it adds missing JavaScript/DOM features to bring all browsers up to a common high level).
JavaScript testing can be painfully simple, so there’s no reason not to do it. For example, he shows an assert that builds a list of results and then adds that list to the page after the page load is completed. Delayed tests (asynchronous behavior like timeouts) are harder.
Cross-browser code.
Strategies: pick your browsers, know your enemies, write your code.
To pick browsers, he shows a cost/benefit chart for the most popular browsers. IE 6 and Opera 9.5 are the only ones with costs exceeding benefit. IE 6 has the highest cost, but a significant amount of benefit (due to its wide use). IE 7 has the second highest cost, but much less than IE6, and it has even greater benefit. Then we have Firefox 3 with a very low cost and very high benefit. Safari 3 and Opera 9.5 are each pretty low cost, but also pretty low benefit. Yahoo publishes a list of the level of support they provide to various browsers, based on their assessments of costs versus benefit. Firefox is “A-grade” on every platform; Opera 9.5 is, too, except on Windows Vista. The Yahoo list covers more than 99% of the actual users they see hitting their sites.
jQuery supports IE, Firefox, Safari and Opera in their previous (by one), current, and next versions. The work is always on the current version, but they test both back one and forward one version. But he notes that this strategy is ignoring market share, which may not be the best approach for others.
Knowing your enemies means know what browser bugs and missing features you’re going to have to work with. But it also means know the mark-up and external code your pages work with. And even bug fixes can be an enemy, since they can break the code you had to write to work around the old bug.
Browser bugs are generally the main concern here. Have a very good test suite so you’ll know if library updates cause problems, and also apply the suite to browser pre-releases. But what is a bug? It’s only a bug if it’s an error in following specified behavior. If you use unspecified functionality and it changes from version to version, it’s still not a bug.
External code can cause problems (or you can cause problems in it). Working to encapsulate your code much more than JavaScript requires is an important way to protect against this. For example, don’t extend existing objects; someone else might be trying the same thing. Example: IE creates properties of form elements for each input field that has an id; the name of the property is the value of the id. So that sometimes overrides standard properties and methods for those form objects. Since form designers and JavaScript programmers are often different folks, this is particularly vexing.
JavaScript programmers know not to “sniff” browser versions but instead detect browser capabilities. Feature simulation is a more advanced way to do this than just object detection. It makes sure not only that an API is available, but also that it’s working as expected. Write a function that uses the API and examine the result. Save a flag based on the result for later use. You don’t care about the what the function is actually doing (so it should be invisible to users and the rest of your code), you just want to know whether the feature it implements is realiable so you can use it for real application code. You can do more with this method. Different browsers can do the same things but different ways. Feature simulation lets you discover which way will work so you can use that capability.
Interesting example of a browser capability that’s hard to detect: IE 7 introduced a native XMLHttpRequest capability in addition to the ActiveX wrapped method previously used. But the native one had a bug that the ActiveX one didn’t. So object detection would lead programmers to use the native one (even though the ActiveX method was still available) and be exposed to a new subtle bug. And there are some problems that just can’t be discovered, like how some code makes the page actually looks, or whether a particular construct will crash a browser (since you’ll never get the result back if you make that happen).
Writing cross-browser code boils down to reduce the number of assumptions you make. Ideally, assume nothing and check everything you want to use before using it. But taken to the extreme, that’s unreasonable, so you have to draw the line at a point that works for you.
We’re taking an early half-break, and he’s planning another one later. I think that’s smart; three hour-long parts works better for most of us than two 90 minute ones. He’s also giving us a link to presentation slides. But they’re not the ones for this talk! They’re relevant, though. And he’s giving us a corrected URL to the correct slides. The slides ask us to keep them private, so I’m not linking to them here.
Good JavaScript Code
If you understand three things, you’ll be able to write good JavaScript: functions, objects, and closures.
Functions can be defined much as in other languages, but also as anonymous functions assigned to variables or properties of objects:
function f(){return true;}
var f = function(){return true;}
window.f = function(){return true;}
Traditional named functions are available throughout the scope, even in code earlier than the definition, but functions assigned to variables or properties are only available after the assignment has executed.
Okay… you can have named anonymous functions (anonymous named functions?):
var ninja = {
yell: function hello(n){
return n>0 ? yell(n-1)+"a" : "hiy";
}
}
But the function name yell is only available within the scope where it’s defined (the definition of ninja). The property name yell is available whenever you can refer to the object. They’re the same thing here, but don’t have to remain that way (assignment to other objects, alteration of the ninja object). The purpose of this is to allow recursion without problems in those cases. If the function were truly anonymous it would have to call itself relative to the object it’s defined in; but if that’s assigned to a different object, that’s not what we want.
var x = ninja;
x.yell(3);
ninja = {};
x.yell(3);
This works if yell is an anonymous named function. But if it were truly anonymous the second invocation would fail because it would try to call ninja.yell recursively, which no longer exists.
Since functions are full-fledged objects, they can have properties attached to them. We see an example of self-memoization, where the function saves prior results and returns them instead of recomputing them when called again with the same arguments.
Functions exist in a context, and you can refer to the context as this. I’m used to seeing that when the function is a method, and this refers to the object. But it’s usable even for global named functions (this is the global context, so this.something is the global variable something). The .call() and .apply() methods for all functions set the context of the function as well as invoke it.
Declarations inside a function are private, but you can make them properties of this to make them externally available:
function Secret(){
var f = function(){true;}
this.g = function(){false;}
}
Outside code can refer to Secret.g, but not Secret.f. I think. If I’m following this right.
The last section of the talk covers a wide variety of advanced topics in a lot of small bites. It’s reminiscent of the extremely advanced Perl talks that Mark-Jason Dominus gives, and it’s interesting, but way above what I expect to be using directly. I’m much more likely to be the user of a library that requires these techniques inside it, than to write one.
A really excellent, useful talk, even if I did get bogged down a bit at the end.