Advertisement

Secrets of JavaScript Libraries

Mar. 12, 2008
Advertisement

More Related Content

Advertisement
Advertisement

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)
Advertisement