'' // Empty string
0 // Number
undefined // Tricky
null // Obvious
false // Boolean
NaN // Weirdness
var name; // Undefined
if (name) { ... } // Never runs
name = 'Big Mike';
if (name) { ... } // Runs
NaN !== NaN // This is the world we live in
isNaN('37') // False: can be parsed to a number
isNaN('asdfasdf') // True: cannot be parsed to a number
isNaN('') or isNaN(' ') // False: can be parsed as a number?
var secretDigit; // Undefined
if (secretDigit === undefined) { ... } // Maybe do some init of 'secretDigit'
undefined = 'I am no longer empty' // Bad, bad man
if (typeof secretDigit === 'undefined') { ... } // Much better
Pretty much everything else
' ' // True: non-empty string
'null' // True: non-empty string
'0' // True: non-empty string
-1 // True: non-zero number
'undefined' // True: non-empty string
'false' // True: non-empty string
[] // True: Empty Array (use length property)
{} // True: Empty Object (more complicated)
function isEmpty(obj) {
for(var prop in obj) {
if(obj.hasOwnProperty(prop)) return false;
}
return true;
}
var obj = {};
isEmpty(obj) // True
0 === '0' // False
0 == '0' // True
if (0) // Falsy
if (!0) // True
if (!1) // False
var label = ''; // Falsy
var opposite = !label; // true
var bool = !!label; // false
something || somethingElse || somethingElseElse // Evals left to right until something is true
something && somethingElse && somethingElseElse // Evals left to right until something is false
(isTrueOrFalse) ? something : somethingElse // Basic ternary
(ifTrueOrFalse) ? (something) ? minor : major : somethingElse // Nested
(ifTrueOrFalse) // More legible?
? (something)
? minor
: major
:somethingElse
if (varName === 'foo' || varName === 'bar') {...} // Old and busted
switch (varName) { // More or less the same as the if method
case 'foo':
case 'bar':
...
break;
}
if (/^(foo|bar)$/.test(varName)) {...} // Better
if (({ foo: 1, bar: 1 })[varName]) {...} // Mo Betta, Truthy
if (({ foo: 0, bar: 0 })[varName]) {...} // Mo Betta, Falsy
if ((function(varName){...}())) {...} // Questionable
if (someFunction(varName)) {...} // Better
var cached = someFunction(varName);
if (cached) {...} // Better still (if applicable)
var cached;
if (cached = someFunction(varName)) {...} // Fun for the whole family
// Bad
function doinSumpin() {
$('.someClass').each(function(){
$(this).addClass('holas');
$(this).bind('mouseover', function() {
$(this).find('.child').bind('mouseover', function() {
$(this).bind('click', function() {
doSumpin(); // Incites rage
});
});
});
});
}
// Better
function doinSumpin() {
$('.someClass')
.addClass('holas')
.bind('mouseover', onHover);
});
}
function onHover(e) {
$(e.target).find('.child').bind('mouseover', onChildHover);
}
function onChildHover(e) {
$(e.target).bind('click', onClick);
}
function onClick(e) {
doSumpin(); // Much less anger
}
Variable scope is defined at function level
Scopes are inherited
function() {
var i = 1;
function() {
var j = 2;
return i + j; // returns 3
}
}
// array lengths
var l = arr.length;
// properties
var sumpin = object.subObject.sumpin;
// responses from functions
var isSomething = testSomething();
function() {
if (false) var x = 0;
return;
var y = 0;
}
might as well be:
function() {
var x, y;
if (false) x = 0;
return;
y = 0;
}
functions remember their environment
// Might be confusing if done accidentally
function counter() {
var i = 0,
addMe = function() {
return i + 1;
};
i++;
return addMe;
}
var countMe = counter();
countMe(); // returns 2
// Mini-module pattern
(function(){
var foo = 'bar',
showMe = function() {
return foo;
};
window['showMe'] = showMe;
}());
showMe(); // returns 'bar'
// feature detection
var canSupport = (function(d){
if (!d['querySelectorAll']) return false; // supports qsa
if (!d['addEventListener']) return false; // support w3c style event listeners
if (!d['isSameNode']) return false; // supports isSameNode method
if (!'onhashchange' in window) return false; // supports hash change event
return true;
}(document));
if (canSupport) {...}
Avoid the global namespace
// Define
var plugin = {
variableName: '',
init: function() {...},
doSomething: function() {...}
};
// Usage
plugin.init();
plugin.doSomething();
// Define
(function(context) {
var privateVar = 'keep it on the down low',
privateMethod = function() {...},
plugin = {
publicVar: 'whip it out in public',
publicMethod: function() {
return privateVar;
}
};
context['plugin'] = plugin;
}(namespace));
// Usage
plugin.publicVar // 'whip it out in public'
plugin.privateVar // undefined
plugin.privateMethod() // undefined
plugin.publicMethod() // returns 'keep it on the down low'
1
Element,2
Attribute and3
Text are most common.<ul id="parent">
<li>List item 1</li>
...
</ul>
var list = document.getElementById('parent') // sets node to element with id of 'parent' (ul)
list.nodeType === 1 // it's an element (ul)
list.childNodes // array of immediate descendent nodes (all types)
list.childNodes[0].nodeType === 3 //the line break and tabs/spaces before the first list item
var listElems = [];
for (var i = 0; i < list.childNodes.length; i++) {
if (list.childNodes[i].nodeType === 1) listElems.push(list.childNodes[i]);
}
// jQuery Style
$('#parent').children('li')
// Old school method
if (addEventListener && document.addEventListener('DOMContentLoaded')) {
... // listen for that event
} else if (attachEvent && document.attachEvent('onreadystatechange')) {
... // listen for event and test if it starts with a 'c' (for complete)
} else {
... // try to scroll the page to see if it will let you. if it will, then DOM is ready
}
// DOM ready: jQuery style
$(document).ready(function() {
... // Do something with the DOM
});
// better yet
var plugin = {
init: function() {...},
somethingElse: function() {...}
};
$(document).ready(plugin.init)
All jQuery events (including DOMReady) queue up, first in, first out.
$('.class')[0]
first DOM element in collection
$('.class')[4]
fifth DOM element in collection
var $links = $('a') // jQuery Collection
var link = $('a')[0] // DOM Element
// jQuery method
$links.addClass('some') // adds class to all the links in the collection
link.addClass('some') // fails, not a jQuery collection
// DOM property
$links.href // fails, not a DOM node
link.href // returns the href attribute value
$links.eq(0).attr('href') // way to get it with jQuery (obviously slower)
<div class="some"></div>
$('.some').addClass('test').text('Test text').append('<p>Child</p>');
<div class="some test">
Test text
<p>Child</p>
</div>
#
for Id.
for classspace
for descendant (not necessarily direct)
>
for direct descendent:first-child
,
:hidden
, :input
, etc
[contenteditable]
,
[type="text"]
, etc
// sample selectors
$('#myId') // get the node with that id
$('.className') // get any node that has that class
$('div.className') // get any div that has that class. more efficient
$('ul li') // get the lis that are under uls, including nested ones
$('ul > li') // get the lis that are immediate children of uls
$(':input') // gets all inputs, textareas, selects & buttons
$('[type="email"]') // get all nodes with a type of email (new HTML form field type)
// selectors with context (& cached)
$('#myForm > :input') // slow
var $form = $('#myForm');
$form.find('> :input') // gets there, could be faster
$form.children(':input') // super fast
$(':input', $form) === $form.find(':input')
$('.data td.domain') // faster
$('table.data .domain') // slower
$.get
, $.getJSON
, $.post
, etc
are more convenient
$.ajax
is more flexible// convenience methods
$.get('/url', { requestParam: value }, function(d) {
... // success callback, d = response
});
$.getJSON('/url', { requestParam: value }, function(d) {
... // success callback, d = parsed json
});
$.post('/url', $form.serialize(), function(d) {
... // success callback, d = response
});
// Notable methods/properties
$.ajax({
url: '/url',
type: 'get', // you know as method, head and preflight work as well
data: { request: param }, // or post payload, also, request=param as a string works
dataType: 'json', // type of response data. Tries to parse based on type
success: function(){}, // do something with the response
error: function() {}, // This is an http error, not a data error (think 404)
beforeSend: function() {
$throbber.show();
},
complete: function() { // fires on success and error
$throbber.hide();
}
});
// Applies to EVERY ajax request
$.ajaxPrefilter('json', function(options, orig, jqXHR) {
if (options.crossDomain && !$.support.cors) return 'jsonp'
});
// fetch the data
$.ajax({
url: 'http://somewhere.else',
dataType: 'json',
success: doSumpin
});
function doSumpin(d) {
... // Do something with that ajaxy goodness
}
// Use auth
$.ajax({
...
xhrFields: {
withCredentials: true // Sends cookies, auth & ssl certs
}
})
// XHR
var get = $.get('/url');
get.done(function(d) { /* Do something with d */ });
...
get.done(function(d) { /* Do something else */ });
// Roll your own
var dfd = $.Deferred(); // setup deferred obj
function doSumpin(data) {...} // Do something with the data that was resolved
dfd.done(doSumpin) // setup doSumpin to fire once the deferred is resolved
...
dfd.resolve('value'); // resolve the deferred, triggering the done
// Chaining deferreds
var get1 = $.ajax( '/url1', { dataType: 'json' } ),
get2 = function(data) {
return $.ajax( '/url2', { data: { user: data.userId } } );
},
chained = get1.pipe(get2);
chained.done(function( data ) { /* data retrieved from url2 as provided by the first request */ });
bind
and trigger
vs. helpers
(click(function)
and click()
,
respectively)
.one
listens for an event once and then removes itself
hover
triggers on mouseenter and mouseleave. Can
provide a handler for each or 1 that triggers on both events
toggle
takes multiple handlers. For each success click
event, fires next one in the list and then recycles
$('a').bind('click.namespace', function(){ // namespace is an additional hook to get at event listener
...
});
$('form').bind('submit', function() {
... // do some of that fun filled form validation we all know and love
});
$('.nav-item').hover(function() {
... // mouseenter function
}, function() {
... // mouseleave function
});
$('.nav-item').hover(function() {
... // one function to rule them all (and handle both events)
});
$('button').toggle(function() { // alternate clicks between the callbacks
... // handle first click
}, function() {
... // handle second click
});
trigger
triggerHandler
// Simulate an event
$('.some').click();
$('.some').trigger('click');
// call the event handler
$('.some').triggerHandler('click');
<ul> ↑
<li> ↑
<a> (click)
$('a').bind('click', function() { /* fires FIRST! */ });
$('li').bind('click', function() { /* fires second */ });
$('ul').bind('click', function() { /* fires third */ });
preventDefault()
stops the default behavior (link
redirection, form submitting, etc)
stopPropagation()
stops the bubbling of the event up
the DOM
return false
does both// Canceling behaviors
$('form').bind('submit', function(e) {
e.preventDefault(); // stop form from submitting
});
$('a').bind('click', function(e) {
e.stopPropagation(); // Stop click event from bubbling (rarely wanted)
});
$('a').bind('click', function(e) {
return false; // stops bubbling and prevents default (usually not a good idea)
});
undelegate
and die
have to use the same
selector/elem they were bound with
// Non-delegated event listeners
$('a').each(function() {
$(this).bind('click', function(){...}); // Each one of these has a memory footprint, 500 links = 500 event listeners
});
$('a').bind('click', function() {...}); // Same thing
// Delegated event listeners
$('a').live('click', function() {...}); // Delegated, 1 event listener
$(document).delegate('a', 'click', function() {...}); // Same thing
<document> ↑
<body> ↑
<a> (click)
// Undelegate
$(document).undelegate('a', 'click');
e.data
// Busted
for (var i = 0; i < 5; i++) {
$('.some').bind('click', function() {
alert(i); // 5 alerts, all of them say 5
});
}
// Works
for (var i = 0; i < 5; i++) {
$('.some').bind('click', { i: i }, function(e) {
alert(e.data.i); // 5 alerts, in counting order from 0 to 4
});
}
$('.some').bind('dinner.bell', function() {
alert('yay!');
});
$('.some').triggerHandler('dinner');
var arr = [2, 3, 4, 5];
$.inArray(2, arr) // returns 0
$.inArray(7, arr) // returns -1
~$.inArray(7, arr) // return 0 (if you don't need index, just testing if is in array)
true
makes it a deep (recursive) copy// Merge objects
$.extend(origObj, newObj) // merge newObj into origObj
var newObj = $.extend({}, origObj) // newObj is a copy of origObj
$.extend(true, origObj, newObj) // recursively merges newObj into origObj
data-
attributes (coerces values)// Usage
$('#some').data('key', 'value'); // Setter
$('#some').data('key') // Getter: returns 'value'
var pluginData = {
foo: 42,
bar: 'holas'
};
$('.some').data('pluginName', pluginData);
$('.some').data('pluginName').bar // returns 'holas'
Check the lengths of jQuery collections before running 3rd party plugins
$('.some').pluginName() // Can be expensive
// Plugin code
(function($) {
$.fn.pluginName = function(options) {
if (!this.length) return this; // this is the magic line
...
};
...
})(jQuery);
// Safer usage
var $somes = $('.some');
if ($somes.length) $somes.pluginName();
Use when working with DOM elements/events, especially collections
// Define
(function($) {
$.fn.pluginName = function(options) {
if (!this.length) return this;
var opts = $.extend({}, $.fn.pluginName.defaults, options);
// Global init stuff goes here
this.each(function() {
var $this = $(this);
// Per node code goes here
});
return this;
};
function privateFunction() {...}
$.fn.pluginName.publicFunction = function() {...};
$.fn.pluginName.defaults = {...};
})(jQuery);
// Usage
$('.someClass').pluginName(optsObj);
Use when state is needed
// Define
(function($){
$.widget('wsm.pluginName', {
options: {...},
_create: function () {
this; // plugin
this.element; // jQuery collection of the single element it was called on
},
_private: function () {...},
public: function () {...},
destroy: function () {
// Take out what you brought in
$.Widget.prototype.destroy.call(this);
}
});
})(jQuery);
// Usage
$('.someClass').pluginName(optsObj);
TL;DR: Use jQuery when available
Unless you know the feature is supported by all the browsers we have to support, use jQuery. It ensures compatible fallback to all the browsers it supports (which supercedes our own support matrix).
Find out if what you are doing can be done with straight JavaScript consistently, cross browser and with minimal additional abstractions/forking. Only then can you make the educated decision to use it or not.