8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
1/78
Secrets of
JavaScript Libraries(Left to Right)Sam Stephenson (Prototype)
Alex Russell (Dojo)
Thomas Fuchs (Script.aculo.us)Andrew Dupont (Prototype)
John Resig(jQuery)
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
2/78
What to Cover Topics: JavaScript Language Cross-Browser Code
Events DOM Traversal Style Animations
Distribution HTML Insertion
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
3/78
Secrets of theJavaScript Language
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
4/78
// Set up a class and create an element
var Clock = Class.create({
initialize: function() {
this.createElement();
},
createElement: function() {
this.element =new Element("div");
}
});
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
5/78
// 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);
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
6/78
// 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));
}});
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
7/78
// 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)
}
}, ...
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
8/78
// Use #toElement
var Clock = Class.create({
...
toElement: function() { returnthis.element;
}
});
$(document.body).insert(new Clock());$(document.body).down("div.clock > div").replace(new Clock());
$(document.body).down("div.clock").update(new Clock());
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
9/78
// Subclass it
var AmPmClock = Class.create(Clock, {
format: "#{hour}:#{minute}:#{second} #{ampm}",
getTime: function($super) { var time =$super();
time.ampm = time.hour 12) {
time.hour -=12;
}
return time;
}});
$(document.body).insert(new AmPmClock());
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
10/78
// 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) {
time.hour -=12;
}
return time;
}});
$(document.body).insert(new Clock());
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
11/78
Secrets ofCross-Browser Code
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
12/78
Browser Sniffing(wait, hear me out)
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
13/78
Conditionally evil
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
14/78
In order of desirability:
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
15/78
capabilities&
quirks
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
16/78
Capabilitiesare easy to sniff
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
17/78
Quirksare... more troublesome
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
18/78
if(document.evaluate) { // ...}
Object detection(for capabilities)
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
19/78
Distill to a booleanvar thinksCommentsAreElements = false;if( document.createElement('!') ) {thinksCommentsAreElements = true;
}
(for stuff in the gray area)
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
20/78
...then sniff(for outright bugs/quirks)if(Prototype.Browser.IE) {element.style.filter =
"alpha(opacity=50);";}
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
21/78
Try to do the right thing,but dont stand on principle
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
22/78
The social contractGood faith from browser makersgood faith from web authors
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
23/78
Secretsof Quality
Assurance
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
24/78
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
25/78
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
26/78
0
750
1,500
1.5.01.5.1
1.6.0.2
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
27/78
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
28/78
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 +=
+ msg;
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
29/78
Secrets ofEvents
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
30/78
Event handlersare tricky
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
31/78
Dont store them on the
element itselfcircular references = sadness
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
32/78
Build a global hashtable(like jQuery does it)
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
33/78
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(){...}], ... }
};
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
34/78
Then clean up
on page unloadSo that IE doesnt keep themin memory in perpetuum
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
35/78
Fixing memory leaks
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
36/78
Internet Explorer 6red-headed stepchild
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
37/78
Dont prove your code
has no leaks(view the results!)
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
38/78
Dripis awesome
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
39/78
Test page:Create a bunch of elements, assign each an eventhandler, then remove each one from the page
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
40/78
Demonstrating the Leak
Notice the stair-step effect
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
41/78
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 );
};
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
42/78
Redefining functionsadd before advice to functionsthat need to remove elements
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
43/78
Code sam leElement.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();
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
44/78
Drop-in fix
for Prototype 1.6
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
45/78
Custom Events
Everything is event based if you squint
DOM is a good foundation
Terrible for stitching together non-DOMcomponents and code
Composition == good, inheritance == bad Custom events let us join loosely
When to use them? Pitfalls?
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
46/78
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]);
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
47/78
Secrets ofDOM Traversal
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
48/78
Selector Internals
Optimized DOM
Top-down vs. bottom-up Caching + winnowing
XPath
Native, aka: querySelectorAll()
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
49/78
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
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
50/78
Secrets ofStyle
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
51/78
Computed Style IE way vs. Everyone else IE returns actual value Everyone else returns pixel value font-size: 2em;
IE: 2emOther: 24
Need to convert to common base
Performance: Very costly, generally avoidedwherever possible
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
52/78
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
http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-1022918/8/2019 Secrets of Javascript Libraries 1205311956392030 5
53/78
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) == ;
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
54/78
Finding dimensions
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
55/78
Dimensions of what?content box, border box, margin box...
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
56/78
DHTML propertiesclientWidth, offsetWidth
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
57/78
The value you want is not captured by any property
Lorem ipsum dolor sit
amet.
width
clientWidth
offsetWidth
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
58/78
Computed stylesgetting padding & border value
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
59/78
The fool-proof,
painful wayTake the offsetWidth,then subtract computed padding & border
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
60/78
Code exampleElement.getCSSWidth = function(element) { element = $(element); returnelement.ofsetWidth -
parseFloat(element.getStyle("borderLeft")) -
parseFloat(element.getStyle("paddingLeft")) - parseFloat(element.getStyle("paddingRight")) - parseFloat(element.getStyle("borderRight"));};
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
61/78
Secretsof Animation
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
62/78
Old-School
Effects
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
63/78
setInterval
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
64/78
Events-based
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
65/78
SecretsofJavaScript
Deployment
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
66/78
CONTENT
EXPIRATION
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
67/78
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
68/78
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
69/78
Concatenation
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
70/78
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
71/78
GZIP4-5x smaller
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
72/78
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
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
73/78
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
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
74/78
Packaging
The build-process divide
Library support vs. server concat/shrink
Can we strip dead code?
Social artifacts of packaging systems
HTML I i
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
75/78
HTML Insertion $(div).append(foo); Convert HTML String innerHTML
Range: .createContextualFragment() Must purifyfirst: Fix s for IE ( wrap)
Handle s (contain in )
HTML I i
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
76/78
HTML Insertion
// FixXHTML-style tags in all browserselem = elem.replace(/(]*?)\/>/g, function(all, front, tag){ return tag.match(/ (abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ? all : front + >;});
$().html(hello!).appendTo(#foo);
S i t E ti
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
77/78
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 );
8/8/2019 Secrets of Javascript Libraries 1205311956392030 5
78/78