JavaScript Idioms and Gotchas
JavaScript Function Objects
In ECMAScript, functions can be defined by a “FunctionDeclaration” or a “FunctionExpression”. Basically, the difference is that a FunctionDeclaration includes a function name so that it can be called from other functions – it is defined as:
function Identifier ( FormalParameterListopt ){ FunctionBody }
while a FunctionExpression is the anonymous function – note in the definition that the “Identifier” part is marked as optional:
function Identifieropt ( FormalParameterListopt ){ FunctionBody }
However, including an identifier is still allowed. The identifier can be used inside the function to refer to itself, but not outside it.
This may confuse authors if we forget that whether something is a FunctionDeclaration or a FunctionExpression depends on the context. The right hand side of an assignment or the arguments of a function call will be a FunctionExpression, in other words behave like an anonymous function. For example
var a=function b(){return ‘Hi’}; alert(b());
fails: there is no function named b there. alert(a()) would work. Likewise,
setTimeout( function a(){}, 50 ); a();
fails because the argument is evaluated as a FunctionExpression and after the setTimeout() call no function named “a” exists. (IE seems to violate/extend the standard here!)
It can be confusing that a FunctionExpression is allowed to look exactly like a FunctionDeclaration but behaves differently, and I don’t really see a strong use case since arguments.callee exists.
Because functions in JavaScript are actually objects, descended from the Function object, when we nest one function inside of another, the scope of “this” gets reassigned to the nested function’s parent. “this” will always refer to its immediate parent unless you create a symbol such as var that = this in the original function which is referenced by the nested functions inside the closure scope or passed as a context object in the arguments of a function call.
The rules for binding ‘this” depend on whether a function is invoked by construction, by method call, by function call, or by reflection. If a function written to be called in one way is instead called in another way, its “this” might be re-bound to a different object or even to the global scope.
In order to access the methods inside of another namespace inside a self-invoking function or a parent prototype inside a child object, create a reference to its prototype as a context object, such as var that = pooter.prototype; as a local variable inside the scope of the self-invoking function or child object.
In prototypal inheritence, each object points to the prototype instance that it is constructed from. When you read a property, it travels up the prototype chain looking for it unless you use hasOwnProperty. When you write a property, JavaScript only adds it to the current context object. When you then update a constructor property, JavaScript modifies the prototype value which affects all instances.
In order to retain the default property values within a prototype object regardless of updates, call the prototype constructor within the object constructor like this:
function Ninja()
{
this.swung = true;
}
function shinobi(weapon)
{
Ninja.call(this);
this.throw = weapon;
}
var shinobi = new Ninja();
var player = new shinobi("stars");
player.swung = false;
Traverse the prototype chain, get each prototype and compare it to the prototype property of the constructor parameter to access the original prototype property of the object’s constructor function when overloading properties
function instanceOf(object, constructor)
{
while (object != null)
{
if (object == constructor.prototype)
return true;
object = Object.getPrototypeOf(object);
}
return false;
}
Functions and variables declared as properties of a functions prototype by using the function’s name or “this” are available to each instance of the function.
//public member variable
MyClass.prototype.publicVar = “My Public Variable”;
Functions and variables declared as a local variable within the scope of a function are private to that function. Private functions can reference public functions within that scope by using myfunc.prototype.publicVar, window.myclass.publicVar, or setting a local variable equal to “this”
Functions availabe to each instance of the constructor but have access to private variables are priveleged members of their parent function
A static function or variable is available on the base class (or JavaScript) function, but is not available to the class instance.
function my_func(){}
//declare a static member
my_func.staticVar = "My static variable";
//declare a static function
my_func.s_func = function(input)
{
return new my_func(my_func.s_func, input);
}
//create an instance
var f_inst = new my_func();
//run a static function (NO access to private or public)
f_inst.s_func(9); //staticFunction is undefined on an instance
You can refer to a function by its name or by the variable it is assigned to within the function’s scope
var ninja = function myNinja()
{
assert( ninja == myNinja, "This function is named two things - at once!" );
};
A function which is an object property can reference the context object of its parent object to create or modify the value of other properties within that object
function katana()
{
this.isSharp = true;
}
katana();
assert( isSharp === true, "A global object now exists with that name and value." );
var shuriken =
{
toss: function(){
this.isSharp = true;
}
};
shuriken.toss();
assert( shuriken.isSharp === true, "When it's an object property, the value is set within the object." );
You can reference a function which is the property of an object literal and use it as the property of another object if the function’s name matches the name of the property, but not if it’s an anonymous function
var dude =
{
party: function party(num)
{
return num + 1;
}
};
var chick =
{
party: dude.party
};
Use arguments.callee to call the function itself within its scope instead of using its name, which is not defined for an anonymous function.
There are three subtle differences between function literals and standard function statements:
- When assigned as a method, a function literal does not leave an extraneous function reference defined in the scope of the function declaration.
- A standard function declaration does not end in a semicolon, whereas a function literal does.
- Functions created with function statements are available throughout an entire script, whereas function literals are available only to statements that come after the literal in the script.
Add a self-invoking function to give closure scope to loop variables used by timers:
var count = 0;
for ( var i = 0; i < 4; i++ )
(function(i)
{
setTimeout(function() { assert( i == count++, "Check the value of i." ); }, i * 200);
}
)(i);
An anonymous function can wrap a library within its own namespace in several ways:
(function()
{
var myLib = window.myLib = function(){
...
};
}
)();
var myLib = (function()
{
function myLib()
{
...
}
return myLib;
}
)();
A function’s length property show’s the number of arguments expected based on the arguments in parentheses of the function declaration
A function’s constructor property show’s the prototype the function inherits its properties from.
A function with properties can pass them to another function by using the new operator to create an instance based on the original constructor
Use a check of the “this” context object with the instanceOf function to make sure the new operator is always used:
if ( !(this instanceof arguments.callee) )
return new arguments.callee(arguments);
Referencing function properties without using the new operator will mistakenly create or overwrite window properties if you use the “this” context object instead of the function name to define your properties or methods
The constructor method of an object returns a reference to the function that created the prototype for each instance
typeof is an operator that works the same as “return (x)”; the unary expression does not need parentheses like a function call, so doing the check without parentheses is fine:
typeof x == “undefined”
The length property of a function object returns the expected number of arguments required by the function based on the function definition. Function overloading can be done by checking this property and applying a different callback function depending on the number of arguments:
function addMethod(context_object, function_name, replacement_function)
{
// Save a reference to the old method
var old = context_object[function_name];
// Overwrite the method with our new one
context_object[function_name] = function(){
// Check the number of incoming arguments, compared to our overloaded function
if (replacement_function.length == arguments.length)
{
// If there was a match, run the function
return replacement_function.apply(this, arguments);
}
// Otherwise, fallback to the old method
else if (typeof old === "function")
{
return old.apply(this, arguments);
}
};
}
The apply method of a function object allows it to invoke itself on a specific “this” context object. It takes two arguments: the context object, and an array of the arguments to be passed to the function. The call method of a function object performs the same action as apply, but it takes the arguments themselves, not an array of them.
var object = {};
function fn()
{
return this;
}
assert( fn() == this, "The context is the global object." );
assert( fn.call(object) == object, "The context is changed to a specific object." );
You can generalize the context passing function by applying a method to a given object by writing a wrapper function, such as this:
// Pass in the desired method and its "this" context into the wrapper function
function wrap_context_object(newthis, method)
{
// Return a new anonymous function
return function(){
// Take the desired method and invoke apply on it, passing it the context object
// and the arguments array passed during the actual call
// The first parameter of the apply function is the newthis, and the caller of the apply function is the oldthis
return oldthis.method.apply(newthis, arguments);
};
}
Assuming “this” as the general context object, you can use a bind function to properly assign events to a function method that does not support bubbling or capturing, such as onload or onunload:
Function.prototype.bind = function(){
var fn = this;
var args = [].prototype.slice.call(arguments);
var object = args.shift();
return function()
{
return fn.apply( object, args.concat( Array.prototype.slice.call( arguments ) ) );
};
};
var Button =
{
load: function()
{
this.load = true;
}
};
The problem with bind() is that it uses apply() internally, which is significantly slower than just executing the function inline as an argument. This makes a big difference for event handlers. Fortunately this is easy to avoid, and there are a number of ways such as the following:
function addMouseEvents()
{
var that = this;
this.xaml.addEventListener('MouseEnter',
function(sender, args)
{
that.onMouseEvent(sender, args);
}
);
}
Preserving Scope While Passing By Reference
function car()
{
this.color = "blue";
this.show_color = function ()
{
alert(this.color);
};
}
/*
You would expect that calling the setTimeout function would create a popup box displaying "blue" after 1 second; however, the popup box reads 'undefined'. This is because the scope that the function funct operates in changes to the window object when passed to setTimeout; thus, this references the window object
*/
var mycar = new car();
setTimeout(mycar.show_color,1000);
function objRef(obj, func)
{
// Create an array of any extra arguments passed
args = [];
var i=2;
while (i < arguments.length)
{
args.push(arguments[i]);
i++;
}
var sub = function()
{
func.apply(obj,args);
};
return sub;
}
/*
The function objRef has two required parameters, obj and func.
func is the function you wish to call.
obj is the object that you want the function to be applied to.
any instances of this in func will refer to obj.
Any additional arguments provided to objRef will be passed
to func in the same order they are provided.
To call func, call the returned function sub with no arguments.
To emulate the intended behavior of the code above, we would do the following:
*/
var mycar = new car();
var ref_func = objRef(mycar, mycar.show_color);
setTimeout(ref_func,1000);
Closures means that if a nested function references the local variables of a parent function, those variables are kept in memory after the parent function has returned and can be directly access until the nested function has returned as well.
It is better to branch conditional statements when the function is defined as opposed to every time it’s called. For example:
// Instead of this:
function myEvent(el, type, fn)
{
if (window.addEventListener)
{
el.addEventListener(type, fn, false);
}
else if (window.attachEvent)
{
el.attachEvent("on" + type, fn);
}
}
// Use this pattern:
if (window.addEventListener)
{
var myEvent = function (el, type, fn)
{
el.addEventListener(type, fn, false);
}
}
else if (window.attachEvent)
{
var myEvent = function (el, type, fn)
{
el.attachEvent("on" + type, fn);
}
}
Assign a function to a local variable in order to reference other local variables within the scope of the closure
Use a function as the return value of another function to create one liners that don’t need variable declarations, for situations like this:
function makeFish(color)
{
function fish()
{
return color + " fish";
}
return fish;
}
var fish0 = makeFish('red');
var fish1 = makeFish('blue');
Use Lazy Function definition to memoize return values:
var getPi = function ()
{
var pi = calculatePi();
getPi = function ()
{
return pi;
}
return pi;
};
Use enhanced Lazy Function definition to memoize and update return values:
var curState = "Washington";
var stateInfo = function ()
{
var reset = stateInfo;
var pState = curState;
var theInfo = "The state is " + curState + ".";
var showInfo = function ()
{
return (pState == curState) ? theInfo : reset();
};
stateInfo = showInfo;
return stateInfo();
};
There are three ways to add functions to a script:
-
Extending JavaScript’s Native String Object
String.prototype.trim = function() { return this.replace(/^s+|s+$/g, ""); }; -
Using a Function Statement
function trim(str) { return str.replace(/^s+|s+$/g, ""); } </li> <li> Using a Function Literal [sourcecode language="js"] sweatte = {}; sweatte.trim = function(str) { return str.replace(/^s+|s+$/g, ""); };
Use a backreference as an argument to create a callback function to replace a regexp pattern
var myObj = function ()
{
this.complex = function (text) { /* long piece of code */ }
this.parse(text)
{
var that = this;
return text.replace(/valid_pattern/gi, function ($1) { return that.complex($1); } );
}
}
Use a callback function as an argument to replace a regexp pattern of CSS styles with their camel-cased DOM equivalents
this.copyStyles = function(element, cssText)
{
String.prototype.trim = function(){
return this.replace(/^\s+|\s+$/g, '');
}
String.prototype.mapStyle = function()
{
return this.replace(/\-(\w)/g, function(m, c){
return c.toUpperCase();
}
);
}
var styles = cssText.split(";");
for (var i = 0; i < styles.length; i++)
{
var style = styles[i];
if (style != "" && (j = style.indexOf(":") ) > 0)
{
element.style[style.substr(0, j).mapStyle()] = style.substr(j + 1).trim();
}
}
};
Change functions from prototype methods to properties of the global object by removing the prototype keyword and replacing all references to the “this” context object with some object passed as an argument:
Array.foldr = function(fnc,start,arr)
{
var a = start;
for (var i = arr.length-1; i > -1; i--)
{
a = fnc(arr[i],a);
}
return a;
}
Array.prototype.foldr = function(fnc,start)
{
var a = start;
for (var i = this.length-1; i > -1; i--)
{
a = fnc(this[i], a);
}
return a;
}
Javascript Variable Scope
When using a closure inside a for loop, the scope captures the final iteration value + 1. To avoid this, define the function outside the scope of the loop
for ( var d = 0; d < 3; d++ )
{
setTimeout(function(){
assert( d == 3, "Check the value of d." );
}, 100);
}
Timer functions can reference variables in the local scope through the use of closures, as in the following code:
function Time4Change()
{
var spin = 45;
var animate = function()
{
//Due to the closure, spin will be defined
document.getElementById('pizza').style.left = spin + "px";
};
//Call function in local scope
setTimeout(animate, 200);
}
Here is a simple example of a closure to demonstrate an alternative to creating local variables by passing functions as values and paramaters as closure arguments:
function createAdder(x)
{
// Return an anonymous function which saves the original parameter using the closure scope
// The variable assigned to this function becomes the named function of the subsequent call
return function(y)
{
// Use the closure argument and the argument from the variable call to return the final value
return y + x;
}
}
addThree = createAdder(3);
addFour = createAdder(4);
document.write('10 + 3 is ' + addThree(10) + '');
document.write('10 + 4 is ' + addFour(10));
Use closure scope the reference parameters within timers
var count = 0;
var timer = setInterval(function(){
if ( count < 5 )
{
log( "Timer call: ", count );
count++;
}
else
{
assert( count == 5, "Count came via a closure, accessed each step." );
assert( timer, "The timer reference is also via a closure." );
clearInterval(timer);
}
}, 100);
Properties added in the function constructor overwrite properties defined in the function prototype
function Ninja()
{
this.swingSword = function(){
return true;
};
}
// Should return false, but will be overridden
Ninja.prototype.swingSword = function(){
return false;
};
var ninja = new Ninja();
assert( ninja.swingSword(), "Calling the instance method, not the prototype method." );
Properties defined in the function prototype are available to all instances of the same function constructor, no matter where they are defined in the source code
function Ninja()
{
this.swung = true;
}
var ninjaA = new Ninja();
var ninjaB = new Ninja();
Ninja.prototype.swingSword = function(){
return this.swung;
};
assert( ninjaA.swingSword(), "Method exists, even out of order." );
assert( ninjaB.swingSword(), "and on all instantiated objects." );
Return the “this” context object when you need to add a method to a function prototype which modifies properties defined in the function constructor and returns itself for use in function chaining
function Ninja()
{
this.swung = true;
}
var ninjaA = new Ninja();
var ninjaB = new Ninja();
Ninja.prototype.swing = function(){
this.swung = false;
return this;
};
assert( !ninjaA.swing().swung, "Verify that the swing method exists and returns an instance." );
assert( !ninjaB.swing().swung, "and that it works on all Ninja instances." );
Functions which reference properties defined in an object can be called by that object, regardless of whether the function inherits from that object’s prototype. As long as the property name referenced by the function is a match, you can use the function to implement mixins. All of the following are equivalent:
foo.getBrand();
bar.apply(foo);
foo.getBrand.apply(foo);
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.
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;}
}
JavaScript DOM Objects
IE and older versions of Opera return elements with a name property equal to the ID passed to getElementByID, which does not work in XML documents
To get the first child of the root element in IE, use xmldoc.firstChild.firstChild;
Use conditional statements to check the existence of a DOM node before referencing its properties
If you conditionally change the style properties of a DOM node, they may need to be reset in an else statement if the node is reused instead of created and destroyed
If you hide a DOM node instead of removing it from the DOM, the childNodes length value will not change so you may need to find other ways to do status checks
Use document.createElement(tagName) to create each of the new HTML5 elements such as section, article, nav, etc. so that CSS styling can be applied to them. They are not recognized in IE since it fails to parse start and end tags for unknown empty elements.
getElementsByTagName returns no elements for object tags in IE7, and the length property gets overwritten in IE if an element with an ID in the document uses the string literal “length”
HTMLElement.prototype.getElementsByClassName cannot be overwritten in Firefox
Opera doesn’t match a more than one class name at a time when using its native getElementsByClassName method, so strictly searching for an elements that use multiple class names does not work
querySelectorAll isn’t defined natively in IE8 when using quirks mode, so you must use strict mode
Safari 3.2 can’t match uppercase characters when using querySelectorAll in quirks mode
querySelectorAll doesn’t match any element IDs within an XML document
Remove all of the childNodes from a DOM element with a while loop, and not a for loop. For example, this is bad since as soon as you remove one child, the length of node.childNodes is 1 smaller, and eventually will return an index that does not exist:
function removeChildrenFromNode(node)
{
if(node !== undefined && node !=== null)
{
return;
}
var len = node.childNodes.length;
for(var i = 0; i < len; i++)
{
node.removeChild(node.childNodes[i]);
}
}
//The correct way to do this is with a while loop with node.hasChildNodes
function removeChildrenFromNode(node)
{
if(node !== undefined && node !=== null)
{
return;
}
var len = node.childNodes.length;
while (node.hasChildNodes())
{
node.removeChild(node.firstChild);
}
}
Test Greedy IDs when doing cross-browser DOM compatibility checking, such as:
- A form with an ID of “form”
- An input text field with an ID of “length”
- A submit input with an ID of “submit”
Use strict code namespacing, and don’t extend outside objects or elements
Fallback Detection means checking for Event Binding and W3C Document Object methods
if ( typeof document !== "undefined" && (document.addEventListener || document.attachEvent) && document.getElementsByTagName && document.getElementById )
{
// We have enough of an API to // work with to build our application
}
else
{
// Provide CSS or HTML only Fallback without any JavaScript or partial API (DOM traversal, but no Events)
}
Don’t make assumptions about browser bugs.
Assuming that a browser will always have a bug is foolhardy
You will become susceptible to fixes that are not forward compatible
// Shouldn't work
var node = documentA.createElement("div");
documentB.documentElement.appendChild(node);
//Proper way
var node = documentA.createElement("div");
documentB.adoptNode(node);
documentB.documentElement.appendChild(node);
Feature Simulation is more advanced than object detection. It makes sure an API works as advertised and captures bug fixes gracefully
// Run once, at the beginning of the program
var ELEMENTS_ONLY = (function(){
var div = document.createElement("div");
div.appendChild(document.createComment(\"test\" ) );
return div.getElementsByTagName("*").length === 0;
}
)();
// ...later in the code:
var all = document.getElementsByTagName("*");
if (ELEMENTS_ONLY)
{
for(var i = 0; i < all.length; i++)
{
action(all[i]);
}
}
else
{
for(var i = 0; i < all.length; i++)
{
if(all[i].nodeType === 1)
{
action(all[i]);
}
}
}
Figure Out Naming
<div id="test" style="color:red;"></div> <div id="test2"></div>
// Perform the initial attribute check
var STYLE_NAME = (function(){
var div = document.createElement("div");
div.style.color = "red";
if (div.getAttribute("style") )
{
return "style";
}
if(div.getAttribute("cssText") )
{
return "cssText";
}
}
)();
//later on:
window.onload = function(){
document.getElementsById("test2").setAttribute(STYLE_NAME, document.getElementById("test").getAttribute(STYLE_NAME) );
};
IE7 introduced XMLHttpRequest support for the file protocol
<strong>Unify Dimensions</strong>
[sourcecode language="js"]
// ignore negative width and height values
if ( (key == "width" || key == "height") && parseFloat(value) < 0 )
{
var value = undefined;
}
Prevent Breakage
if (name == "type" && elem.nodeName.toLowerCase() == "input" && elem.parentNode)
{
throw "type attribute can't be changed";
}
Untestable Problems
Has an event handler been bound?
Will an event fire?
Do CSS properties like color or opacity affect the display?
Problems that cause a browser crash.
Problems that cause an incongruous API.
In FF3, Safari 3, Opera 9.6, the children property only returns elements for getElementByID and getElementsByTagName
Use getElementsByName(“myname”).all[id] to match multiple elements by ID
Top-Down CSS Selector Engine
This is the traditional style of traversal. It is used by all major libraries and works from left-to-right, e.g. “div p” find all divs first, then finds paragraphs inside. This requires a lot of result merging and removal of duplicates.
function find(selector, root)
{
root = root || document;
var parts = selector.split(" ");
var query = parts[0];
var rest = parts.slice(1).join(" ");
var elems = root.getElementsByTagName( query );
var results = [];
for (var i = 0; i < elems.length; i++)
{
if(rest)
{
results = results.concat(find(rest, elems[i]) );
}
else
{
results.push(elems[i]);
}
}
return results;
}
(function()
{
var run = 0;
this.unique = function(array){
var ret = [];
run++;
for(var i = 0, length = array.length; i < length; i++)
{
var elem = array[i];
if(elem.uniqueID !== run)
{
elem.uniqueID = run;
ret.push(array[i]);
}
}
return ret;
};
}
)();
Bottom-Up CSS Selector Engine
This works from right-to-left, and is how CSS works in browsers, e.g. “div p” find all paragraphs, then check whether they have a div ancestor until all elements are searched. This is fast for specific queries, such as “div #foo”, but deep queries, such as “#foo p”, are slow in comparison to top-down selection. Some nice features are:
Only one DOM query
No merge/unique required except for “div, span”
Everything is a process of filtering
function find(selector, root)
{
root = root || document;
var parts = selector.split(" ");
var query = parts[parts.length - 1];
var rest = parts.slice(0,-1).join("").toUpperCase();
var elems = root.getElementsByTagName(query);
var results = [];
for (var i = 0; i < elems.length; i++)
{
if (rest)
{
var parent = elems[i].parentNode;
while(parent && parent.nodeName != rest)
{
parent = parent.parentNode;
}
if (parent)
{
results.push(elems[i]);
}
}
else
{
results.push(elems[i]);
}
}
return results;
}
<strong>CSS to XPath Selector Engine</strong>
Browsers provide XPath functionality to collect elements from a document. This works on HTML documents in all browsers, but it doesn't work on XML documents in IE. It is fast for a number of selectors, such as ".class" or "div div div", but slow for some others like #id. It is currently used by Dojo and Prototype
[sourcecode language="js"]
if (typeof document.evaluate === "function")
{
function getElementsByXPath(expression, parentElement)
{
var results = [];
var query = document.evaluate(expression, parentElement || document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
for (var i = 0, length = query.snapshotLength; i++)
{
/*
//p
// Element By ID #foo: //*[@id='foo']
// Element By Class: [contains(concat(" ", .foo @class, " ")," foo ")]
// Element With Attribute [title]: //*[@title]
// First Child of All P (p < *:first-child): //p/*[0]
// All P with an A descendant(p a): //p[a]
// Next Element (p+*): //p/following-sibling::*[0]
Selectors API
The Selectors API from the W3C has two methods: querySelector (get the first element) and querySelectorAll (get all elements). It works on document, elements, and DocumentFragments nodes. It is implemented in Firefox 3.1, Safari 3, Opera 10, and IE 8. querySelector selects the first element in the DOM that matches the specified selector. For example, the following selects the first p element in a document:
document.querySelector(“p”);
querySelectorAll selects all of the matching elements in the DOM. For example, the following line selects all of the elements in a document with a class of alert:
document.querySelectorAll(“.alert”);
<div id="test"> <b>Hello</b> I'm a ninja! </div> <div id="test2"></div>
window.onload = function(){
var divs = document.querySelectorAll("body > div");
assert( divs.length === 2, "Two divs found using a CSS selector." );
var b = document.getElementById("test").querySelector("b:only-child");
assert( b, "The bold element was found relative to another element." );
};
<div id="test"> <b>Hello</b>, I'm a ninja! </div>
window.onload = function(){
var b = document.getElementById("test").querySelector("div b");
assert( b, "Only the last part of the selector matters." );
};
DOM Modification
Injecting HTML
HTML5: insertAdjacentHTML
Already in IE, dicey support, at best
What can we use instead?
We must generate our own HTML injection
Use innerHTML to generate a DOM
function getNodes(htmlString)
{
var map = {
"<td": [3, "<table><tbody><tr>", "</tr></tbody></table>"],
"<option": [1, "", ""]
// ...a full list of all element fixes
};
var name = htmlString.match(/<\w+/);
var node = name ? map[ name[0] ] || [0, "", ""];
var div = document.createElement("div");
div.innerHTML = node[1] + htmlString + node[2];
while (node[0]--)
{
div = div.lastChild;
}
return div.childNodes;
}
assert(getNodes("<td>test</td><td>test2</td>").length === 2, "Get two nodes back from the method."); assert(getNodes("<td>test</td>").nodeName === "TD", "Verify that we're getting the right node.");
<strong>Element Mappings</strong>
option and optgroup need to be contained in a select tag
legend need to be contained in a form tag
thead, tbody, tfoot, colgroup, and caption need to be contained in a table tag
tr needs to be in table and thead/tfoot/tbody tags
td and th need to be in table, tbody, and tr tags
col needs to be in table and tbody tags
link and script need to be in a self closing in XHTML
<strong>Document Fragments</strong>
Fragments can collect nodes , and be appended or cloned in bulk. They are super-fast (2-3x faster than normal DOM manipulation)
[sourcecode language="js"]
function insert(elems, args, callback)
{
if (elems.length)
{
var doc = elems[0].ownerDocument || elems[0];
var fragment = doc.createDocumentFragment();
var scripts = getNodes(args, doc, fragment);
var first = fragment.firstChild;
if (first)
{
for (var i = 0; elems[i]; i++)
{
callback.call(root(elems[i], first), i > 0 ? fragment.cloneNode(true) : fragment);
}
}
}
}
var divs = document.getElementsByTagName("div");
insert(divs, ["Name:"], function(fragment){
this.appendChild( fragment );
}
);
insert(divs, ["First Last"], function(fragment){
this.parentNode.insertBefore(fragment, this);
}
);
Inline Script Execution
.append(“var foo = 5;”);
Must execute scripts globally
window.execScript() (for IE)
eval.call(window, “var foo = 5;”);
Cross-browser way is to build a script element then inject it to execute it globally
function globalEval(data)
{
data = data.replace(/^\s+|\s+$/g, "");
if (data)
{
var head = document.getElementsByTagName("head")[0] || document.documentElement
var script = document.createElement("script");
script.type = "text/javascript";
script.text = data;
head.insertBefore(script, head.firstChild);
head.removeChild(script);
}
}
Use the createContextualFragment() method as an alternative to the innerHTML for standards-compliant browsers to return a DocumentFragment node from an HTML string to inserting into the current document.
Use a regular expression to replace all tag varieties with their canonical XHTML equivalents
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 + “>”; });
}
Removing Elements
Have to clean up bound events because IE leaks memory. This is easy to do if it’s managed consistently.
Attaching functions that have a closure to another node as properties causes a memory leak:
elem.test = function() {
anotherElem.className = “foo”;
};
Users like having their “this” refer to the target element not the case in IE
What’s the solution?
Single Handler
Bind a single handler to an event
Call each bound function individually
Can fix “this” and the IE event object
How do we store the bound functions in a way that won’t leak?
Central Data Store Store all bound event handlers in a central object Link elements to handlers
Keep good separation
One data store per library instance
Easy to manipulate later
Trigger individual handlers
Easy to remove again, later
Use contextual targeting to look filter nodes within a collection based on their parent element or the existence of child nodes instead of drilling down from a parent node with an ID and recursively checking the properties of its children. For example, use getElementsByTagName to target all the anchor tags in the DOM, then check the ID of the second-nearest parent node within a loop to find specific links within the footer
var myLinkCollection = document.getElementsByTagName("a");
for (i=0;i<myLinkCollection.length;i++)
{
if (myLinkCollection[i].parentNode.parentNode.id === "footer")
{
// do something with footer anchor tags here
}
}
Define functions within conditional blocks(lazy evaluation) rather than conditional branching within functions
JavaScript DOM Events
In standards compliant browsers, when an event fires, the function is executed as a method of the object, and passed a single parameter; the event object. In Internet Explorer, it is not passed any parameters at all:
Keyup/Keydown is for key events that produce action instead of text, such as the cursor keys, enter, caps lock, shift, alt, etc. Use e.keycode for key events in standards compliant browsers
Keypress is for key elements that generate content and map to a unique ANSI value(A,a,1,space,etc). Use e.charcode for keypress events, and for all keys in IE
To prevent the default behavior of links unless JavaScript is disabled, use return false at the end of the event handling functions
The void method can be used to stop a value being returned from a function, method, or other code. It is normally used in bookmarklets to prevent the value returned from the bookmarklet processing from overwriting the web page.
In general, if a function is called without parentheses, “this” refers to the HTML element that is currently handling the event, otherwise “this” refers to the global object. There are some exceptions.
This is written into the onclick method in the following cases:
element.onclick = doSomething
element.addEventListener(‘click’,doSomething,false)
element.onclick = function () {this.style.color = ‘#cc0000′;}
Examples – referring
In the following cases this refers to the window:
element.onclick = function () {doSomething()}
element.attachEvent(‘onclick’,doSomething)
attachEvent() creates a reference to the function and does not copy it. Therefore it is sometimes impossible to know which HTML currently handles the event.
Event Capturing finds and triggers the target of an event by searching from the outermost node to its innermost child. This model is implemented in Netscape.
Event Bubbling finds and triggers the target of an event by searching from the innermost child node to the outermost parent node. This model is implemented in Internet Explorer and other browsers.
W3C takes a middle position in this struggle. Events are first captured until it reaches the target element, and then bubbled up. During the event flow, an event can respond to any element in the path (an observer) in either phase by causing an action, and/or by stopping the event (with method event.stopPropagation() for Mozilla and command event.cancelBubble = true for Internet Explorer), and/or by cancelling the default action for the event(event = false).
Making your event-driven styles, such as AJAX widgets and JavaScript UI controls, depend on a dynamically-added class name, such as body.jsenabled, so they are invisible to CSS and HTML only users.
The event object needs to be properly detected to distinguish between the IE and W3C event models, as shown in the following snippet:
function myFunc(evt)
{
/* First, if the W3C event object is passed into the function, assign it to the 'event' local variable inside the function. If not, check the window object for the event property of the IE DOM, then assign it to the 'event' variable. Otherwise, set the 'event' variable equal to an empty object. Before using the object, however, include one more condition that gracefully handles earlier browsers lacking an event object in their object models
<ul>
<li>/</li>
</ul>
event = (evt) ? evt : ((window.event) ? window.event : {})
if (event)
{
// process event here
}
}
For accessibility, you can make the enter key change focus to the next form field until the form is filled out completely, like so:
function blockEnter(evt)
{
evt = (evt) ? evt : event;
var charCode = (evt.charCode) ?
evt.charCode :
(
(evt.which) ?
evt.which :
evt.keyCode);
if (charCode == 13 || charCode == 3)
{
form.elements[elemName].focus( );
return false;
}
else
{
return true;
}
}
For accessibility, you can prevent accidentally double submissions by disabling the submit button after the first click, as shown here, or simply put input[type="submit"]:active {disable;} in CSS:
function submitForm( )
{
document.forms["myForm"].submit( );
submitForm = blockIt;
return false;
}
function blockIt( )
{
return false;
}
Use the activeElement property to return the object that currently has focus in IE, or use event delegation for the focus and blur methods on standards compliant browsers
An event must be checked on an element that could actually originate that event, so for onclick an anchor, input, or button element will work. Firefox populates event methods as object properties when an attribute with the same name as a standard event is set on an element:
var isEventSupported = (function()
{
var TAGNAMES =
{
'select':'input','change':'input',
'submit':'form','reset':'form',
'error':'img','load':'img','abort':'img'
}
function isEventSupported(eventName)
{
var el = document.createElement(TAGNAMES[eventName] || 'div');
eventName = 'on' + eventName;
var isSupported = (eventName in el);
if (!isSupported)
{
el.setAttribute(eventName, 'return;');
isSupported = typeof el[eventName] == 'function';
}
el = null;
return isSupported;
}
return isEventSupported;
}
)();
JavaScript String Objects
Strings created with the new constructor are objects, but they are still immutable. So copying and passing are by duplication and comparison is by value.
Use the Online Character Database to find the hex code for a numeric HTML entity value. Prepend the hex code with a backslash+u (“\u”) to generate it as a string in JavaScript
Use the backslash character to create carriage returns and create variables for each opening and closing tag when assigning large HTML fragments to a variable
var HTMLCode = ‘\ ‘ + pageTitle + ‘\ \ ‘ + pageContent + ‘\ \ ‘ + authorBio +’\ \ \ \ ‘ + footerContent + ‘\ ‘;
JavaScript Number Objects
When working with floating point numbers, it is important to understand the limitations and program defensively. For example, the Associative Law does not hold. (((a + b) + c) + d) is not guaranteed to produce the same result as ((a + b) + (c + d))
and a function, and it returns in array containing the results of calling the function on pairs of elements. This sort of thing might be popular in the future to take advantage of parallelism because work on each of the pairs could happen simultaneously.
var partial_reduce = function (array, func){
var i, result = [], x = array.length - 1;
for (i = 0; i < x; i += 2)
{
result.push(func(array[i], array[i + 1]) );
}
if (i === x)
{
result.push(array[i]);
}
return result;
};
We can then write an add function and a totalizer function that works by looping over partial_reduce until it produces a single value.
var add = function (a, b){
return a + b;
// To avoid precision errors, use return Math.round((a + b) * 100)/100;
};
var totalizer = function (array){
while (array.length > 1)
{
array = partial_reduce(array, add);
}
return array[0];
};
If I make an array containing 10000 elements all set to 0.01, then totalizer(array) produces 100, which is good.
JavaScript Array Objects
An array can be turned into a queue by using the push() and shift() methods. push() inserts the passed argument at the end of the array, and shift() removes and returns the first item. let’s see how to use them:
A stack can be simulated by using unshift()/shift() on an array
Join array elements into one string instead of concatenating multiple strings, using a function like this:
Swap myArray.push(obj) with myArray[myArray.length] = obj if programmatic array insertions are slowing down the browser
function buildString(pieces)
{
var tmp = [];
for (var i = 0, piece = pieces[i]; i++;)
{
tmp.push(piece);
}
str = tmp.join(''); // Specified an empty separator, thanks Jonathan
return str;
}
This method doesn’t suffer from the extra string objects, and generally executes faster.
Arrays are implemented so that only the indexed elements with a defined value use memory; they are sparse arrays. Sparse means that all indexes between zero and the length of the array are counted as part of the data structure, even if the values are undefined. Simply setting myArray.length = 99 without assigning any values will create an array of undefined indexes. Setting myArray[10] = ’someThing’ and myArray[57] = ’somethingOther’ only uses space for these two elements, just like any other object, but the length of the array will still be reported as 58 even though the other array indexes have undefined as their value.
function collectionToArray(collection)
{
var ary = [];
for(var i=0, len = collection.length; i < len; i++)
{
ary.push(collection[i]);
}
return ary;
}
function sum()
{
// Take any number of parameters and return the sum
var total = 0;
for (var i = 0; i < arguments.length; ++i)
{
total += arguments[i];
}
return total;
}
The arguments object contains each argument passed into the function. It can have its individual items changed, but not removed. You can loop through the key/value pairs in the arguments array as follows:
var root = {};
for ( var key in arguments[i] )
root[key] = arguments[i][key];
Any items added via bracket notation instead of dot notation don’t change the arguments.length property.
A collection of DOM elements (called a NodeList, such as getElementsByTagName() or document.images)
can’t be manipulated like an array, because it isn’t one. These return HTMLCollection object, but HTMLCollection inherits from NodeList
NodeLists are dynamic in nature whereas Arrays and arguments are static.
Each time a NodeList is accessed, it actually queries the document for all matching nodes.
As a result, an infinite loop occurs when traversing through a nodeList using a for statement
for (var i=0; i < document.images.length; i++)
{
document.body.appendChild(document.createElement(" img"));
}
//Store the length of a NodeList in a variable to avoid the infinite loop:
for (var i=0, len=document.images.length; i < len; i++)
{
//work with images here
}
Object collections have several advantages over arrays:
-The index key can be a string variable not just a number variable
-Members of area, controlRange, and option collections can be inserted with .add()
-Members of area, controlRange, and option collections can be deleted with .remove()
-Members of collections can be retrieved with .item() or .children()
-The number of objects in a collection can be set or retrieved with .length()
An Array object can be modified at any time and contains specific methods. You can convert a nodeList collection into an array like this:
// Call the array slice method to convert a nodeList to an array
var nodeArray = [].slice.call(nodeList, 0);
// Loop through the nodeList to copy each index to an array
function collectionToArray(collection)
{
var ary = [];
for(var i=0, len = collection.length; i < len; i++)
{
ary.push(collection[i]);
}
return ary;
}
You can generalize the convert function for future reference:
function arrayList(array)
{
return [].slice.call(array);
}
Programming a function for number comparison (since base10 is not used in the string sort function) is trivial:
function cmp(a, b)
{
return a - b;
}
Now we can sort our array using this function:
var list = [5, 10, 2, 1]; list.sort(cmp); /* The list is now: [1, 2, 5, 10] instead of [1, 10, 2, 5] with the default sort function */
To perform type checking for an array, first get a string version of the constructor using the toString() method. Next test the string to see if it contains the keyword “Array.” using the indexOf() function. If it returns -1, we know the literal is not an array, otherwise we know it is an array. For example:
var my_array = ["dog", "cat", "fish"];
if (my_array.constructor.toString().indexOf("Array") != -1)
{
my_array[] = "snake";
}
To get the min or max values of an array, call either function from the Math object and apply it to the Math object for the selected array
function smallest(array)
{
return Math.min.apply(Math, array);
}
function largest(array)
{
return Math.max.apply(Math, array);
}
assert(smallest([0, 1, 2, 3]) == 0, "Locate the smallest value.");
assert(largest([0, 1, 2, 3]) == 3, "Locate the largest value.");
Javascript Patterns
Use an empty named function within closure scope that has its prototype set to the object argument of its parent function to return an object factory as the constructor of a new function
function object(o)
{
// Create an empty function called F()
function F() {}
// Assign the object passed by the caller to the prototype of the F() function
F.prototype = o;
// Return the F() function as the constructor of the object assigned to this function call
return new F();
}
var Alice =
{
name: "Alice",
run: function() {document.write("Run! Run! Run!")}
};
/*
IE, Opera et al may need to use the object, clone or Object.beget functions
to make a new object and set its __proto__ to the parent object and return
the newly created child object.
<ul>
<li>/</li>
</ul>
var Bob = object(Alice);
/* Gecko and Webkit can directly manipulate the hidden __proto__ link. */
var Bob = Alice.__proto__;
Here, Alice is just a regular JavaScript object. It has one property and one method. Notice that there is no constructor. Using this, you can inherit from an object that requires a default value for its constructor properties without providing that value since you are bypassing the constructor by using the prototype instead
Right now Bob inherits from Alice. It has the same properties and methods. But we can customize Bob:
Bob.name = “Bob”;
Bob.hobbies = ["Science Fiction", "Archery"];
We replaced the name property and add a new property to Bob. But Bob and Alice still share the run() method. But watch this: We can add a new property to Alice, after we created Bob, and Bob will inherit that property also:
Alice.job = “Cryptographer”;
Now Bob also has the attribute job, with the value “Cryptographer”, courtesy of Alice.
Private properties
This is where things begin to get uglier. To get private properties in JavaScript you need to use closures (ie. anonymous functions). For example, like this:
var Jimmy = (function()
{
var age = 10;
return
{
birthDay: function() { age += 1; return age;},
getAge: function() { return age },
name: "Jimmy"
};
}
)();
What’s happened here is that we made an anonymous function and immediately executed it. The variable age is local to that function, and so it is private. But because it is referenced by the methods, it is not destroyed when we exit the function.
We can also use a closure to get private properties in a child object:
var Sarah = (function()
{
var age = 8;
var that = object(Jimmy);
that.birthDay = function() { age += 1; return age; };
that.name = "Jimmy";
return that;
}
)();
Notice that Sarah still has the getAge() method, but that this method still points to the old age variable in Jimmy.
Use function literals with an object literal as the return value to create parasitic inheritance without the need for an object function:
var Person = function(name, age)
{
/* "age" is a private variable */
return
{
name: name,
birthDate: function() {age += 1; return age;},
setAge: function(n) {age = n;},
getAge: function() {return age;}
};
};
var Employee = function(name, age, group)
{
var e = Person(name, age);
e.group = group;
return e;
};
Notice that the sub-class Employee also has access to the private variable name. And how do we create objects? You can just call the functions:
var joe = Person(“Joe”, 31);
var sam = Employee(“Sam”, 24, “Sales”);
Or if you prefer the traditional syntax:
var joe = new Person(“Joe”, 31);
var sam = new Employee(“Sam”, 24, “Sales”);
The two options are equivalent, and they work the same!
And a neat property is that the objects created by these constructors are compatible with the prototypal inheritance we saw earlier:
var alice = object(joe);
alice.name = “Alice”;
alice.setAge(27);
//Pseudoclassical Inheritance:
function NormalObject()
{
this.name = 'normal';
this.getName = function() { return this.name; };
}
function PreciousObject()
{
this.shiny = true;
this.round = true;
}
PreciousObject.prototype = new NormalObject();
var crystal_ball = new PreciousObject();
crystal_ball.name = 'Crystal Ball.';
crystal_ball.round; // true
crystal_ball.getName(); // "Crystal Ball."
function Person(){}
Person.prototype.getName = function(){
return this.name;
};
function Me()
{
this.name = "John Resig";
}
Me.prototype = new Person();
var me = new Me();
assert( me.getName(), "A name was set." );
//Inheritance by copying
var shiny =
{
shiny: true,
round: true
};
var normal =
{
name: 'name me',
getName: function()
{
return this.name;
}
};
function extend(parent, child)
{
for (var i in parent)
{
child[i] = parent[i];
}
}
extend(normal, shiny);
shiny.getName(); // "name me"
//Prototypal inheritance
function object(o)
{
function F(){}
F.prototype = o;
return new F();
}
var parent = {a: 1};
var child = object(parent);
child.a; 1
child.hasOwnProperty(a); false
//Namespace Mixin Inheritance
var Griffins = {};
Griffins.Chris =
{
brainCapacity : "Low",
getCapacity : function ()
{
return this.brainCapacity;
}
};
Griffins.Meg =
{
brainCapacity : "Decent",
lowSelfEsteem : "Low self esteem"
getCapacity : function ()
{
// Apply the getCapacity method from the Griffins.Chris object to the Griffins.Meg object and get the property brainCapacity
var capacity = Griffins.Chris.getCapacity.apply(this, arguments);
capacity += " - " + this.lowSelfEsteem;
return capacity;
}
};
// Returns "Low"
Griffins.Chris.getCapacity();
// Returns "Decent - Low self esteem"
Griffins.Meg.getCapacity();
// Private Mixin Inheritance
(function()
{
var strDate = "Friday";
// Define global method with reference to private variable
PublicFunction = function(){
return strDate;
}
// Define global method with reference to the name property of calling function
PublicFunctionWithThis = function(){
return this.name;
}
}
)();
// Define our test object
function Mixin(strName)
{
this.name = strName;
}
// Create an instance of our object.
var objMixin = new Mixin("Molly");
// Bind the simple alert function and execute.
objMixin.PublicFunction = PublicFunction;
objMixin.PublicFunction();
// Bind the this reference to a function with the name property and call the function
objMixin.PublicFunction = PublicFunctionWithThis;
objMixin.PublicFunction();
Private data members are not protected. Each subclass can change them permanently, affecting the values seen by all other subclasses. One way to overcome this problem is to call the superclass constructor from within each subclass constructor. In this way, each subclass creates a local copy of the private data members, and does not step over its peer subclasses memory.
Inheritance by prototyping supports dynamic inheritance. You can define superclass methods and properties after the constructor is done, and have the subclass object pick the new methods and properties, automatically.
There are two ways to call a constructor from within a constructor. One way is to use the call() method of the superclass constructor. Here is the superclass Shape() and the subclass SquareB():
function Shape()
{
var area = 50;
this.setArea = function(a) {area = a;};
this.getArea = function() {return area;};
}
function SquareB()
{
Shape.call(this);
}
The second way to call a constructor from within a constructor is by defining the superclass constructor as a method of the subclass constructor. Here is the superclass Shape() and the subclass SquareA():
function Shape()
{
var area = 50;
this.setArea = function(a) {area = a;};
this.getArea = function() {return area;};
}
function SquareA()
{
this.Shape = Shape;
this.Shape();
}
An object’s property may be enumerable. An enumerable property can assume only predefined values from a given list.
var a = ["jan", "feb", "march"]; 'a' can assume one of three values only: "jan", "feb", or "march".
Sometimes, you need to find out if a certain object is an instance of a given class (constructor() function). In other languages, this operation is called instanceOf(). JavaScript does not support the instanceOf() method, but we can write one ourselves, using the internal __proto__ (two underscores on each side) property. The algorithm is based on searching the object’s constructor along the inheritance chain, using __proto__:
function instanceOf(object, constructorFunction)
{
while (object != null)
{
if (object == constructorFunction.prototype)
{return true}
object = object.__proto__;
}
return false;
}
__proto__ is a property of an object, while prototype is a property of a constructor function. An object’s __proto__ is a reference to its constructor’s prototype.
Everytime you reassign a prototype, the constructor will be overwritten and needs to be everytime you assign a prototype using an object or a different instance.
var me = new Person; alert(me instanceof Person); // true alert(me.constructor === Person); // false
Function prototypes are an instanceof whatever they are an instance of (natively of an Object). If you assign an instance, or an object, as constructor prototype, every “new constructor” will share its methods. If those are created inside a scope, every instance will share that scope.
function A()
{
var scope = "private";
this.getScope = function(){ return scope; };
this.setScope = function(s){ scope = s };
};
var a1 = new A;
var a2 = new A;
a2.setScope("a2");
a1.getScope() === a2.getScope(); // returns false
// but if you use a1 as prototype
function B(){};
B.prototype = a1;
JavaScript Idioms
Always use === and !== instead of == and != to ensure that you don’t return any false positives for empty strings or null values
alert(” == ‘0′); //false
alert(0 == ”); // true
alert(0 == ‘0′); // true
=== returns false for all of these statements as expected.
For any DOM elements or properties that are assumed to work cross-browser, create a Boolean variable which defaults to the expected value, test the object, then reset the variable if the assumption is false
Create a div for simple logging, which works on all browsers, such as:
document.getElementById("output") .innerHTML += "<br>" + msg;
Build a global hash table for event binding in which each element gets a unique ID bound with a unique property (e.g., element.jQuery12345 = 1;) that points back to large data structure( data[1] = { ... all element data here ... };). Data (like event handlers) is stored here( data[1] = { handlers: { click: [ function(){...} ], ... } };), then cleaned up onunload so that IE doesn’t keep them in memory
Labels are used to name the 'while', 'do - while', 'for', 'for - in' and 'switch' control structures. The syntax used is:
LabelName:
Control Structure
[sourcecode language="js"]
outer:
for(i=0; i<200; i++)
{
for(j=0; j<200; j++)
{
if ( j==10 )
{
alert ('10');
break outer;
}
}
}
Using labels can help immensely on cutting down on loop iterations – and make scripts faster.
Assign both the window object and the document object to global variables to avoid implied globals
var DOM = document; var JS = window;
You can use the ‘in’ operator to test for a property. It can only search for the name, and cannot be used to see if one of the properties holds a specific value or value type.
if( ‘innerHTML’ in document.body ) { …}
Note that this is around 20 times slower in Internet Explorer than a boolean check
To replace all occurrences of a regex match, you need to set the global modifier.
var myString = "this is my string";
myString = myString.replace(/ /,"%20"); // "this%20is my string"
myString = myString.replace(/ /g,”%20?); // “this%20is%20my%20string”
Attach a local function to your global namespace to make it accessible outside of the anonymous function
window['MYNAMESPACE']['myFunction'] = myFunction;
If you need to add a parameter to handle a special case call in your function that you’ve already been calling, set a default value for that parameter in your function, just in case you missed updating one of the calls in one of your scripts.
function addressFunction(address, city, state, country)
{
country = country || “US”; // if country is not passed, assume USA
//rest of code
}
var myVariable = 1;
while( myVariable H
string[3] // –> l
var h = string.charAt(0)
The Number constructor may be used to perform explicit numeric conversion:
var myString = “123.456″;
var myNumber = Number( myString );
A double use of the ! operator can be used to normalize a boolean value:
var arg = null;
arg = !!arg; // arg is now the value false, rather than null
arg = “finished”; // non-empty string
arg = !!arg; // arg is now the value true
The !! operator normalizes the value returned by the comparison to a boolean value, so !!(obj1 && obj2) returns either True or False, while (obj1 && obj2) returns the actual value.
Despite the fact that JavaScript has no notion of constant, you can use DOM constants or string literals in case statements
to emulate enumeration:
switch(node.type)
{
case Node.ELEMENT_NODE: blah blah
case Node.ATTRIBUTE_NODE: blah blah
case Node.TEXT_NODE: blah blah
}
Remember to put a break after each case statement, unless you intend to group multiple cases to one action, like this:
switch(i)
{
case 3:
case 6:
dosomething()
break;
case 1:
case 4:
doSomethingElse();
break;
default:
doSomethingElseAllTogether()
}
The exception to Javascript’s loose type checking is comparing case values to the value of the switch statement.
For the case to be a match, the data types must match.
Think for it as switch statements including === (strict equal) instead of == (equal) for case comparisons.
y = 5;
switch(y)
{
case '5':
alert("hi"); // this alert will not show since the data types don't match
}
Use onmousedown instead of onclick (~100msec faster!)
Use setTimeout with zero wait to yield control to the browser from slow functions
setTimeout(function() { doSaveImpl(id); }, 0);
Initially, the statements within the try block execute. If an exception is thrown, the script’s control flow immediately transfers to the statements in the catch block, with the exception available as the error argument. Otherwise the catch block is skipped. Once the catch block finishes, or the try block finishes with no exceptions thrown, then the statements in the finally block execute. This is generally used to free memory that may be lost if a fatal error occurs—though this is less of a concern in JavaScript.
try
{
// Statements in which exceptions might be thrown
}
catch(error)
{
// Statements that execute in the event of an exception
}
finally
{
// Statements that execute afterward either way
}
Safari 3 always returns an empty string for the getComputedStyle value of an element with the display property set to none. To workaround this, test for the existence of the value in addition to it being blank
var ret = document.defaultView.getComputedStyle(elem, null);
return !ret || ret.getPropertyValue("color") == "";
getComputedStyle returns a string composed of the value and the measurement unit in IE, but simply the numeric value in every other browser. Use a regular expression to abstract away this difference instead of littering the code with conditional checks:
if (!/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) )
{
// Remember the original values
var style = elem.style.left
var 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;
}
//Reference: http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
To calculate the width of an element, the fool-proof, painful way is to take the offsetWidth, then subtract computed padding & border:
Element.getCSSWidth = function(element)
{
element = $(element);
var element_width = element.offsetWidth - parseFloat(element.getStyle("borderLeft"));
element_width -= parseFloat(element.getStyle("paddingLeft"));
element_width -= parseFloat(element.getStyle("paddingRight"));
element_width -= parseFloat(element.getStyle("borderRight"));
return element_width;
};
Avoid a setTimeout or setInterval deadlock by making sure that the end condition of the loop is not set by a function within the recursive timer call
Use setTimeout and clearTimeout to add a delay between function calls or loop iterations
var preload = setTimeout(function(){}, 700);
clearTimeout(preload);
function preload()
{
for (var i = 0, img; img = document.images[i]; i++)
{
while (!img.complete || !img.currentStyle.backgroundImage || !document.defaultView.getComputedStyle(img,null).getPropertyValue('background-image') )
{
var preload = setTimeout(function(){preload()}, 500);
clearTimeout(preload);
}
}
}
For reading an unverified attribute, use the following:
var anchor = document.getElementById(“sirius”);
if (anchor.getAttribute("title") && anchor.title == "Not the satellite radio")
{
...
}
This makes sure that the attribute exists, and is not null, before fetching its value.
For writing to an unverified attribute, use the following code:
var anchor = document.getElementById("sirius");
anchor.setAttribute("title", "");
anchor.title = "Yes, the satellite radio";
This code makes sure that the attribute is created correctly first, and is then set in such a way that Internet Explorer will not have problems if the attribute affects the visual display of the element.
Process an array in small chunks with a timer to free up the browser between iterations and avoid the long-running script dialog you may encounter when using a blocking loop such as a for statement:
function chunk(array, process, context)
{
// Use the concat function to make a copy of the array
var items = array.concat();
// Queue the indexes to process one item at a time with a delay
setTimeout(function(){
// Remove the top item from the array and pass it to the process function
var item = items.shift();
// Run the process function using the optional context object as a replacement for the 'this' reference
process.call(context, item);
// Recursively loop through the array until you reach the last index
if (items.length > 0)
{
setTimeout(arguments.callee, 100);
}
}, 100);
}
You can also use a variation of this concept to queue an array of function calls to run sequentially:
var JS = window;
function schedule(functions, context)
{
setTimeout(function(){
var process = functions.shift();
process.call(context);
if (functions.length > 0)
{
setTimeout(arguments.callee, 100);
}
}, 100);
}
// Call the function
schedule([crawl, walk, run], JS);
Use the call property to implement loop through a callback function instead of a hardcoded function
function loop(array, fn)
{
for ( var i = 0; i < array.length; i++)
{
fn.call( array, array[i], i );
}
}
var num = 0;
loop([0, 1, 2], function(value, i){
assert(value == num++, "Make sure the contents are as we expect it.");
assert(this instanceof Array, "The context should be the full array.");
}
);
Use a self-invoking anonymous function to avoid nested loops which may lock up the browser:
function bubbleSort(items)
{
var len = items.length - 1;
for (var i = len, item; item = items[i]; i--)
{
for (var j = i; j >= 0; j--)
{
if (item < items[j-1])
{
var temp = item;
item = items[j-1];
items[j-1] = temp;
}
}
}
}
function bubbleSort(array, onComplete)
{
var pos = 0;
var len = array.length;
(function()
{
var j, value;
for (j = len; j > pos; j--)
{
if (array[j] < array[j-1])
{
value = data[j];
data[j] = data[j-1];
data[j-1] = value;
}
}
pos++;
if (pos < array.length)
{
setTimeout(arguments.callee,10);
}
else
{
onComplete();
}
}
)();
}
Use the ability to create function properties to add a cache data structure to an existing function which collects return values using the memoization pattern:
getElements.cache = {};
function getElements(name)
{
// create a local variable to store return value
var results;
if ( getElements.cache[name] )
{
// check for the requested DOM elements in the cache property of the function
results = getElements.cache[name];
}
else
{
// get the DOM elements from the document and add them to the cache for future reference
results = document.getElementsByTagName(name);
getElements.cache[name] = results;
}
return results;
}
Use a generic memoization function to add caching abilities to any existing code:
function memoizer(fundamental, cache)
{
cache = cache || {};
var shell = function(arg)
{
if (!(arg in cache))
{
cache[arg] = fundamental(shell, arg)
}
return cache[arg];
};
return shell;
}
Redefine a function within itself to cache values of local parameters within the function as properties of that function
If you unknowingly have two assignments to the same property value, the last one to execute is the one that sticks.
If one property of an object is not enough to make it unique, you may need to combine values to obtain that uniqueness.
var sales = {};
sales[sales.length] = {period:”q1″, region:”east”, total:2300};
sales[sales.length] = {period:”q2″, region:”east”, total:3105};
None of the label properties – the properties you’d likely be using to look up sales information – is totally unique. The East region is shared by four objects, and the Q1 period is shared by three objects. But a combination of the region and period names generates a unique identifier for a given object. Thus, if we use a name of the form region_period (e.g., east_q1), other scripts can perform lookups to reach individual records. Therefore, the hash table maker comes after the object creation statements above:
for (var i = 0; i < sales.length; i++)
{
sales[sales[i].region + "_" + sales[i].period] = sales[i];
}
To access the third quarter sales for the central region, use the following reference:
sales["central_q3"].total
When a hash table entry is assigned a reference to an object (as happens in the preceding examples), each hash table entry simply points to the original object without duplicating the data. Any change you assign to an object's property in the array of objects is reflected in the hash table reference to that object's property.
Any time you have a large multidimensional array or collection of objects through which your scripts will be looking for matching records, try to add the simulated hash table to your array. It gives you the best of both worlds: the ability to iterate through the collection when you need to use every entry, and the ability to dive into a specific record without any looping.
The W3C DOM specification includes facilities to help scripts know what level of support is offered by the browser. Every element object has an isSupported( ) method, whose parameters let you test for the browser's support of various W3C standards or DOM modules. The module names available for testing as of DOM Level 2 are Core, XML, HTML, Views, StyleSheets, CSS, CSS2, Events, UIEvents, MouseEvents, MutationEvents, HTMLEvents, Range, and Traversal. In theory, if the browser fully supports the required portions of a module, the isSupported( ) method returns a value of true:
if (myElem.isSupported("CSS", "2.0"))
{
myElem.style.color = "green";
}
But this test is not rigorous enough for most scripters. The standards contain numerous optional features, whose omission still allows a browser maker to claim full conformance with the standard. The only way to assure complete support for modern scriptable features is via the more explicit object, property, and method condition testing.
JavaScript Gotchas
The comma operator causes the expressions on either side of it to be executed in left-to-right order, and returns the value of the expression on the right. For example:
var x = (y = 3, z = 9);
This sets x to 9.
When using objects as hash tables, the toString, valueOf and other object properties are not enumerated. This means if you try this:
var x = {};
x["toString"]
It will display the details of the toString method on the Object Object instead of the string representation of the values assigned to the variable. This can be worked around by using the Javascript standard function ‘hasOwnProperty’. So you could write a simple Hashtable object with get and put methods like this:
function Hashtable()
{
this.table = {};
}
Hashtable.prototype.get = function(key){
if(this.table.hasOwnProperty(key) )
{
return this.table[key];
}
else
{
throw "No such key";
}
}
Hashtable.prototype.put = function(key, value){
this.table[key] = value;
}
// The following code now works as expected:
var x = new Hashtable();
x.put("get", 5);
x.get("get");
// returns 5
x.get("toString");
// returns "uncaught exception: No such key"
If you use an array object as a hash table, the length will be zero. Variables of Boolean, Date, and String type can store key/value pairs as well since they all inherit from the Object prototype. It works because all you’re doing is setting properties on an object (car["color"] is the same as car.color), and a for..in loop simply iterates over an object’s properties, but the loop will iterate through all the constructor properties of the object as well as the properties of your instance.
When you create booleans, numbers and strings using the new constructor, the results are objects, not primitives. Anything you do with these will be by reference, not by value, except for strings, which are immutable and use copies.
Make sure to use quotes around strings and reference files using relative paths to call on-demand Javascript include functions
Make sure to set the onload and src attributes of the script before inserting it into the document
Make sure to use status === 0 when checking a successful request on the file:// domain or local file system instead of status === 200 as in http:// AJAX requests
Make sure to get responseText or responseXML inside the readyState function and append it to a documentFragment instead of assigning it to a local variable
Make sure you use the continue statement to jump to the beginning of a loop instead of the break statement when you need to wait until the condition is satisfied
Make sure the CSS IDs in your HTML don’t match any Javascript function, object, property or variable names to avoid clobbering those values
Avoid putting a trailing semicolon after the counter increment or final parameter of a for loop
Avoid inserting XML directly into the DOM without parsing anything
To replace all occurrences of a regex match, you need to set the global modifier.
var myString = “this is my string”;
myString = myString.replace(/ /,”%20″); // “this%20is my string”
myString = myString.replace(/ /g,”%20″); // “this%20is%20my%20string”
Use a parenthesis after a function declaration to immediately execute it without an explicit call, and use parenthesis around a function to make it anonymous
(function(){ })();
Attach a local function to your namespace within the window object to make it accessible outside of the anonymous function
window['MYNAMESPACE']['myFunction'] = myFunction;
If you need to add a parameter to handle a special case call in your function that you’ve already been calling, set a default value for that parameter in your function, just in case you missed updating one of the calls in one of your scripts.
function addressFunction(address, city, state, country)
{
country = country || “US”; // if country is not passed, assume USA
//rest of code
}
Here are the differences between undefined and null:
- null and undefined are two different types.
- null is an special object of type null that means “no value”
- undefined is not an object, it’s type is undefined
- A variable or property is undefined with a type of undefined by default
- The == (equality) operator considers them equal, but the === (identity) operator does not
- The delete operator “undefines” hash values and array members
- The delete operator cannot “undefine” hash or array objects since it is not an object
- null can be set as the type of an object if it has a defined type
- If a variable or property name has not be assigned a value, it is not an object
- null checks against unassigned names will throw an error since null is an object
// Wrong way, since if myObject is undefined, it can't test for null, and will throw an error
if ( myObject !== null && typeof myObject !== 'undefined' )
// Right way
if ( typeof myObject !== 'undefined' && myObject !== null )
// Better way, since it uses the difference between the operators instead of string comparison
function isUndefined(x) { return x == null && x !== null; }
// Best way, since it is shorter and avoids the use of the equality operator
function isUndefined(x) { var u; return x === u; }
Whenever you’re accessing a property method through a reference instead of directly through its object, the context of “this” references the global window object. Instead of using “this”, use the object name when calling the function value.
Whenever you need to pass a value to an anonymous function, set the value to a local variable of the calling function and access through the closure
The main difference between objects and arrays is the length property. The length property is always one larger than the largest integer key in the array.
JavaScript contains a small set of data types. It has the three primitive types boolean, number, and string and the special values null and undefined. Everything else is variations on the object type.
Do not confuse the primitive Boolean values true and false with the true and false values of the Boolean object. Any object whose value is not undefined , null, 0, NaN, or the empty string , including a Boolean object whose value is false, evaluates to true when passed to a conditional statement.
Comparing NaN with anything (even NaN) is always false.
apply is very similar to call, but apply can only use an array literal or an Array object. call can use the arguments array or a list of arguments
JavaScript Debugging
Replace Notepad as the View Source target in IE by changing the value of the following registry key:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\View Source Editor\Editor Name
As an alternative, copy the code from Notepad to an IDE with line numbers to map IE error messages to their location in the code
An assignment of a variable always evaluates to true when used in a conditional statement. This can cause an infinite loop, such as:
while (x = 0)
Create a timestamp to check the execution time of a function
var timestamp=+new Date();
Assertion is one of the commonly-used debugging techniques. It’s used to ensure that an expression evaluates to true during execution. if the expression evaluates to false, this indicates a possible bug in code. JavaScript lacks a built-in assert function, but fortunately it’s easy to write one. The following implementation throws an exception of type AssertException if the passed expression evaluates to false:
function AssertException(message)
{
this.message = message;
}
AssertException.prototype.toString = function ()
{
return 'AssertException: ' + this.message;
}
function assert(exp, message)
{
if (!exp)
{
throw new AssertException(message);
}
}
Throwing an exception on its own isn’t very useful, but when combined with a helpful error message or a debugging tool, you can detect the problematic assertion. You may also check whether an exception is an assertion exception by using the following snippet:
try
{
// ...
}
catch (e)
{
if (e instanceof AssertException)
{
// ...
}
}
This function can be used in a way similar to C or Java:
assert(obj != null, ‘Object is null’);
If obj happens to be null, the following message will be printed in the JavaScript console in Firefox:
uncaught exception: AssertException: Object is null
Prevent annoying errors while testing in IE when using console.log() for Firebug:
try { console.log(”); } catch(e) { console = { log: function(s) {alert(s);} }
Reuse a Generic Function for Form Error Handling
window.onload = attachFormHandlers;
function attachFormHandlers()
{
// Ensure we're working with a 'relatively' standards
// compliant browser
if (document.getElementsByTagName)
{
var objForm = document.getElementsByTagName('form');
for (var iCounter=0; iCounter<objForm.length; iCounter++)
objForm[iCounter].onsubmit = function(){return checkForm(this);}
}
}
function checkForm(objForm)
{
var arClass, bValid;
var objField = objForm.getElementsByTagName('*');
for (var iFieldCounter=0; iFieldCounter<objField.length; iFieldCounter++)
{
// Allow for multiple values being assigned to the class attribute
arClass = objField[iFieldCounter].className.split(' ');
for (var iClassCounter=0; iClassCounter<arClass.length; iClassCounter++)
{
switch (arClass[iClassCounter])
{
case 'string':
bValid = isString(objField[iFieldCounter].value.replace(/^\s*|\s*$/g, ''));
break;
case 'number' :
bValid = isNumber(objField[iFieldCounter].value);
break;
case 'email' :
bValid = isEmail(objField[iFieldCounter].value);
break;
default:
bValid = true;
}
if (bValid == false)
{
// If this field is invalid, leave the testing early,
// and alert the visitor to this error
alert('Please review the value you provided for ' + objField[iFieldCounter].name);
objField[iFieldCounter].select();
objField[iFieldCounter].focus();
return false;
}
}
}
return true;
}
function isString(strValue)
{
return (typeof strValue == 'string' && strValue != '' && isNaN(strValue));
}
function isNumber(strValue)
{
return (!isNaN(strValue) && strValue != '');
}
function isEmail(strValue)
{
var objRE = /^[\w-\.\']{1,}\@([\da-zA-Z-]{1,}\.){1,}[\da-zA-Z-]{2,}$/;
return (strValue != '' && objRE.test(strValue));
}
Use a scheduler function to queue long running functions
function schedule(functions, context)
{
setTimeout(function()
{
var process = functions.shift();
process.call(context);
if (functions.length > 0)
{
setTimeout(arguments.callee, 100);
}
}, 100);
}
Use a memoize function to cache recursive function data:
function memoizer(memo, fundamental)
{
var shell = function (n)
{
var result = memo[n];
if (typeof result !== 'number')
{
result = fundamental(shell, n);
memo[n] = result;
}
return result;
};
return shell;
};
As a web app runs, objects are created and destroyed but still consume memory. Eventually, enough memory is consumed that the garbage collector releases some by clearing space used by obsolete objects.
But the garbage collection process stops execution of the application until it is finished, so if there’s alot of objects to check and delete, this results in slowdown in the browser that breaks the user experience.
Type checking for instanceof Error doesn’t give you any useful information. By checking for the more specific error types, you get more robust error handling:
Error – base type for all errors. Never actually thrown by the engine.
EvalError – thrown when an error occurs during execution of code via eval()
RangeError – thrown when a number is outside the bounds of its range. For example, trying to create an array with -20 items (new Array(-20)). These occur rarely during normal execution.
ReferenceError – thrown when an object is expected but not available, for instance, trying to call a method on a null reference.
SyntaxError – thrown when the code passed into eval() has a syntax error.
TypeError – thrown when a variable is of an unexpected type. For example, new 10 or “prop” in true.
URIError – thrown when an incorrectly formatted URI string is passed into encodeURI, encodeURIComponent, decodeURI, or decodeURIComponent.
Throwing any value will result in an error if it’s not caught via a try-catch statement.
Firefox, Opera, and Chrome all call String() on the value that was thrown to display something logical as the error message; Safari and Internet Explorer do not. The only surefire way to have all browsers display your custom error message is to use an Error object.
If you’re throwing your own errors, and you’re throwing a data type that isn’t an error, you can more easily tell the difference between your own errors and the ones that the browser throws. There are, however, several advantages to throwing actual Error objects instead of other object types.
First, as mentioned before, the error message will be displayed in the browser’s normal error handling mechanism. Second, the browser attaches extra information to Error objects when they are thrown. These vary from browser to browser, but they provide contextual information for the error such as line number and column number and, in some browsers, stack and source information. Of course, you lose the ability to distinguish between your own errors and browser-thrown ones if you just use the Error constructor.
The solution is to create your own error type that inherits from Error . For example:
function MyError(message)
{
this.message = message;
}
MyError.prototype = new Error();
There are two important parts of this code:
1) the message property, which is necessary for browsers to know the actual error string
2) setting the prototype to an instance of Error, which identifies the object as an error to the JavaScript engine.
Now, you can throw an instance of MyError and have the browser respond as if it’s a native error:
throw new MyError(“Hello world!”);
The only caveat to this approach is that Internet Explorer prior to version 8 won’t display the error message. Instead, you’ll see the generic “exception thrown but not caught” error message. Throwing custom error objects allows you to test specifically for your own errors:
try
{
//something that causes an error
}
catch (ex)
{
if (ex instanceof MyError)
{
//handle my own errors
}
else
{
//handle all others
}
}
If you’re always catching any errors you throw, then IE’s slight stupidity shouldn’t matter all that much. The benefits from such an approach are huge in a system with proper error handling. This approach gives you much more flexibility and information for determining the correct course of action for a given error.
In order to handle concurrent events, the onreadystatechange function can be wrapped in a function which will pass an argument to the query string of the URL as well as the XMLHTTP object to the callback function:
// Call POST or GET function, passing XML request object and server request
xmlRequestPost(xmlhttpTime,'time');
xmlRequestGet(xmlhttpRate,'rate');
function xmlRequestGet(xmlhttp,option)
{
xmlhttp.open("GET","/poc/xmlServerGet.php?return="+option,true);
xmlhttp.onreadystatechange = function(){checkData(xmlhttp)};
xmlhttp.send(null);
}
function xmlRequestPost(xmlhttp,option)
{
xmlhttp.open("POST","/poc/xmlServerPost.php",true);
xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xmlhttp.onreadystatechange = function(){checkData(xmlhttp)};
xmlhttp.send("return="+escape(option));
}
For the single request above, the “checkData” function will be called on an event. For the concurrent requests, it will be called from the event function, passing the XML object variable.
The function will be triggered for different events in the request, therefore it needs to interrogate the “readyState” to determine when it is complete (1=Loading 2=Loaded 3=Interactive 4=Complete).
The “status” will also be returned corresponding to http status codes (e.g. 301, 404), the normal status is 200.
// handling single request
function checkData()
{
if (xmlhttp.readyState != 4) return;
if (xmlhttp.status != 200) return;
var xmlDoc = xmlhttp.responseXML.documentElement;
if (!xmlDoc) return;
// take any appropriate action required
alert(
"readyState ="+ xmlhttp.readyState +String.fromCharCode(0x0A)+
"status ="+ xmlhttp.status +String.fromCharCode(0x0A)+
"statusText ="+ xmlhttp.statusText +String.fromCharCode(0x0A)+
"responseText ="+ xmlhttp.responseText +String.fromCharCode(0x0A)+
"responseXML ="+ xmlhttp.responseXML
);
}
// handling concurrent requests
function checkData(xmlhttp)
{
if (xmlhttp.readyState != 4) return;
if (xmlhttp.status != 200) return;
var xmlDoc = xmlhttp.responseXML.documentElement;
if (!xmlDoc) return;
// Take appropriate action, depending on returned data
var action = xmlDoc.getElementsByTagName("action")[0].childNodes[0].nodeValue;
var parameter = xmlDoc.getElementsByTagName("parameter")[0].childNodes[0].nodeValue
switch(action)
{
case "time": displayTime(parameter); break;
case "rate": displayRate(parameter); break;
default: alert("Invalid function");
}
}
The DOM3 XPath API is called XPathEvaluator.
It contains methods for working with XPath expressions, including the primary method evaluate(), which accepts five arguments:
the XPath query string
the node from which the query should begin
a namespace resolver
the type of result to return
an optional result object onto which the new results should be added
The last argument is rarely used since the result is also returned as the value of evaluate().
The result types are:
XPathResult.ANY_TYPE – Returns the type of data appropriate for the XPath expression
XPathResult.ANY_UNORDERED_NODE_TYPE – Returns a node set of matching nodes, although the order may not match the order of the nodes within the document
XPathResult.BOOLEAN_TYPE – Returns a Boolean value
XPathResult.FIRST_ORDERED_NODE_TYPE – Returns a node set with only one node, which is the first matching node in the document
XPathResult.NUMBER_TYPE – Returns a number value
XPathResult.ORDERED_NODE_ITERATOR_TYPE – Returns a node set of matching nodes in the order in which they appear in the document. This is the most commonly used result type.
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE – Returns a node set snapshot, capturing the nodes outside of the document so that any further document modification doesn’t affect the result set. The nodes in the result set are in the same order as they appear in the document.
XPathResult.STRING_TYPE – Returns a string value
XPathResult.UNORDERED_NODE_ITERATOR_TYPE – Returns a node set of matching nodes, although the order may not match the order of the nodes within the document
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE – Returns a node set snapshot, capturing the nodes outside of the document so that any further document modification doesn’t affect the node set. The nodes in the node set are not necessarily in the same order as they appear in the document.
The information returned from evaluate() depends wholly on the result type requested. The simplest results return a single value (Boolean, Node, Number, and String) while the more complex ones return multiple nodes. When called, evaluate() returns an XPathResult object. This object’s properties contain the result of the evaluation. There is a property for each type of simple result: booleanValue, singleNodeValue, numberValue, and stringValue. Additionally, there is a resultType property whose value maps to one of the XPathResult constants. This is useful in determining the type of result when using XPathResult.ANY_TYPE. If there is no matching result, evaluate() returns null.
To perform an XPath query, you’ll need to use an XPathEvaluator object. You can either create a new instance or use a built-in one. Creating your own means instantiating XPathEvaluator:
var evaluator = new XPathEvaluator();
//get first div
var result = evaluator.evaluate(
"//div", document.documentElement, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
);
alert("First div ID is " + result.singleNodeValue.id);
In standards browsers all instances of Document also implement the XPathEvaluator interface, which means you can access document.evaluate() if you want to query the HTML page. If you load an XML document via XMLHttpRequest or another mechanism, the evaluate() method is also available. For example:
//get first div
var result = document.evaluate(
"//div", document.documentElement, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
);
alert("First div ID is " + result.singleNodeValue.id);
Note that you cannot use document.evaluate() outside of document; you can use an instance of XPathEvaluator any document.
There are two ways to return multiple nodes, via iterator or snapshot. Iterator results are still tied to the document, so any changes made will automatically be reflected in the result set. Snapshot results, on the other hand, take the results at that point in time and are not affected by further document augmentation. Both result types require you to iterate over the results. For iterator results, you’ll need to use the iterateNext() method, which will either return a node or null (this works for both ordered and unordered iterator results):
//get all divs - iterator style
var result = document.evaluate(
"//div", document.documentElement, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null
);
if (result)
{
var node = result.iterateNext();
while(node)
{
alert(node.id);
node = node.iterateNext();
}
}
For snapshot results, you can use the snapshotLength property to determine how many results were returned and the snapshotItem() method to retrieve a result in a specific position. Example (this works for both ordered and unordered snapshot results):
//get all divs - iterator style
var result = document.evaluate(
"//div", document.documentElement, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
);
if (result)
{
for (var i=0, len=result.snapshotLength; i < len; i++)
{
alert(result.snapshotItem(i).id);
}
}
In most cases, a snapshot result is preferable to an iterator result because the connection with the document has been severed; every call to iterateNext() re-executes the XPath query on the document and so is much slower. In short, iterator results have the same performance implications as using HTMLCollection objects, which also query the document repeatedly.
Compared to walking the DOM manually, XPath queries are incredibly fast and so they are used in several JavaScript-based CSS query engines to speed up their execution. Anytime you are looking for a specific node or set of nodes buried inside of a document, consider using XPath to speed up the process in Firefox, Safari, Chrome, and Opera (Internet Explorer doesn’t support DOM 3 XPath).
If you’re simply using XPath to query an HTML document, then the namespace resolver argument for evaluate() will always be null;
Every namespace URI is mapped to a specific prefix defined in the XML document with the exception of the default namespace, which doesn’t require a prefix. A namespace resolver performs the mapping between namespace prefix and namespace URI for the XPath engine. There are two ways to create namespace resolvers. The first is to create a function that accepts the namespace prefix as an argument and returns the appropriate URI. For example:
function resolver(prefix)
{
switch(prefix)
{
case "yahoo": return "http://www.yahoo.com/";
case "google": return "http://www.google.com/";
default: return "http://www.clusty.com/";
}
}
This approach may work if you already have the prefixes and namespace URIs handy. When the default namespace is going to be resolved, an empty string is passed into the function.
The second approach is to create a namespace resolver using a node that contains namespace information, such as:
<books xmlns:wrox="http://www.goodreads.com/" xmlns="http://www.amazon.com/">
<wrox:book>Practical JavaScript from Scratch</book>
</books>
The <books> element contains all of the namespace information for this XML snippet. You can pass a reference to this node into the XPathEvaluator object’s createNSResolver() method and get a namespace resolver automatically created:
var evaluator = new XPathEvaluator();
var resolver = evaluator.createNSResolver(xmldoc.documentElement);
This approach is more useful when the namespace information is embedded in the XML document, in which case it doesn’t make sense to duplicate that information and too tightly couple the JavaScript to the XML document.
Using either approach, you can easily evaluate XPath expressions on XML documents that have namespaces:
[sourcecode language="javascript"]
var evaluator = new XPathEvaluator();
var resolver = evaluator.createNSResolver(xmldoc.documentElement);
var result = evaluator.evaluate("
goodreads:book", xmldoc.documentElement, resolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null
);
if (result)
{
alert(result.singleNodeValue.firstChild.nodeValue);
}
Selenium is a heavier-weight testing tool that allows cross-platform testing in the browser. Writing cross-platform JavaScript code is a regrettable cross that Web developers must bear. Writing Web applications is difficult enough, and just when you think you’re done, you inevitably run into some obscure bug that is only present on one browser.
Unfortunately, a unit test does not catch this type of bug. In fact, these bugs often make a Web developer skeptical of doing any testing in the first place. They figure that testing is yet another hoop to jump through, that it gets in the way of their already tight deadline, and it doesn’t even reliably work. So why bother?
Selenium (as well as other browser testing tools) is an answer to this problem. You can write functional tests that run in each browser, and then implement some form of continuous integration system that runs the functional tests upon each check-in of the source code. This allows potential bugs in the browser to quickly get caught, and has an immediate payoff.
You can turn the browsers autocomplete off by adding autocomplete=”off” attribute to an input field
Use a timeout instead of keydown event bubbling to handle fast typers on slow connections
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;}
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.
In named anonymous functions the function name yell is only available within the scope where it’s defined. The property name is available whenever you can refer to the 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 it’s assigned to a different object, that’s not what we want. In truly anonymous functions, the second invocation would fail because it would try to call the function property recursively, but it would no longer exist.
var ninja =
{
yell: function hello(n)
{
return n>0 ? yell(n-1)+"a" : "hiy";
}
}
var x = ninja;
x.yell(3);
ninja = {};
x.yell(3);
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.
A is an Object. Object A has state A and behavior.
Object A has a reference to A
An object can have references to other objects
Object A can communicate with Object B because it has a reference to Object B .
Object B provides an interface that constrains access to its own state and references.
Object A does not get access to Object B ’s innards.
Object A does not have a reference to Object C, so Object A cannot communicate with Object C.
In an Object Capability System, an object can only communicate with objects that it has references to.
An Object Capability System is produced by constraining the ways that references are obtained.
A reference cannot be obtained simply by knowing the name of a global variable or a public class.
There are exactly three ways to obtain a reference: Creation, Construction, and Introduction.
If a function creates an object, it gets a reference to that object.
An object may be endowed by its constructor with references. This can include references in the constructor’s context and inherited references.
A has a references to B and C. B has no references, so it cannot communicate with A or C. C has no references, so it cannot communicate with A or B.
A calls B , passing a reference to C. B is now able to communicate with C.
If references can only be obtained by Creation, Construction, or Introduction, then you may have a safe object capability system. If references can be obtained in any other way, you do not have a safe system.
A holdover from the old DOM0 days it’s a practice where elements with a given name or ID are added as an expando property to another DOM node.
Here are my two favorite examples of this bug in action:
The first is a simple form that does a search on a site. Additionally a link is provided that, when clicked, fills in a search value and submits the form.
<form action="" method="POST" id="form">
Search: <input type="text" name="search" id="search">
<input type="submit" id="submit">
</form>
<a href="#" id="quick">Quick Search 'JavaScript'</a>
<script>
document.getElementById("quick").onclick = function(){
document.getElementById("search").value = "JavaScript";
document.getElementById("form").submit();
};
</script>
Before running the example you can spot the problem with a quick run to the address bar:
javascript:alert(document.getElementById("form").submit)"[object HTMLInputElement]"
The .submit() method (which is available on all Form elements) is overwritten by the input element of the same name. This ends up being a very common problem – with frameworks using id=”submit” as a default in their code.
Worst of all this fails in all browsers (preventing you from accessing the overwritten method).
The second example is even more devious. In this case we’re going to loop over all the DOM elements in the page and alert out their contents.
<div id="length">12 stories</div>
<div id="makeup">radiation</div>
<script>
var all = document.getElementsByTagName("*");
for (var i = 0; i < all.length; i++)
{
alert( all[i].innerHTML );
}
</script>
This will work in most browsers – but not Internet Explorer.
To understand why we return to the address bar.
javascript:alert(document.getElementsByTagName("*").makeup)
"[object]"
javascript:alert(document.getElementsByTagName("*").length)
"[object]"
Oops. All browsers turn elements with specific IDs into expandos of the returned NodeSet.
But Internet Explorer goes a step farther and decides to overwrite the built-in .length property as well, breaking current forms of iterating over the DOM elements.
At least within jQuery you’ll see a number of cases where, instead of doing the normal array traversal, we do the following in order to work around the issue:
for ( var i = 0; elems[i]; i++ )
{
// Do stuff with elems[i]
}
Browsers add names and ids of form controls as properties to the form. This results in the properties of the form being replaced.
Browsers also may add names and id’s of other elements as properties to document, and sometimes to the global object (or an object above the global object in scope). This non-standard behavior can result in replacement of properties on other objects. The problems it causes are discussed in detail.
A problem occurs when a form control’s name conflicts with the a property name of the FORM object. For example:
<form action=""> <input type="text" name="name"> <input type="submit" name="submit"> </form>
The element with name=”name” replaces the value of the FORM’s name property. A similar type of conflict happens with the FORM’s submit method and the element with name=”submit’. Which will win?
In most cases, the form control wins and the FORM’s property is replaced to have the value of the form control, or, if more than one form control has that name, the FORM’s property is replaced with a NodeList containing those form controls.
However, in some cases, the FORM’s property is not replaced to have the value of the form control with the same name. The examples in this page show that the result depends on the browser, the property, and how the control is added.
How is a Form Like a Collection?
The DOM 1 specification states:
The FORM element encompasses behavior similar to a collection and an element. It provides direct access to the contained input elements as well as the attributes of the FORM element. [DOM1]
“Similar to a collection?” What collection? Similar in what way? The only standardized feature that is “similar to a collection” is the length property.
In most browsers, a form has direct access to all named form controls, (except for input type=”image”), not just input elements.
Accessing controls as named or indexed properties is not standardized by the w3c DOM specification. It is a holdover feature from by Netscape Navigator that was copied by Internet Explorer and since copied by every other major browser vendor.
How the Nonstandard Form-as-a-Collection Really Works
Controls may be accessed directly off the form.
Given the following form:
<form action=""> <input type="text" name="a"> </form>
The input name=”a” may be retrieved in one of two ways, from the form or from the elements collection.
The elements Collection (standard)
document.forms[0].elements[0];
document.forms[0].elements.a;
The control may be accessed directly off the form:
Directly from the Form (nonstandard)
document.forms[0][0];
document.forms[0].a;
Accessing a named control directly off the form can have undesirable results.
In a few browsers, removing a named control from the form will leave the named control as a property of the form.
Accessing a named element property directly from the form may cause the property to remain on the form, even after the element has been removed from the DOM.
Similarly, when a named FORM element is removed from the document some browsers will keep that form name as a property of the document. Accessing named FORM controls as properties of the document is also nonstandard.
Standard
document.forms.myform;
Nonstandard
document.myform;
A form’s indexed form control properties may also appear out of order. This behavior would not be compliant for the elements collection, it does not violate any standard for the form-as-a-collection (because there is no standard).
Form-as-a-collection is unreliable and therefore should never be used.
The form.elements collection provides programmers a safe place to access form controls.
The elements collection contains the following properties: length, item, namedItem, plus an ordinal index property for each form control (excepting input type=”image” controls). Some browsers also have a tags property on the elements collection.
Any test that takes less than 15ms will always round down to 0ms in these IE8, Opera, and Webkit browsers. If you attempt to query for an updated time it’ll always be rounded down to the last time the timer was updated (which, on average, will have been about 7.5 milliseconds ago), so it becomes impossible to determine how much time the tests are taking with consistently zeroed out results.
alertLink.num = 2;
// Use "this" to reference a variable as a context property of the caller object
alertLink.onclick = function() { alert('EVEN: '+ this.num); };
// Use a function literal to pass a variable as a function argument
var act = function(localVar)
{
alertLink.onclick = function() { alert('EVEN: '+ localVar); };
};
act(alertLink.num);
if(!document.getElementById)
{
return false; // and thus kill all the script
}
else
{
document.documentElement.className = 'js'; // apply JavaScript only style rules
}
document.getElementsByTagName('html')[0].className = 'js'; // Add JS class to the HTML tag
Use a CSS rule to hide JavaScript content by default
[sourecode language="css"]
.js #flash { display: none; } // Hide content that requires Javascript
[/sourcecode]
Functions have scope, so conditional statements don’t prevent closures
if (0)
{
alert("No");
function really() { alert("Yes, really"); }
// Avoid the closure by using a function literal to redefine the function
var really = function() { alert("Yes, really"); }
}
really(); // Alerts "Yes, really"
Check the load status of an on-demand javascript include using readystate or onload for IE
js.onreadystatechange = function ()
{
if (js.readyState == 'complete')
{
alert('JS onreadystate fired');
}
}
js.onload = function ()
{
alert('JS onload fired');
}
return this; //Return the value of the function back to its caller
To lazy load a script hidden in a comment block, give it an ID, find the DOM element, strip out the comment block, and eval() the code. Lazy loading this way allows for synchronous fetching of multiple scripts since they are already on the page
The arguments object contains each argument passed into the function.
It can have its individual items changed, but not removed.
Any items added via bracket notation instead of dot notation don’t change the arguments.length property.
A collection of DOM elements (called a NodeList, such as getElementsByTagName() or document.images)
can’t be manipulated like an array, because it isn’t one.
These return HTMLCollection object, but HTMLCollection inherits from NodeList
NodeLists are dynamic in nature whereas Arrays and arguments are static
Each time a NodeList is accessed, it actually queries the document for all matching nodes.
As a result, an infinite loop occurs when traversing through a nodeList using a for statement
for (var i=0; i < document.images.length; i++)
{
document.body.appendChild(document.createElement(" img"));
}
It’s recommended to store the length of a NodeList in a separate variable for use inside of a for loop. For example:
for (var i=0, len=document.images.length; i < len; i++)
{
//work with images here
}
An Array object can be modified at any time and contains specific methods
You can convert it to an array like this:
// Call the array slice method to convert a nodeList to an array
var nodeArray = [].slice.call(nodeList, 0);
// Loop through the nodeList to copy each index to an array
function collectionToArray(collection)
{
var ary = [];
for(var i=0, len = collection.length; i < len; i++)
{
ary.push(collection[i]);
}
return ary;
}
Remember to put a break after each case statement, unless you intend to group multiple cases to one action, like this:
switch(i)
{
case 3:
case 6:
dosomething()
break;
case 1:
case 4:
doSomethingElse();
break;
default:
doSomethingElseAllTogether()
}
The exception to JavaScript’s loose type checking is comparing case values to the value of the switch statement. For the case to be a match, the data types must match. Switch statements include strict equality checking (===) instead of loose equality checking (==) for case comparisons.
y = 5;
switch(y)
{
case '5':
alert("hi"); // this alert will not show since the data types don't match
}
Despite the fact that JavaScript has no notion of constants, you can use DOM constants or string literals in case statements to emulate enumeration:
switch(node.type)
{
case Node.ELEMENT_NODE: blah blah
case Node.ATTRIBUTE_NODE: blah blah
case Node.TEXT_NODE: blah blah
}
Add an ID to a script tag to dynamically replace it with another source file
<script id="external_script"></script>
<script>
document.getElementById('external_script').src = 'http://cross.domain.com/other.js';
</script>
This page contains the following JavaScript:
<script>
var domscript = document.createElement('script');
domscript.src = "inex-js.php";
domscript.text = "document.getElementById('text1').innerHTML += '<br>external script: foo = ' + foo;";
document.getElementsByTagName('head')[0].appendChild(domscript);
</script>
<script>
foo = 2;
document.getElementById('text1').innerHTML += '<br>internal script: foo = ' + foo;
</script>
The external script, inex-js.php, has a 2 second delay in the response. It contains the following code:
foo = 1;
var scripts = document.getElementsByTagName("script");
var cntr = scripts.length;
while (cntr)
{
var curScript = scripts[cntr-1];
if ( -1 != curScript.src.indexOf('inex-js.php') )
{
eval( curScript.innerHTML );
break;
}
cntr--;
}
Things to note:
The inline script code that’s dependent on the external script is attached to the dynamic script DOM element via domscript.text.
Inside the external script, index-js.php, the inline script code is evaled at the end of the file.
This achieves the goal of not executing the inline code until the external script is loaded, but doing it as soon as possible after the external script is loaded.
JavaScript is strongly (i.e. objects always have a certain type and different treatment requires casting them to another type) but dynamically (i.e. variables are untyped, it’s values that have types) typed without type annotations. Types are run time changing as well. Which leads to the fact that you have very little type information at compile time.
Encapsulation in JavaScript is pretty simple. The only black box construct is lexical scoping of functions.
There is only one way of making data private, and that is the closure. In the case of object oriented code, you have the constructor closure. That’s the encapsulating unit, that’s why it’s such a pain to get a super “class” and a sub “class” to share private members.
Any logic other than defining the methods of an object can be contained in a constructor method with an explicit naming convention so that arguments can be passed to each instance of the superclass constructor. This technique makes object property lookup and storage more efficient since all data properties (and no method definitions) exist in only the topmost object in the prototype chain. Instance variables defined by even the deepest superclass methods are still created in the topmost object only.
Static methods and attributes can be defined using anonymous functions instead of the function prototype
You can make recursive calls within an anonymous function using arguments.callee,
which is a reference to the currently executing function (even if it has no name).
You can pass parameters to a self-invoking function in order to use public data
in a private scope
(function i_am_anonymous(me)
{
alert(me);
}
('one'));
You can even get really crazy and create anonymous (private) functions that self
invoke an object literal, hence making private object literals…
(function ()
{
var the =
{
thing:’scary monster’,
attach:function()
{
alert(the.thing);
}
}
the.attach();
}
())
References
Applying Object Oriented Principles in JavaScript
Private Variables in JavaScript
On modifying prototypes of JavaScript built-ins
Associative Arrays and JavaScript
Private Static Members in JavaScript
JavaScript Private Static Members
Create Advanced Web Applications With Object-Oriented Techniques
JavaScript Private Static Members
The jQuery JavaScript Implementation Quality Metric
http://www.mactech.com/articles/mactech/Vol.16/16.06/SolitaireinJavaScript/index.html
http://www.kirupa.com/developer/oop/AS1OOPControl4.htm
http://dean.edwards.name/weblog/2006/11/hooray/
http://lazutkin.com/blog/2009/mar/1/javascript-explained/
Blog at WordPress.com. | Theme: Pool by Borja Fernandez.
Entries and comments feeds.