Understanding the environment
JavaScript basics
Understanding the DOM
Basic performance
Overcoming browser differences
Global objects - types
Array
Boolean
Date
Function
Number
Object
RegExp
String
Array
var foo = new Array();
var foo = new Array(10);
var foo = new Array('a', 'b', 'c');
var foo = [];
Just use literal notation
Arrays are not associative, use Objects
You can dynamically increase the size of an array
var foo = Array(5);
foo[999] = 'large array';
Boolean
0, -0, null, false, NaN, undefined and empty strings are all converted to false
everything else is converted to true
var asFunction = Boolean(false); // == false
var asConstructor = new Boolean(false); // == true
Let's just pretend Boolean doesn't exist
Use ! and !! to convert to booleans
var isFalse = !'false';
var isTrue = !!'false';
Date
var date = new Date();
var date = new Date(1234567890);
var date = new Date('December 25, 1995');
var date = new Date(1995, 11, 25);
Date.parse() converts a string to a timestamp
Months range from 0 to 11
Probably won't need dates too often
Function
var sum = new Function('a', 'b', 'return a + b;');
var sum = function(a, b) { return a + b; };
function sum(a, b) { return a + b; }
Functions define scope
Functions can be nested in other functions
Functions can be passed as parameters to other functions
We'll cover functions much more later on
Number
var num = new Number('5.2');
var num = Number('5.2');
var num = 5.2;
Use parseInt() and parseFloat() to convert to numbers
Number.MAX_VALUE, Number.MIN_VALUE
NaN, isNaN()
Object
var obj = new Object();
var obj = {};
obj.foo = 'bar';
obj['foo'] = 'bar';
Just use literal notation
Bracket notation and dot notation are equal
RegExp
var re = new RegExp('foo', 'i');
var re = /foo/i;
/bar/i.test('fooBARbaz'); // true
/bar/i.exec('fooBARbaz'); // ["BAR"]
Just use literal notation
Supports UTF-8, but not UTF-16
String
var str = new String('string');
var str = String('string');
var str = 'string';
Just use literal notation
.match() and .search() are similar to RegExp's .exec() and .test()
String operations create new strings
var str = 'string';
str.replace('i', 'o'); // strong
alert(str); // string
Checking types
.constructor returns a global object
global objects are properties on the window object
.constructor doesn't work cross-window
typeof returns a string, not reliable for all types
if ("foo".constructor == String) {
alert("is a string");
}
if (typeof "foo" == "string") {
alert("is a string");
}
Checking types
Object.prototype.toString works better for most situations
can also check for methods you know exist on specific types
if (Object.prototype.toString.call("foo")
== "[object String]") {
alert("is a string");
}
if ("foo".substr) {
alert("is a string");
}
Checking types
Array: jQuery.isArray()
Boolean: typeof
Date: typeof
Function jQuery.isFunction()
Number: typeof
Object: currently being debated
RegExp: typeof
String: typeof
Variable scope
JavaScript has lexical scoping
Variable scope cannot change based on execution
Functions are the only thing that create scope
Control structures and blocks do not create scope
Nested functions can see variables defined in outer functions
Exceptions
try...catch blocks can be used to handle exceptions
native exceptions vary from browser to browser
all browsers have a message property on their exceptions
try {
window.missingFunction();
} catch (e) {
alert(e.message);
}
Exceptions
You can throw (and rethrow) exceptions yourself
The exception can be anything (string, number, object, etc.)
try {
throw "something went wrong";
} catch (e) {
alert(e);
}
Understanding the DOM
The Document Object Model (DOM) is an API for HTML and XML documents.
It provides a structural representation of the document, enabling you to modify its content and visual presentation.
Essentially, it connects web pages to scripts or programming languages.
https://developer.mozilla.org/En/DOM
Traversing and modifying the DOM
.childNodes, .firstChild, .lastChild, .parentNode
.nextSibling, .previousSibling
.appendChild(), .cloneNode(), .insertBefore()
.removeChild(), .replaceChild()
Notice there is no .insertAfter() or .prependChild()
Extending the DOM
Easy to implement .prependChild()
document.body.prependChild = function(newChild) {
if (this.childNodes.length) {
this.insertBefore(newChild, this.firstChild);
} else {
this.appendChild(newChild);
}
};
Why would the DOM not implement such a simple method?
Because we don't actually need all of this logic
Extending the DOM
document.body.prependChild = function(newChild) {
this.insertBefore(newChild, this.firstChild);
};
see demo
The DOM's cleverness can make it very confusing
<div><p id="foo">foo<⁄p></div>
<div><p id="bar">bar<⁄p></div>
<div><p id="baz">baz<⁄p></div>
var foo = document.getElementById('foo');
var bar = document.getElementById('bar');
foo.insertBefore(bar);
Extending the DOM
Not so useful on an element-by-element basis
Can't modify all elements in a cross-browser manner (yet)
Extending the DOM
function prependChild(elem, newChild) {
elem.insertBefore(newChild, elem.firstChild);
}
Adding utility methods solves the problem
Some libraries wrap DOM elements to provide new functionality
jQuery(elem).prepend(newChild);
Exercise
Implement insertAfter(elem, newSibling)
Implement wrap(elem, tagName)
Implement outerHtml()
Circular references
var elem = document.createElement('div');
// JavaScript points to DOM
var leaker = {
elem: elem
};
// DOM points to JavaScript
elem.expando = leaker;
JavaScript has a different garbage collector than the DOM
Circular references between JavaScript and the DOM can cause memory leaks
see demo
jQuery.data
var elem = document.createElement('div');
var leaker = {
elem: elem
};
jQuery(elem).data('expando', leaker);
jQuery.data solves the expando problem
Creates a hash of data and gives the element a unique id
see demo
Exercise
Implement jQuery.data() as data(elem, property, [value])
Performance
"If JavaScript were infinitely fast, most pages would run at about the same speed" - Crockford
Modifying the DOM can be costly
Page reflows are expensive
Performance
var list = document.getElementById('perf-list1');
var newItem;
for (var i = 0; i < 5; i++) {
newItem = document.createElement('li');
newItem.innerHTML = 'new item';
list.appendChild(newItem);
}
run
Touches the DOM for each new item
Performance
var list = document.getElementById('perf-list2');
var fragment = document.createDocumentFragment();
var newItem;
for (var i = 0; i < 5; i++) {
newItem = document.createElement('li');
newItem.innerHTML = 'new item';
fragment.appendChild(newItem);
}
list.appendChild(fragment);
run
Touches the DOM once
All real work is done on nodes disconnected from the DOM
.innerHTML
Using .innerHTML as a setter creates a lot of work for the browser
However, it only touches the DOM once
Can cause problems with existing events/data bound to elements
.innerHTML
<ul id="list">
<li id="clickable">click me</li>
</ul>
window.onload = function() {
document.getElementById('clickable')
.addEventListener('click', function() {
alert('clicked!');
}, false);
};
document.getElementById('list')
.innerHTML += '<li>new item</li>';
The window object
.alert(), .confirm(), .prompt()
.back(), .forward()
.moveBy(), .moveTo(), .resizeBy(), .resizeTo()
.open(), .opener
.scroll(), .scrollBy(), .scrollTo(), .scrollX, .scrollY
.status
Timeouts and intervals
Use setTimeout() to delay execution
setTimeout(function() {
alert('I was delayed!');
}, 2000);
run
Use setInterval() to execute code repeatedly
If the execution takes a long time you may want to use recursive timeouts
window.screen
.width, .height
.left, .top
.colorDepth, .pixelDepth
window.location
Generally acts as a string, but actually an object
.hash
.host
.hostname
.href
.pathname
.port
.protocol
.search
window.location
.assign(url)
.reload(forceget)
.replace(url)
Does not affect session history (can't use back button)
.toString()
Only needed when performing string operations
Finding elements
document.getElementById()
document.getElementsByName()
document.getElementsByTagName()
document.getElementsByClassName()
document.querySelector()
document.querySelectorAll()
Overcoming browser differences
function bind(elem, type, handler) {
if (/msie/i.test(navigator.userAgent)) {
elem.attachEvent('on' + type, handler);
} else {
elem.addEventListener(type, handler, false);
}
}
Overcoming browser differences
function bind(elem, type, handler) {
if (elem.attachEvent) {
elem.attachEvent('on' + type, handler);
} else {
elem.addEventListener(type, handler, false);
}
}
Feature detection is good, but can be tricky
Sometimes browser sniffing is necessary, but very rarely
jQuery.support
Exercise
Determine if tbody elements are automatically inserted into newly created tables, without browser sniffing
Make setTimeout() accept parameters
Create a page which opens another window and allow them to talk to each other