secrets of javascript libraries

Post on 05-Dec-2014

44.705 Views

Category:

Technology

1 Downloads

Preview:

Click to see full reader

DESCRIPTION

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

TRANSCRIPT

Secrets ofJavaScript Libraries

(Left to Right)Sam Stephenson (Prototype)

Alex Russell (Dojo)Thomas Fuchs (Script.aculo.us)

Andrew Dupont (Prototype)John Resig (jQuery)

What to Cover✦ Topics:

✦ JavaScript Language✦ Cross-Browser Code✦ Events✦ DOM Traversal✦ Style✦ Animations✦ Distribution✦ HTML Insertion

Secrets of theJavaScript Language

// Set up a class and create an element

var Clock = Class.create({ initialize: function() { this.createElement(); }, createElement: function() { this.element = new Element("div"); }});

// Display the time

var Clock = Class.create({ initialize: function() { this.createElement(); }, createElement: function() { this.element = new Element("div");

var date = new Date(); this.element.update( date.getHours() + ":" + date.getMinutes().toPaddedString(2) + "." + date.getSeconds().toPaddedString(2) ); }});

$(document.body).insert(new Clock().element);

// Add the timer

var Clock = Class.create({ initialize: function() { this.createElement(); this.createTimer(); }, createElement: function() { this.element = new Element("div"); }, updateElement: function() { var date = new Date(); this.element.update( date.getHours() + ":" + date.getMinutes().toPaddedString(2) + "." + date.getSeconds().toPaddedString(2) ); }, createTimer: function() { window.setInterval(500, this.updateElement.bind(this)); }});

// Add some options

var Clock = Class.create({ color: "black", format: "#{hour}:#{minute}.#{second}", initialize: function(options) { Object.extend(this, options); this.createElement(); this.createTimer(); }, createElement: function() { this.element = new Element("div"); this.element.setStyle({ color: this.color }); }, 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) } }, ...

$(document.body).insert(new Clock().element);$(document.body).insert(new Clock({ color: "red" }).element);$(document.body).insert(new Clock({ format: "#{hour}:#{minute}" }).element);

// Use #toElement

var Clock = Class.create({ ...

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

$(document.body).insert(new Clock());$(document.body).down("div.clock > div").replace(new Clock());$(document.body).down("div.clock").update(new Clock());

// Subclass it

var AmPmClock = Class.create(Clock, { format: "#{hour}:#{minute}:#{second} #{ampm}", getTime: function($super) { var time = $super(); time.ampm = time.hour < 12 ? "am" : "pm"; if (time.hour == 0) { time.hour = 12; } else if (time.hour > 12) { time.hour -= 12; } return time; }});

$(document.body).insert(new AmPmClock());

// Or monkeypatch it

Object.extend(Clock.prototype, { format: "#{hour}:#{minute}:#{second} #{ampm}", getTime: Clock.prototype.getTime.wrap(function(original) { var time = original(); time.ampm = time.hour < 12 ? "am" : "pm"; if (time.hour == 0) { time.hour = 12; } else if (time.hour > 12) { time.hour -= 12; } return time; }});

$(document.body).insert(new Clock());

Secrets ofCross-Browser Code

Browser Sniffing(wait, hear me out)

Conditionally evil

In order of desirability:

capabilities&

quirks

Capabilitiesare easy to sniff

Quirksare... more troublesome

if (document.evaluate) { // ...}

Object detection(for capabilities)

Distill to a booleanvar thinksCommentsAreElements = false;if ( document.createElement('!') ) { thinksCommentsAreElements = true;}

(for stuff in the gray area)

...then sniff(for outright bugs/quirks)

if (Prototype.Browser.IE) { element.style.filter = "alpha(opacity=50);";}

Try to do the right thing,but don’t stand on principle

The social contractGood faith from browser makers ➝

good faith from web authors

Secrets of QualityAssurance

0

750

1,500

1.5.01.5.1

1.6.0.2

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;

Secrets ofEvents

Event handlersare tricky

Don’t store them on the element itselfcircular 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 back to large data structure:data[1] = { ... all element data here ... };

✦ Data (like event handlers) are stored heredata[1] = { handlers: { click: [ function(){...} ], ... }};

Then clean up on page unload

So that IE doesn’t keep themin memory in perpetuum

Fixing memory leaks

Internet Explorer 6red-headed stepchild

Don’t “prove” your code has no leaks

(view the results!)

Dripis awesome

Test page:Create a bunch of elements, assign each an event

handler, then remove each one from the page

Demonstrating the LeakNotice the stair-step effect

Plugging the leak// In Prototype, will remove all event listeners from an elementEvent.stopObserving(someElement);

Event.purgeObservers = function(element, includeParent) { Element.select(element, "*").each(Event.stopObserving); if ( includeParent ) Event.stopObserving( element );};

Redefining functionsadd “before advice” to functionsthat need to remove elements

Code sampleElement.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();

Drop-in fixfor Prototype 1.6

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?

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”]);

Secrets ofDOM Traversal

Selector Internals

• Optimized DOM

• Top-down vs. bottom-up

• Caching + winnowing

• XPath

• Native, aka: querySelectorAll()

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

Secrets ofStyle

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

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

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”) == “”;

Finding dimensions

Dimensions of what?content box, border box, margin box...

DHTML propertiesclientWidth, offsetWidth

The value you want is not captured by any property

Lorem ipsum dolor sit

amet.

width

clientWidth

offsetWidth

Computed stylesgetting padding & border value

The fool-proof, painful way

Take the offsetWidth,then subtract computed padding & border

Code exampleElement.getCSSWidth = function(element) { element = $(element); return element.offsetWidth - parseFloat(element.getStyle("borderLeft")) - parseFloat(element.getStyle("paddingLeft")) - parseFloat(element.getStyle("paddingRight")) - parseFloat(element.getStyle("borderRight")); };

Secrets of Animation

Old-School“Effects”

setInterval

Events-based

Secrets ofJavaScript Deployment

CONTENTEXPIRATION

Concatenation

GZIP4-5x smaller

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

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

Packaging

• The build-process divide

• Library support vs. server concat/shrink

• Can we strip “dead” code?

• Social artifacts of packaging systems

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

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”);

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

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)

top related