Secrets of
JavaScript Libraries
          (Left to Right)
    Sam Stephenson (Prototype)
        Alex Russell (Dojo)
   Th...
What to Cover
✦   Topics:
    ✦ JavaScript Language
    ✦ Cross-Browser Code
    ✦ Events
    ✦ DOM Traversal
    ✦ Style
...
Secrets of the
JavaScript Language
// Set up a class and create an element

var Clock = Class.create({
  initialize: function() {
     this.createElement();
...
// Display the time

var Clock = Class.create({
  initialize: function() {
     this.createElement();
  },

  createElemen...
// Add the timer

var Clock = Class.create({
  initialize: function() {
     this.createElement();
     this.createTimer()...
// Add some options

var Clock = Class.create({
  color: quot;blackquot;,
  format: quot;#{hour}:#{minute}.#{second}quot;,...
// Use #toElement

var Clock = Class.create({
  ...

  toElement: function() {
    return this.element;
  }
});



$(docum...
// Subclass it

var AmPmClock = Class.create(Clock, {
  format: quot;#{hour}:#{minute}:#{second} #{ampm}quot;,

  getTime:...
// Or monkeypatch it

Object.extend(Clock.prototype, {
  format: quot;#{hour}:#{minute}:#{second} #{ampm}quot;,

  getTime...
Secrets of
Cross-Browser Code
Browser Sniffing
    (wait, hear me out)
Conditionally evil
In order of desirability:
capabilities
    &
  quirks
Capabilities
  are easy to sniff
Quirks
are... more troublesome
Object detection
          (for capabilities)

 if (document.evaluate) {
   // ...
 }
Distill to a boolean
          (for stuff in the gray area)

var thinksCommentsAreElements = false;
if ( document.createEl...
...then sniff
       (for outright bugs/quirks)

if (Prototype.Browser.IE) {
  element.style.filter =
   quot;alpha(opacity...
Try to do the right thing,
      but don’t stand on principle
The social contract
 Good faith from browser makers ➝
   good faith from web authors
Secrets
of Quality
Assurance
1,500




                          750




                          0
1.5.0
        1.5.1
                1.6.0.2
Debugging
✦   Localize & Reproduce
    ✦ Find the smallest possible code that
      generates the problem
✦   Easy test-ca...
Secrets of
  Events
Event handlers
     are tricky
Don’t store them on the
     element itself
     circular references = sadness
Build a global hashtable
        (like jQuery does it)
Element Data Store
✦   Each element gets a unique ID
    (bound w/ a unique property)
    elem.jQuery12345 = 1;
✦   Points...
Then clean up
on page unload
 So that IE doesn’t keep them
  in memory in perpetuum
Fixing memory leaks
Internet Explorer 6
    red-headed stepchild
Don’t “prove” your code
     has no leaks
       (view the results!)
Drip
is awesome
Test page:
Create a bunch of elements, assign each an event
  handler, then remove each one from the page
Demonstrating the Leak
    Notice the stair-step effect
Plugging the leak
// In Prototype, will remove all event listeners from an element
Event.stopObserving(someElement);

Even...
Redefining functions
   add “before advice” to functions
    that need to remove elements
Code sample
Element.Methods.update = Element.Methods.update.wrap(
  function(proceed, element, contents) {
    Event.purge...
Drop-in fix
for Prototype 1.6
Custom Events
• Everything is event based if you squint
• DOM is a good foundation
• Terrible for stitching together non-D...
Custom Events (contd.)
// in Dojo:
dojo.subscribe(“/foo”, function(e, arg){ ... });
dojo.publish(“/foo”, [{ data: “thinger...
Secrets of
DOM Traversal
Selector Internals

• Optimized DOM
 • Top-down vs. bottom-up
 • Caching + winnowing
• XPath
• Native, aka: querySelectorA...
Selector Internals
• Tradeoffs: size vs. speed vs. complexity
• Prototype: XPath when possible, else DOM
 • Good perf, low...
Secrets of
  Style
Computed Style
✦   IE way vs. Everyone else
    ✦ IE returns “actual value”
    ✦ Everyone else returns “pixel value”
    ...
Pixel Values in IE
✦   if ( !/^d+(px)?$/i.test( ret ) && /^d/.test( ret ) ) {
    	

 // Remember the original values
    ...
Computed Style
✦   Safari 2 & 3:
    Giant mess for display: none elements
    ✦ Safari 2
      ✦ getComputedStyle() retur...
Finding dimensions
Dimensions of what?
  content box, border box, margin box...
DHTML properties
   clientWidth, offsetWidth
The value you want is not captured by any property



                Lorem ipsum dolor sit
                amet.




    ...
Computed styles
 getting padding & border value
The fool-proof,
    painful way
           Take the offsetWidth,
then subtract computed padding & border
Code example
Element.getCSSWidth = function(element) {
  element = $(element);
  return element.offsetWidth -
   parseFloa...
Secrets
of Animation
Old-School
“Effects”
setInterval
Events-based
Secrets
ofJavaScript
Deployment
CONTENT
EXPIRATION
Concatenation
GZIP
4-5x smaller
Packaging
• Dev vs. deployment constraints
• No library a single file, but all ship that way
• # of requests largest constr...
Packaging
// in Dojo:
dojo.provide(“foo.bar.Baz”);
dojo.require(“dojox.dtl”);

// in GWT:
package com.foo.bar;
import com....
Packaging

• The build-process divide
• Library support vs. server concat/shrink
• Can we strip “dead” code?
• Social arti...
HTML Insertion
✦   $(“div”).append(“<b>foo</b>”);
✦   Convert HTML String
    ✦ innerHTML
    ✦ Range: .createContextualFr...
HTML Insertion
✦   // Fix “XHTML     ”-style tags in all browsers
    elem = elem.replace(/(<(w+)[^>]*?)/>/g, function(all...
Script Execution
✦   Execute Script in Global Scope

✦   var head = document.getElementsByTagName(”head”)[0] ||
    docume...
Questions
✦   Panelists:
    ✦ John Resig (ejohn.org)
    ✦ Sam Stephenson (conio.net)
    ✦ Alex Russell (alex.dojotoolki...
Secrets of JavaScript Libraries
Secrets of JavaScript Libraries
Secrets of JavaScript Libraries
Secrets of JavaScript Libraries
Secrets of JavaScript Libraries
Secrets of JavaScript Libraries
Upcoming SlideShare
Loading in …5
×

Secrets of JavaScript Libraries

53,462 views

Published on

This is the presentation from the "Secrets of JavaScript Libraries" panel at SXSW 2008.

Published in: Technology
3 Comments
64 Likes
Statistics
Notes
No Downloads
Views
Total views
53,462
On SlideShare
0
From Embeds
0
Number of Embeds
3,520
Actions
Shares
0
Downloads
1,423
Comments
3
Likes
64
Embeds 0
No embeds

No notes for slide

Secrets of JavaScript Libraries

  1. Secrets of JavaScript Libraries (Left to Right) Sam Stephenson (Prototype) Alex Russell (Dojo) Thomas Fuchs (Script.aculo.us) Andrew Dupont (Prototype) John Resig (jQuery)
  2. What to Cover ✦ Topics: ✦ JavaScript Language ✦ Cross-Browser Code ✦ Events ✦ DOM Traversal ✦ Style ✦ Animations ✦ Distribution ✦ HTML Insertion
  3. Secrets of the JavaScript Language
  4. // Set up a class and create an element var Clock = Class.create({ initialize: function() { this.createElement(); }, createElement: function() { this.element = new Element(quot;divquot;); } });
  5. // Display the time var Clock = Class.create({ initialize: function() { this.createElement(); }, createElement: function() { this.element = new Element(quot;divquot;); var date = new Date(); this.element.update( date.getHours() + quot;:quot; + date.getMinutes().toPaddedString(2) + quot;.quot; + date.getSeconds().toPaddedString(2) ); } }); $(document.body).insert(new Clock().element);
  6. // Add the timer var Clock = Class.create({ initialize: function() { this.createElement(); this.createTimer(); }, createElement: function() { this.element = new Element(quot;divquot;); }, updateElement: function() { var date = new Date(); this.element.update( date.getHours() + quot;:quot; + date.getMinutes().toPaddedString(2) + quot;.quot; + date.getSeconds().toPaddedString(2) ); }, createTimer: function() { window.setInterval(500, this.updateElement.bind(this)); } });
  7. // Add some options var Clock = Class.create({ color: quot;blackquot;, format: quot;#{hour}:#{minute}.#{second}quot;, initialize: function(options) { Object.extend(this, options); this.createElement(); this.createTimer(); }, createElement: function() { this.element = new Element(quot;divquot;); this.element.setStyle({ color: Clock().element); $(document.body).insert(new this.color }); }, $(document.body).insert(new Clock({ color: quot;redquot; }).element); $(document.body).insert(new Clock({ format: quot;#{hour}:#{minute}quot; }).element); updateElement: function() { this.element.update(this.format.interpolate(this.getTime())); }, getTime: function() { var date = new Date(); return { hour: date.getHours(), minute: date.getMinutes().toPaddedString(2), second: date.getSeconds().toPaddedString(2) } }, ...
  8. // Use #toElement var Clock = Class.create({ ... toElement: function() { return this.element; } }); $(document.body).insert(new Clock()); $(document.body).down(quot;div.clock > divquot;).replace(new Clock()); $(document.body).down(quot;div.clockquot;).update(new Clock());
  9. // Subclass it var AmPmClock = Class.create(Clock, { format: quot;#{hour}:#{minute}:#{second} #{ampm}quot;, getTime: function($super) { var time = $super(); time.ampm = time.hour < 12 ? quot;amquot; : quot;pmquot;; if (time.hour == 0) { time.hour = 12; } else if (time.hour > 12) { time.hour -= 12; } return time; } }); $(document.body).insert(new AmPmClock());
  10. // Or monkeypatch it Object.extend(Clock.prototype, { format: quot;#{hour}:#{minute}:#{second} #{ampm}quot;, getTime: Clock.prototype.getTime.wrap(function(original) { var time = original(); time.ampm = time.hour < 12 ? quot;amquot; : quot;pmquot;; if (time.hour == 0) { time.hour = 12; } else if (time.hour > 12) { time.hour -= 12; } return time; } }); $(document.body).insert(new Clock());
  11. Secrets of Cross-Browser Code
  12. Browser Sniffing (wait, hear me out)
  13. Conditionally evil
  14. In order of desirability:
  15. capabilities & quirks
  16. Capabilities are easy to sniff
  17. Quirks are... more troublesome
  18. Object detection (for capabilities) if (document.evaluate) { // ... }
  19. Distill to a boolean (for stuff in the gray area) var thinksCommentsAreElements = false; if ( document.createElement('!') ) { thinksCommentsAreElements = true; }
  20. ...then sniff (for outright bugs/quirks) if (Prototype.Browser.IE) { element.style.filter = quot;alpha(opacity=50);quot;; }
  21. Try to do the right thing, but don’t stand on principle
  22. The social contract Good faith from browser makers ➝ good faith from web authors
  23. Secrets of Quality Assurance
  24. 1,500 750 0 1.5.0 1.5.1 1.6.0.2
  25. Debugging ✦ Localize & Reproduce ✦ Find the smallest possible code that generates the problem ✦ Easy test-case generation ✦ ./gen.sh 1245 ajax ./gen.sh 1246 dom ✦ Simple logging, all browsers: document.getElementById(“output”) .innerHTML += “<br>” + msg;
  26. Secrets of Events
  27. Event handlers are tricky
  28. Don’t store them on the element itself circular references = sadness
  29. Build a global hashtable (like jQuery does it)
  30. Element Data Store ✦ Each element gets a unique ID (bound w/ a unique property) elem.jQuery12345 = 1; ✦ Points back to large data structure: data[1] = { ... all element data here ... }; ✦ Data (like event handlers) are stored here data[1] = { handlers: { click: [ function(){...} ], ... } };
  31. Then clean up on page unload So that IE doesn’t keep them in memory in perpetuum
  32. Fixing memory leaks
  33. Internet Explorer 6 red-headed stepchild
  34. Don’t “prove” your code has no leaks (view the results!)
  35. Drip is awesome
  36. Test page: Create a bunch of elements, assign each an event handler, then remove each one from the page
  37. Demonstrating the Leak Notice the stair-step effect
  38. Plugging the leak // In Prototype, will remove all event listeners from an element Event.stopObserving(someElement); Event.purgeObservers = function(element, includeParent) { Element.select(element, quot;*quot;).each(Event.stopObserving); if ( includeParent ) Event.stopObserving( element ); };
  39. Redefining functions add “before advice” to functions that need to remove elements
  40. Code sample Element.Methods.update = Element.Methods.update.wrap( function(proceed, element, contents) { Event.purgeObservers(element); return proceed(element, contents); } ); Element.Methods.replace = Element.Methods.replace.wrap( function(proceed, element, contents) { Event.purgeObservers(element, true); return proceed(element, contents); } ); Element.Methods.remove = Element.Methods.remove.wrap( function(proceed, element) { Event.purgeObservers(element, true); return proceed(element); } ); Element.addMethods();
  41. Drop-in fix for Prototype 1.6
  42. Custom Events • Everything is event based if you squint • DOM is a good foundation • Terrible for stitching together non-DOM components and code • Composition == good, inheritance == bad • Custom events let us join loosely • When to use them? Pitfalls?
  43. Custom Events (contd.) // in Dojo: dojo.subscribe(“/foo”, function(e, arg){ ... }); dojo.publish(“/foo”, [{ data: “thinger”}, “second arg”]); // in Prototype: document.observe(“event:foo”, function(e){ ... }); $(“nodeId”).fire(“event:foo”, { data: “thinger” }); // in jQuery: $(document).bind(“foo”, function(e, data, arg){ ... }); $(document).trigger(“foo”, [{ data: “thinger”}, “second”]);
  44. Secrets of DOM Traversal
  45. Selector Internals • Optimized DOM • Top-down vs. bottom-up • Caching + winnowing • XPath • Native, aka: querySelectorAll()
  46. Selector Internals • Tradeoffs: size vs. speed vs. complexity • Prototype: XPath when possible, else DOM • Good perf, lower complexity, hackable • Dojo: all 3 methods, picks best available • Best perf, biggest, highest complexity • JQuery: DOM • Good perf, small size, extensiblity vs. forward-compat tradeoff
  47. Secrets of Style
  48. Computed Style ✦ IE way vs. Everyone else ✦ IE returns “actual value” ✦ Everyone else returns “pixel value” ✦ font-size: 2em; IE: “2em” Other: 24 ✦ Need to convert to common base ✦ Performance: Very costly, generally avoided wherever possible
  49. Pixel Values in IE ✦ if ( !/^d+(px)?$/i.test( ret ) && /^d/.test( ret ) ) { // Remember the original values var style = elem.style.left, runtimeStyle = elem.runtimeStyle.left; // Put in the new values to get a computed value out elem.runtimeStyle.left = elem.currentStyle.left; elem.style.left = ret || 0; ret = elem.style.pixelLeft + “px”; // Revert the changed values elem.style.left = style; elem.runtimeStyle.left = runtimeStyle; } ✦ From Dean Edwards: http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
  50. Computed Style ✦ Safari 2 & 3: Giant mess for display: none elements ✦ Safari 2 ✦ getComputedStyle() returns undefined ✦ Safari 3 ✦ Always return empty strings for value ✦ Much harder to detect var ret = document.defaultView.getComputedStyle( elem, null ); return !ret || ret.getPropertyValue(”color”) == “”;
  51. Finding dimensions
  52. Dimensions of what? content box, border box, margin box...
  53. DHTML properties clientWidth, offsetWidth
  54. The value you want is not captured by any property Lorem ipsum dolor sit amet. width clientWidth offsetWidth
  55. Computed styles getting padding & border value
  56. The fool-proof, painful way Take the offsetWidth, then subtract computed padding & border
  57. Code example Element.getCSSWidth = function(element) { element = $(element); return element.offsetWidth - parseFloat(element.getStyle(quot;borderLeftquot;)) - parseFloat(element.getStyle(quot;paddingLeftquot;)) - parseFloat(element.getStyle(quot;paddingRightquot;)) - parseFloat(element.getStyle(quot;borderRightquot;)); };
  58. Secrets of Animation
  59. Old-School “Effects”
  60. setInterval
  61. Events-based
  62. Secrets ofJavaScript Deployment
  63. CONTENT EXPIRATION
  64. Concatenation
  65. GZIP 4-5x smaller
  66. Packaging • Dev vs. deployment constraints • No library a single file, but all ship that way • # of requests largest constraint • Sync vs. async • Static resource servers + CDNs • Dependency management matters! • Runtime vs. deployment
  67. Packaging // in Dojo: dojo.provide(“foo.bar.Baz”); dojo.require(“dojox.dtl”); // in GWT: package com.foo.bar; import com.foo.bar.Blah; // in JSAN: JSAN.use(“foo.bar.Blah”); // exports handled by build tools
  68. Packaging • The build-process divide • Library support vs. server concat/shrink • Can we strip “dead” code? • Social artifacts of packaging systems
  69. HTML Insertion ✦ $(“div”).append(“<b>foo</b>”); ✦ Convert HTML String ✦ innerHTML ✦ Range: .createContextualFragment() ✦ Must purify first: ✦ Fix <table>s for IE (<tbody> wrap) ✦ Handle <option>s (contain in <select>)
  70. HTML Insertion ✦ // Fix “XHTML ”-style tags in all browsers elem = elem.replace(/(<(w+)[^>]*?)/>/g, function(all, front, tag){ return tag.match(/^(abbr|br|col|img|input|link|meta|param| hr|area|embed)$/i) ? all : front + “></” + tag + “>”; }); ✦ $(“<abbr/>”).html(“hello!”).appendTo(“#foo”);
  71. Script Execution ✦ Execute Script in Global Scope ✦ var head = document.getElementsByTagName(”head”)[0] || document.documentElement, script = document.createElement(”script”); script.type = “text/javascript”; if ( jQuery.browser.msie ) script.text = data; else script.appendChild( document.createTextNode( data ) ); head.appendChild( script ); head.removeChild( script );
  72. Questions ✦ Panelists: ✦ John Resig (ejohn.org) ✦ Sam Stephenson (conio.net) ✦ Alex Russell (alex.dojotoolkit.org) ✦ Thomas Fuchs (script.aculo.us/thomas) ✦ Andrew Dupont (andrewdupont.net) ✦ Frameworks: ✦ Prototype (prototypejs.org) ✦ jQuery (jquery.com) ✦ Dojo (dojotoolkit.org) ✦ Script.aculo.us (script.aculo.us)

×