Hello, this is a website that covers my interest in web development topics, especially client-side stuff, including JavaScript and the DOM. This site is really meant as place where I can put up notes that I can look up whenever I'm on another computer, but since this is a public web site, I'm adding this intro.

I also spend some time working on MDC (a.k.a. Devmo), again mostly on the JavaScript and DOM sections.

Other interests include: games, piano, some other stuff, gerbils.

Contact:

Last updated: 12/29/05

I've been pretty busy lately, so I haven't gotten much chance to work on devmo.

Hmm, my antispam script on this page is probably overkill. I doubt spammer robots execute scripts. Oh well, better safe than sorry...

Tests

Scripts

Notes

Various notes I've been taking. Sometime in the (far) future, I'll organize it.

Disclaimer: I don't claim that any of the following is verified. Some of it may be outdated.

results from: Mozilla Firefox 1.5, Internet Explorer 6.0 (SP2), Opera 8.51 on Win XP SP2
	(can't test Safari since I don't have a Mac)
---
note:
	'==' means "same speed"
	'~>' means "slightly faster"
	'>' means "faster" not "slower"
	'>>' means "much faster"
	'>>>' means "exponentially faster"
note: I didn't record how fast each browser is relative to each other,
	since that is useless information for cross-browser optimization. But if you're interested:
	- the performance of the JS engines in all three are all comparable,
		though overall Opera is the fastest
		- Opera calls functions and concats strings much faster than the others 
	- for DOM manipulation and access, Opera was by far the fastest, with Fx 2nd, and IE last
* innerHTML vs. W3C methods:
	- innerHTML is faster than appendChild/createElement
	- innerHTML is slower than appendChild/createTextNode
	- innerHTML is thus slower than appendChild
	- IE: appendChild and removeChild are relatively slow compared to createElement/cloneNode
	- Fx and Opera: appendChild and removeChild are relatively fast compared to createElement/cloneNode
* HTML DOM vs. XHTML/XML DOM:
	- case sensitivity:
		- HTML: tag names are returned uppercased (e.g. 'title' => 'TITLE')
		- HTML: attribute names are returned lowercased (e.g. 'ID' => 'id')
		- HTML: case-insensitive attribute values are returned lowercased (e.g. input's type attribute)
		- HTML: tag names and case-insensitive attribute values can be passed or set either upper- or lower-cased
		- XHTML: everything is case-sensitive
	- XHTML: doesn't support document.write or document.writeln
	- IE: doesn't support XHTML
* "ignorable" whitespace and comments:
	- "ignorable" whitespace or text nodes = whitespace-only text nodes that have no effect on the
		default presentation of the HTML document
	- IE:
		- DOM does not include comment nodes and "ignorable" text nodes before the first
			non-comment non-"ignorable"-text node
		- DOM does not include "ignorable" text nodes between element nodes
	- Fx and Opera: includes all "ignorable" whitespace
///////////////////////////////////// need more tests /////////////////////////////////
* accessing event object:
	(1) JS: some_element.onclick = foo;
		- Fx and Opera: event object is passed as sole parameter to foo
		- IE: event object is stored in window.event and NOT passed to foo
		- this object is the target of the event object
			(IE: event.srcElement; Fx and Opera (W3C): event.target)
		- addEventListener (Fx and Opera): event object is passed as sole parameter to addEventListener listeners
		- attachEvent (IE): event object is passed as sole parameter to attachEvent listeners
			and also stored in window.event
	(2) HTML: <some-element onclick="foo()">
		- IE:
			- following function is assigned as onclick listener:
				function anonymous() {
					foo();
				}
			- event object is stored in window.event and NOT passed to foo
		- Fx and Opera:
			- following function is assigned as onclick listener:
				function anonymous(event) {
					foo();
				}
			- event object is passed to this function but NOT passed to foo
			- Fx: function name is actually "onclick", but that's irrelevant
		- in foo, this object refers to window object (and thus useless)
	(3) HTML: <some-element onclick="foo(event)">
		- IE:
			- following function is assigned as onclick listener:
				function anonymous() {
					foo(event);
				}
			- event object is stored in window.event
			- identifier event resolves to window.event and is then passed to foo
		- Fx and Opera
			- following function is assigned as onclick listener:
				function anonymouse(event) {
					foo(event);
				}
			- event object is passed to this function as identifier event,
				which is then passed to foo
		- in foo, this object refers to window object (and thus useless)
		- other parameters can be passed, e.g. <some-element onclick="foo(event, a, b, c)">
	- (1) and (3) are cross-browser
	- Fx: event object is truly passed down each listener during event propagation
	- IE: new event object is created for each listener during event propagation (verify this)
* computed style:
	- the computed style of an element is the combination of:
		- the element's style attribute
		- style rules from style elements that apply to the element
		- style rules linked external style sheets that apply to the element
	- IE: element.currentStyle
	- Fx and Opera: window.getComputedStyle(element, null)
	- cross-browser: element.currentStyle || window.getComputedStyle(element, null)
	- the returned object has the same properties names as element.style (e.g. computedStyle.display)
* "visibility: hidden" vs. "display: none":
	- "visibility: hidden"
		- offsetTop, offsetLeft:
			- Fx: (computed)
			- IE: (computed)
			- Opera: (computed)
		- offsetHeight, offsetWidth:
			- Fx: (computed)
			- IE: (computed)
			- Opera: (computed)
		- computedStyle.top, computedStyle.left:
			- Fx: (computed)
			- IE: (computed)
			- Opera: (computed)
		- computedStyle.height, computedStyle.width:
			- Fx: (computed)
			- IE: "auto"
			- Opera: (computed)
		- cannot be tabbed to
	- "display: none":
		- offsetTop, offsetLeft:
			- Fx: 0
			- IE: (computed)
			- Opera: 0
		- offsetHeight, offsetWidth:
			- Fx: 0
			- IE: 0
			- Opera: 0
		- computedStyle.top, computedStyle.left:
			- Fx: "0px"
			- IE: (computed)
			- Opera: (computed)
		- computedStyle.height, computedStyle.width:
			- Fx: "auto"
			- IE: "auto"
			- Opera: "0px"
		- cannot be tabbed to
* IE: setAttribute and getAttribute can use both "class" and "className" as parameters
	to refer to the class attribute
* setting event listener attributes:
	- in IE, a function must be passed: setAttribute('onclick', function() { foo(); })
	- in Fx and Opera (in accordance with W3C spec), a string must be passed: setAttribute('onclick', "foo();")
* IE bug: in a try/catch/finally statement, if an exception is thrown in the catch block, finally block does not execute
* creating new text nodes (createTextNode) is slightly faster than cloning empty text nodes and setting them
* creating elements:
	(1) document.createElement('div')
	(2) assume the following is defined:
			var div = document.createElement('div');
		div.cloneNode(false)
	(3) assume the following is defined:
			var div = document.createElement('div');
		div.cloneNode(true)
	- (2) and (3) are faster than (1)
	- (2) is as fast as (or negligably faster than) (3)
	- Fx, IE, and Opera summary: (2) == (3) > (1)
	- each element takes a different amount of time to be created;
		generally the more properties and methods an element has, the longer it takes;
		for example, creating an <object> vs. creating a <div>
		- Fx: slightly slower
		- IE: nearly 10 times slower!
		- Opera: slower
	- recommend: (2)
* creating document fragments:
	(1) document.createDocumentFragment()
	(2) assume the following is defined:
			var df = document.createDocumentFragment();
		df.cloneNode(false)
	(3) assume the following is defined:
			var df = document.createDocumentFragment();
		df.cloneNode(true)
	(4) (control case 1)
		assume the following is defined:
			var div = document.createElement('div')
		div.cloneNode(false)
	- Fx: (2) and (3) are slightly faster than (1)
	- Opera: (2) and (3) are much faster than (1)
	- Fx: (2) is as fast as (or negligably faster than) (3)
	- Opera: (2) is slightly faster than (3)
	- Fx and Opera: (2) and (3) are faster than (4)
	- IE: (2) is faster than (1)
	- IE: (3) is slower than (1)
	- IE: (4) is surprisingly much faster than (2)
		(creating/cloning document fragments is slower than cloning elements!)
	- Fx summary: (2) == (3) > (1) > (4)
	- IE summary: (4) >> (2) > (1) > (3)
	- Opera summary: (2) > (3) >> (1) > (4)
	- recommend: try not to use document fragments because of IE's sluggishness,
		but if they have to be used, use (2)
* onload and onunload:
	(1) window.onload = func
	(2) <body onload="func()">
	(3) <frameset onload="func()">
	- all refer to the same thing, so setting one set the others
	- same thing with onunload
* setting attributes:
	- Fx: if attribute doesn't exist, getAttribute returns null
	- IE and Opera: if attribute doesn't exist, getAttribute returns ''
	- getting and setting attribute properties are slightly faster than using get/setAttribute:
		for ex, anchor.href = 'url' is faster than anchor.setAttribute('href', 'url')
	- setAttribute(attrStr, null) does not do anything
* iterating through arrays:
	(1)	var len = array.length;
		for (var i = 0; i < len; i++)
			<expression involving array[i]>
	(2)	for (var i = array.length - 1; i >= 0; i++)
			<expression involving array[i]>
	(3) for (var i in array)
			<expression involving array[i]>
	- Fx and Opera: (1) ~> (2)
	- IE: (2) ~> (1)
	- (1) and (2) >> (3)
* iterating through NodeLists (.childNodes or lists returned by getElements* methods):
	(1)	for (var child = node.firstChild; child; child = child.nextSibling) <expression involving child>
	(2)	var childNodes = node.childNodes; for (var i = 0, len = childNodes.length; i < len; i++) <expression involving childNodes[i]>
		(node.childNodes and node.childNodes.length are cached into temporary vars, i.e. only resolved once), and
	(3)	var childNodes = node.childNodes; for (var i = childNodes.length - 1; i >= 0; i--) <expression involving childNodes[i]>
		(same as above, but node.childNodes.length no longer has to be cached)
	(4)	for (var child = node.firstChild, i = 0; child; child = child.nextSibling, i++) <expression involving child and i>
	- (4) is only slightly slower than (1), which indicates that incrementing i isn't very expensive
	- no difference (or negligable) in speed between (2) and (3)
	- Fx: (2) and (3) are faster than (1)
	- IE: (1) is much faster than (2) and (3)
	- Opera: (1) is faster than (2)
	- Fx and IE: (2) is as fast as (3)
	- Opera: (3) is much slower than (2)
	- Fx summary: (2) == (3) > (1)
	- IE summary: (1) >> (2) == (3)
	- Opera summary: (1) > (2) >> (3)
	- recommend: (1)
* dynamically making arrays:
	(1)	var a = new Array; <loop while (a.push(<value>)) until condition is met>
	(2)	<loop until condition is met to calculate array length and store in var len>;
		var a = new Array(len); <incrementing loop while (a[i] = <value>) until i equals something>
	(3)	same as (2) except 2nd loop is decrementing loop
	(4)	var a = new Array; <loop while (a[a.length] = <value>) until condition is met>
	(5)	var a = new Array; <incrementing loop while (a[i] = <value>) until i equals something>
	- Fx: (1) is the slowest
	- IE: (1) is by far the slowest
	- Opera: (3) is the slowest
	- Fx and IE: no difference (or negligable) in speed between (2) and (3)
	- Opera: (3) is much slower than (2) mysteriously
	- (5) is slightly faster than (4)
	- Fx: slightly faster with (3) than with (5) while IE is slightly faster with (5) than with (3)
	- Fx summary: (2) == (3) > (5) > (4) >> (1)
	- IE summary: (5) > (4) > (2) == (3) >> (1)
	- Opera summary: (5) > (4) > (2) >> (1) >> (3)
	- recommend: (5) if i is available; otherwise, (4)
///////////////////////////////// redo tests 16, 17 ///////////////////////////////
* adding to arrays:
	(1) array.push(x);
	(2) array[array.length] = x;
	- (1) is slower than (2)
* concatenating strings:
	(1) for (var i = 0; i < len; i++)
			str1.concat(str_literal);
	(2) for (var i = 0; i < len; i++)
			str1 += str_literal;
	(3) for (var i = 0; i < len; i++)
			array.push(str_literal);
		str1 = array.join('');
	(4) for (var i = 0, halflen = len >> 1; i < halflen; i++)
			str1 += str_literal + str_literal;
	- note on (3): this should be the fastest, since it's O(n) (linear),
		while the rest are O(n^2) (quadratic)
	- note on (4): this "unrolls" the loop to use + as well as +=
		- checks to see if + is faster than += here (it is, as expected)
	- note: these tests concatenate string literals instead of string variables, but the results
		are the same, except for (5)
	- Fx: (2) == (3) == (4) ~> (1)
	- IE: (3) >>> (4) > (2) > (1)
	- IE: by far the slowest in string concatenation compared to Fx and Opera
	- Opera: (3) >> (4) >>> (2) ~> (1)
	- Opera: there seems to be a huge memory bug in Opera for (3), specifically array.join():
		when the array approaches a certain size (~34000 strings of length 64) and array.join()
		is called, Opera starts to consume memory extremely quickly and freezes for several seconds
		or indefinitely
	- recommend: (3) since it's the fastest in IE,
		and the Opera problem only happens at extreme (and unrealistic) array sizes
- adjacent string literals
	(1) 'abcdefghijklmnop'
	(2) 'a'+'b'+'c'+'d'+'e'+'f'+'g'+'h'+'i'+'j'+'k'+'l'+'m'+'n'+'o'+'p'
	- Fx: (1) == (2);
		in fact, if the a function containing (1) and another function containing (2) are the same
		other than the difference between (1) and (2), Fx treats those two functions the same
		function ("joined" functions), e.g. setting a property on "one" of those functions sets it
		on the "other"
	- IE: (1) >> (2)
	- Opera: (1) >> (2)
	- conclusion: Fx combines adjacent string literals into a single string literal compile-time
		(i.e. during parsing), while IE and Opera don't
* traditional event (e.g. node.onload) and advanced event (e.g. node.addEventListener (W3C) or
	node.attachEvent (MS)) models do not overlap, e.g. if a listener is attached via traditional
	event model and another is attached via advanced event model, both listeners are executed
* addEventListener cannot add the same function multiple times, while attachEvent can
* IE bug (or design error)?: the this object in event listeners attached via attachEvent refers to
	the global object (window), not the event target
* event timing:
	- IE: listeners added via traditional event model run first
	- Fx and Opera: listeners added via advanced event model run first
	- IE: listeners added via advanced event model run from last-added to first-added
	- Fx and Opera: listeners added via advanced event model run from first-added to last-added
* eval is very slow
	- recommend: don't use eval; there's usually an alternative
	- uncommon cases where eval is needed:
		- getting value within a var, given var name in string form, in non-global scope, e.g.
			function foo() {
				var a = 10;
				var b = eval('a');	//if this was in global scope, window['a'] could've been used
			}
		- copying scope to a function:
			function foo() {
				alert(a);
			}
			function blah() {
				var a = 10;
				eval(foo.toString());	//creates "copy" of foo with blah's scope
				foo();	//this now refers to the local copy of foo, rather than the original foo
			}
* get function/method vs. accessing var/property:
	(1)	given:
			function getA() {
				return 10;
			}
		var r = getA();
	(2)	given:
			var a = 10;
		var r = a;
	- Fx: (2) is ~6x faster than (1)
	- IE: (2) is ~15x faster than (1)
	- Opera: (2) is ~3.5x faster than (1)
	- remember: calling functions takes time (function overhead)
* object properties and methods are stored as a hashtable (usually):
	- obj[1], obj['1'], obj[1.0] are all equivalent; obj[1.3], obj['1.3'] are equivalent
	- keys are stored as strings
	- larger string keys are negligably slower
	- number keys are slower than string keys, since they must be converted to strings
	- larger number keys are significantly slower, since they must be converted to strings
	- size of object (number of methods and properties an object has) has no impact on performance,
		which is typical of a hashtable
		- Opera: larger objects do have a slight negative impact on performance
	- trying to access non-existent properties takes as long as accessing existent properties
* to access a property or method of an object, the object properties and methods must be searched,
	(size of the object shouldn't matter, since the properties and methods are stored in a hashtable)
	so store properties into temporary variables before using them,
	(storing methods can also work, but only if the method does not use the this object)
	but the performance increase is miniscule overall
* host objects vs. native objects:
	- accessing properties of host objects (e.g. DOM nodes) is much slower
		than accessing those of native objects (e.g. arrays)
	- accessing a property on window takes less time than doing so on document
	- Fx 1.5: accessing a property on window takes more time than doing so on document
	- Fx and Opera: for host objects, trying to access a non-existent property
		takes much less time than trying to access an existent property
	- IE: for host objects, trying to access a non-existent property
		takes a different amount of time than trying to access an existent property
		(whether slower or faster depends on type of host object)
* prototype chains (derivation):
	(1)	given:
			function classA() {}
			classA.prototype.foo = 'bar';
			obj = new classA();
		obj.foo
	(2) given:
			function classB() {}
			classB.prototype.foo = 'bar';
			function classA() {}
			classA.prototype = new classB();
			obj = new classA();
		obj.foo
	- (1) is faster than (2)
	- conclusion: the farther along the prototype chain a property is,
		the longer it takes to access it
* Array vs. Object:
	- arrays also support string keys, since Array extends Object
	- only difference is that arrays have a length property and more methods
	- array length property is equal to the array's highest integer key
	- setting new properties on arrays is slightly slower than on objects (may have to do with
		needing to check and set the length property)
* function arguments:
	- Fx and Opera: each actual argument that is passed negatively affects speed
	- each actual argument that is passed for which there is no formal argument
		negatively affects speed
	- each formal argument that is not passed an actual argument
		negatively affects speed
* semicolons and whitespace have no impact on performance;
	therefore, they are parsed when the javascript is initially parsed ("compile-time"),
	not during run-time
* casting to string manually via toString() is much slower than automatic casting
* a += b is the same speed (or negligably faster) than a = a + b
	(same thing applies to -=, *=, etc.)
* setting a var to its own value takes as long as setting it to a different value
* function expression vs. using Function constructor:
	- function expression: function(a0, a1, ...) {<body>}
	- using Function constructor: new Function('a0,a1,...', '<body>')
	- function expression is much faster than using Function constructor
	- function expression creates a closure, while using Function constructor does not
* function expressions vs. function declaration:
	- functions defined by function declarations can be used before the declaration
		ex:
			foo();
			function foo() {}
	- besides the above behavior,
		var foo = function() {}
			is equivalent to
		function foo() {}
	- IE and Fx: function expressions are barely slower than function declarations
	- Opera: function expressions are slightly faster than function declarations
	- IE: defining functions with names will define them regardless of conditionals
* there is no difference in speed between a for loop and the equivalent while loop
* array index function:
	(1)	loop from first node going to next node until index is reached
	(2)	check whether index is closer to 0 or to length;
		if closer to 0, loop from first node going to next node until index is reached;
		else, loop from last node going to previous node until index is reached
	- (2) is slightly faster than (1)
* comparison operators:
	- operators >, <, >=, <= are faster than !=
	- operators >, <, >=, <= are approximately the same speed
		(too hard to tell from each other)
	- comparing to 0 is usually slightly faster than comparing to other values
	- these operators are extremely fast,
		so it doesn't really matter which ones are used
* equality vs. strict equality:
	true: '100' == 100
	false: '100' === 100
	false: 0 == null
	false: 0 === null
	true: null == undefined
	false: null === undefined
	true: '' == 0
	false: '' === 0
	true: '' == false
	false: '' === false
	false: '' == null
	false: '' === null
	true: '0' == false
	false: '0' === false
	true: 0 == false
	false: 0 === false
	false: 0 == null
	false: 0 === null
	true: null == false
	false: null === false
	true: 1 == true
	false: 1 === true
	false: 100 == true
	false: 100 === true
	true: '1' == true
	false: '1' === true
	false: '100' == true
	false: '100' === true
///////////// construct tables for equality, strict equality, and not operators; test [] //////////
* checking if undefined:
	(1) some_var === undefined
	(2) !some_var	//only use this if some_var can have some other value that resolves to false
	(3) typeof somevar == 'undefined'
	(4) some_var == null	//only use this if some_var cannot be null
	- 0, false, null, undefined, NaN, and '' (empty string) evaluate to false;
		everything else evaluates to true
	- Fx: (2) and (4) barely faster than (1) and (3)
	- IE: (2) and (4) barely faster than (1), which is faster than (3)
	- Opera: (2) == (4) > (1) >> (3)
	- note: some_var == null is equivalent to some_var == undefined;
		to check if some_var is really undefined, (1) or (3) must be used
	- note: ancient versions of Javascript don't support (1)
* checking if a property exists:
	(1) some_obj.some_prop === undefined
	(2) !some_obj.some_prop	//only use this if some_prop cannot have another value that resolves to false
	(3) typeof some_obj.some_prop == 'undefined'
	(4) some_obj.some_prop == null	//only use this if some_prop cannot be null
	(5) 'some_prop' in some_obj
	- Fx: (2) and (4) barely faster than (1) and (3), which is faster than (5)
	- IE: (2) and (4) barely faster than (1), which is faster than (3) and (5)
	- Opera: (2) and (4) barely faster than (1) and (5), which is faster than (3)
* declaring functions - conditional declaration and preserving function names:
	(1)	if (false) function blah() {};
	(2)	if (false) blah = function() {};
	(3)	if (false) blah = function blah() {};
	(4) if (false) (function() { blah = function() {}; })();
	(5)	if (false) (function() { blah = function blah() {}; })();
	(6)	if (false) (function() { window.blah = function blah() {}; })();
	(7) blah1 = function blah2() {};
	(8) if (false) blah1 = function blah2() {};
	- (1), (3), (5), (6) preserves the function name, i.e.
		the string returned from blah.toString() will include "blah"
	- IE:
		- (1) and (3) incorrectly defines the function
		- (2) and (6) correctly defines the function
		- (5) also won't define the function, but it causes an error in IE
		- (5) and (6): as an added bonus, since the function is defined in an anonymous inner function,
			the function won't be defined in the global scope (or whatever scope that statement
			was executed in)
		- (4): if blah was not declared (via a var statement), causes an error;
			this doesn't occur if blah is declared, or the function name and variable name
			are different, e.g. blah = function blah2() {}
		- (7) incorrectly defines both blah1 and blah2 (only blah1 should be defined)
		- (8) incorrectly defines only blah2 (nothing should be defined),
			indicating that functions with names will be defined regardless of conditionals
	- Fx and Opera:
		- (1)-(6): function is correctly never defined
		- (7) correctly defines only blah1
		- (8) correctly defines nothing
	- note: preserving function name is usually only useful for debugging
* scope operator:
	- JS has no intrinsic scope operator
	(1) var x = function() { /* code here */ }();
	(2) function() { /* code here */ }();
	(3) (function() { /* code here */ })();
	- (2) throws a syntax error, since the function is treated as a function declaration,
		and function declared via function declarations must have names
	- (1) works because the function is treated as a function expression,
		which doesn't need a function name
	- (3) works because the parenthesis tell the parser this can't be a function declaration
		and must therefore be a function expression
* merging arrays:
	(1)	Array.prototype.push.apply(array1, array2); return array1;
	(2)	Array.prototype.unshift.apply(array2, array1); return array2;
	(3) return array1.concat(array2)
	(4) manual concat:
			var len1 = array1.length;
			var len2 = array2.length;
			var newLen = len1 + len2;
			var array3 = new Array(newLen);
			var i = newLen - 1;
			for (j = len2 - 1; j >= 0; j--, i--)
				array3[i] = array2[j];
			for (; i >= 0; i--)
				array3[i] = array1[i];
			return array3;
	- (1)/(2) are faster than (3)/(4), probably because (3)/(4) have to create new arrays
	- (1) is faster than (2) if array1.length > array2.length
	- (2) is faster than (1) if array1.length < array2.length
	- IE, Fx: (3) is negligably faster than (4)
	- Opera: (3) is much faster than (4)
* IE: puts ids of frames, iframes, objects, and forms into global scope,
	so instead of document.getElementById('some_id').tagName, some_id.tagName can be done;
	however, this pollutes the global scope,
	in that var some_id won't work and results in weird behavior
* iframes: iframe element and the frame of the iframe are different:
	document.getElementById('iframe_id').contentWindow == frames.frame_name
	- IE, Fx, and Opera: supports contentWindow
	- Fx and Opera: supports contentDocument (== contentWindow.document)
* IE bug?: if frame's onload and src (in that order) are set dynamically, frame's onload won't fire
	(maybe the frame's onload can fire only once, during the loading of the document)
* IE: wherever the name attribute can be used, the id attribute usually works (see exception below)
* IE bug?: can't use frames.iframe_name for frames dynamically created with document.createElement
	(works with creating them by innerHTML though);
	assigning a value to name doesn't make a name attribute
* IE: surrounding quotes from values of attributes id and name are removed in inner/outerHTML
	id="id_value" => id=id_value
	name="name_value" => name=name_value
* style.cssText:
	- Opera doesn't support this
	- setting style.<css property> will change style.cssText
	- two elements that are identical in style will have equivalent style.cssText,
		regardless of how or in what order the style was set or changed
	- style.cssText can be written to to set or change style
* any method or property related to accessing or setting HTML/XML PCDATA (string of HTML/XML),
	such as innerHTML, insertAdjacentHTML, textRange.htmlText, range.createContextualFragment
	automatically modify the string to be well-formed HTML/XML
	example:
	suppose HTML PCDATA for body == '<body></body>'
	(1)	document.body.insertAdjacentHTML('beforeEnd', '<div>');
		document.body.insertAdjacentHTML('beforeEnd', 'text</div>');
		document.body.insertAdjacentHTML('BeforeEnd', '<div>normal</div>');
	(2)	document.body.appendChild(document.createRange().createContextualFragment('<div>'));
		document.body.appendChild(document.createRange().createContextualFragment('text</div>'));
		document.body.appendChild(document.createRange().createContextualFragment('  <div>normal</div>'));
	- (1) works in IE and Opera
	- (2) works in Fx
	- IE: document.body.innerHTML == '<div></div>text\n<div></div>\n<div>normal</div>'
	- Fx: document.body.innerHTML == '<div></div>text  <div>normal</div>'
	- Opera: document.body.innerHTML == '\n<div></div>text  <div>normal</div>'
	- IE: note how IE often deletes whitespace (if any) and inserts '\n' in its place
	- note how Fx and Opera they don't write '</div>' from the second statement at all,
		while IE writes '<div></div>'
* Fx: although I can't seem to isolate a simple test case,
	XMLHttpRequest.onreadystatechange is more reliable than XMLHttpRequest.onload;
	onload sometimes fails when requesting an xml from a servlet several times consecutively async
	(each request is sent after previous request's onload is triggered);
	in any case, onreadystatechange is faster
* IE: setting onreadystatechange to null has weird problems,
	so set it to function() {} instead (empty function)
* make sure XMLHttpRequest.onreadystatechange is set after calling XMLHttpRequest.open;
	otherwise, weird results
* Fx: to use range.createContextualFragment(), range needs to be "initialized" by arbitrarily settings its bounds;
	for ex, range.setStart(document.documentElement, 0);
	once initialized, range.createContextFragment() can be used any number of times
	- large performance increase if Range and XMLSerializer are instantiated only once
* Fx: range.createContextualFragment() can't parse the xml prolog,
	even if range is created from an XML document; use DOMParser instead
* importing nodes from a dynamically loaded document (maybe applies to any foreign document):
	- Fx: requires either using XMLSerializer or manual deep copy of node for node to be properly rendered
		importNode and cloneNode don't work
	- Fx: manual deep copy of node is slower than XMLSerializer if node is not a text node (in which case it's much faster)
	- IE: manual deep copy of node is slightly slower than setting innerHTML/getting xml if node is not a text node (in which case it's much faster)
	- Fx: setting innerHTML is faster than using Range/createContextualFragment and appendChild
	- IE: insertAdjacentHTML is very slightly faster than setting innerHTML
* designMode (rich text editing mode):
	- activate rich test editing mode by setting doc.designMode to 'on' (case-insensitive)
	- IE bug: "undo/redo...seems to reset the undo buffer every time you use JavaScript to set the value of a form element or otherwise make changes to the page."
	- Fx bug: setting doc.designMode
* input and text area value:
	- IE: setting value changes innerHTML and vice versa
	- Fx and Opera (W3C): value is independent of innerHTML (innerHTML only defines initial value);
		as a consequence:
		- Fx: window.getSelection() returns an empty selection object (toString() == '' and getRangeAt(0) returns empty range)
		- Opera: document.getSelection() returns empty string
		- both support nonstandard properties on the input/textarea element: selectionStart, selectionEnd, textLength
			(should be obvious what those do, and textLength is a redundant equivalent to value.length)
	- Fx (Gecko 1.7-) bug (to be fixed in Gecko 1.8) and Opera bug:
		undo buffer is reset if value property is set dynamically
* Opera bug?: document.getSelection() doesn't work if you click a button,
	since that technically changes the selection to the button
	- workaround: save document.getSelection() in the document's mousedown event
* Opera bug: textarea and text input .scrollTop and .scrollLeft have incorrect values, and setting them does nothing
* preventing default action of tab key:
	- Fx: can be prevented at onkeypress
	- IE: can be prevented at onkeydown
	- Opera: cannot be prevented
		- workaround: create two "hidden" (position: absolute; left: 0; top: -1000px)
			input elements with their tabindex's properly set, one to intercept tab, other to intercept shift-tab,
			and when those elements are focused, they shift focus back to the original input element
* IE text range:
	- when working in a textarea and not a designMode iframe, considers whole document as part of the textedit:
		expand('textedit'), moveStart/End('textedit'), etc. move the text range to include the whole document
	- moveStart/End('word') and other methods with similar arguments include whitespace past the word that's included
		up to and including the first new line characters ('\r\n'):
		ex: "hello []there friend" where text range is represented by the brackets;
		moveEnd('word', 1) will move it to: "hello [there ]friend" rather than "hello [there] friend"
	- text ranges that belong to different objects (such as the selection object and the textarea element)
		cannot be compared or used with each other in any way, even if they encompass the same text
	- new line chars are trimmed off ends of .htmlText and .text,
		and all methods that can move range by one char don't include new line chars
		- as a result, range.text = range.text can actually change the text!
* IE design error: the args argument of the func.apply(this, args) method cannot be null,
	even though ECMA-262 3rd Edition specifies that a null value for this arguments is allowed;
	so use func.apply(this, args || [])
* IE bug: when String's split method is called with a reg exp, consecutive delimiters are treated as a single delimiter
	- ex: 'a..b'.split(/./) => ['a', 'b'] instead of ['a', '', 'b']
	- bug doesn't apply when a string delimiter is passed instead of a reg exp
* Opera bug: tabs dynamically added to textarea/text input boxes sometimes aren't displayed until user modifies value in any way
	- workaround: convert each tab to 6 spaces
* Fx bug?: dynamically setting value of textarea/text input boxes will scroll that textbox to the top and left
	- partial workaround: save scrollTop and scrollLeft before setting value, then restore them afterwards
* use func.call method instead of func.apply method whenever possible:
	func.call(this, 1, 2, 3) is faster than func.apply(this, [1, 2, 3]) since the latter requires the creation of an array
* getting first element by tag name:
	given for all:
		there are several previous sibling nodes of node,
		and there are several nodes of nodeName both inside and outside node
	(1)	given:
			function getFirstChildByNodeName(node, nodeName) {
				for (var child = node.firstChild; child && child.nodeName != nodeName; child = child.nextSibling);
				return child;
			}
		getFirstChildByNodeNameCI(node, nodeName)
	(2) given:
			function getFirstChildByNodeNameCI(node, nodeName) {
				nodeName = nodeName.toLowerCase();
				for (var child = node.firstChild; child && child.nodeName.toLowerCase() != nodeName; child = child.nextSibling);
				return child;
			}
		getFirstChildByNodeName(node, nodeName)	//where nodeName must be capitalized if the document is HTML
	(3) document.getElementsByTagName(nodeName)[0]
	(4) node.getElementsByTagName(nodeName)[0]
	- there are cases where (3) and (4) won't return the correct value by their document tree traversal nature
		(can't pinpoint what causes this though)
	- IE and Fx: (3) and (4) are much faster than (2) and (1)
	- Opera: (3) and (4) are faster than (2) and (1) in most scenarios;
		in the best case scenario, (1) is faster than (3) and (4)
	- IE and Fx: (3) is faster than (4)
	- Opera: (4) is faster than (3)
	- Fx: (3) > (4) >> (1) >> (2)
	- IE: (3) > (4) >> (1) > (2)
	- Opera: (4) > (3) > (1) >> (2)
	- recommend: (3) if the node has no other descendant nodeName nodes besides its direct children;
		otherwise, (2) for HTML, and (1) for XHTML and XML
* string literals and comparisons:
	(1) function test() {
			var t = 'some random string';
		}
	(2) given:
			var s = 'some random string';
		function test() {
			var t = s;
		}
	(3) given:
			var s1 = 'some random string';
		function test() {
			return s1 == 'some random string';
		}
	(4) given:
			var s1 = 'some random string';
			var s2 = 'some random string';
		function test() {
			return s1 == s2;
		}
	- Fx and Opera: (1) is faster than (2)
	- Fx: (3) is slower than (4)
	- IE: (2) is faster than (1)
	- IE and Opera: (3) is as fast as (4)
	- note: it would be impractical to optimize based on this, since they're all extremely fast,
		not to mention that it was very hard to compare results
		when the function call takes most of the time
* string comparisons: equality vs. strict equality:
	(1) 'some random string' == 'some random string'
	(2) 'some random string' === 'some random string'
	- (1) is as fast as (2) (also tested inequality)
* regular expression literals:
	(1) function test() {
			var t = /a/;
		}
	(2) given:
			var re = /a/;
		function test() {
			var t = re;
		}
	- Fx and Opera: (1) is faster than (2)
	- IE: (2) is much faster than (1)
	- note: since IE is kind of slow here, it might be reasonable to always use (2)
* string searching:
	given for all:
		var s = 'some random string that is ; (semi-colon) in it';
	(1) function test(div) {
			return s.indexOf(';');
		}
	(2) var re1 = /;/;
		function test(div) {
			return s.search(re1);
		}
	(3) var re2 = /;/g;
		function test(div) {
			return s.search(re2);
		}
	(4) function test(div) {
			return s.search(';');
		}
	(5) function test(div) {
			var len = s.length;
			for (var i = 0; i < len; i++)
				if (s.charAt(i) == ';')
					return i;
		}
	- (1) is faster than (2) and (3)
	- (2) is as fast as (3)
	- (2)/(3) is faster than (4)
	- (5) is by far the slowest
* array literals:
	(1) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
	(2) new Array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
	- Fx: (2) is slightly faster than (1)
	- IE, Opera: (1) is slightly faster than (2)
* closures:
	run:
		function foo() {
			var x = 10;
			window.foo1 = function() { alert(x); }
			x = 5;
			window.foo2 = function() { alert(x); }
		}
		var foo1, foo2;
		foo();
		foo1();
		foo2();
	output:
		5
		5
	conclusion:
		variables in scope are not copied for each inner function
		instead, each inner function has a reference to function scope
		therefore, the size of the scope (number of vars defined)
			does not affect the speed of defining inner functions
			and inner functions "share" the variables in scope
* new Function: functions created via the Function constructor are not closures
* Opera bug: creating an anonymous function without assigning it to a var or passing it into a function throws an error
	ex: function() {}	//not alert(function() {})
* Object.prototype.constructor:
	run:
		function classA() {}
		function classB() {}
		classB.prototype = new classA();
		var objB = new classB();
		alert(objB.constructor);
	output:
		function classA() {
		}
	conclusion:
		constructor property refers to the base of the inheritance tree
* element (as in list element, not DOM element) addition and removal in native arrays (new Array())
	are much faster than custom linked lists except for very large lists
	(> 100 in Firefox, even more in IE)
	- garbage collector also has a hard time handling a lot of deletions
* IE: if a rule is changed on a stylesheet right after an alert, the page isn't automatically
	re-rendered (resizing will cause it re-render)
* attempting to return an object in a constructor will cause that object to be returned instead of
	the object being constructed
	ex: function XMLHTTP() { return XMLHttpRequest(); }
		var xmlhttp = new XMLHTTP();
		//xmlhttp is an object of type XMLHttpRequest instead of type XMLHTTP
* labelled statements and break statements:
	- any statement can have a label; ex:
		label: for(...) { ... }
	- break and continue statements can target labels that nest those
		break/continue statements; ex:
		label: for(...) {
			for (...) {
				if (...)
					break label;
			}
		}
	- break statement cannot be used as a goto statement (can only target labels that nest them)
	- this can be used in conjunction with the block statement; ex:
		label: {
		  if (...)
		    break label;
		  ...
		}
* block statements, i.e. { ... }
	- unlike most other C-style languages, block statements does not introduce a new scope
* IE: events belong to windows - that means that an event in another frame are stored in the
	frame's event object (frame.contentWindow.event) rather than window.event
* Math.sin and Math.cos:
	given for all:
		var sin = Math.sin;
		var PI = Math.PI;
		var RAD_TO_DEG = 180 / PI;
	(1) sin(PI)
	(2) given:
			var sinTable = {};
			for (var t = 0, i = 0, ts = PI / 180; i < 360; t += ts, i++)
				sinTable[t] = sin(t); // notice sinTable[t]
		sinTable[PI]
	(3) given:
			var sinTable = {};
			for (var t = 0, i = 0, ts = PI / 180; i < 360; t += ts, i++)
				sinTable[i] = sin(t); // notice sinTable[i]
		sinTable[PI * RAD_TO_DEG];
	- only (1) supports angles < 0 and >= 2 * Math.PI
	- (3) ~> (1) > (2)
	- (2) is slow probably because of the floating point keys
		(number keys must be converted to string keys)
	- same thing applies to Math.cos
* redeclaring variables in loops:
	(1)	for (var i = 0; i < 1000; i++) {
			var x = i;
		}
	(2)	var x;
		for (var i = 0; i < 1000; i++) {
			x = i;
		}
	- (1) == (2)
* childNodes and getElementsByTagName vs. manual node arrays:
	(1) var array = hiddendiv.childNodes;
	(2) var array = hiddendiv.getElementsByTagName('*');
	(3) var array = new Array(hiddendiv.childNodes.length);
		for (var node = hiddendiv.firstChild, i = 0; node; node = node.nextSibling, i++)
			array[i] = node;
	(4) given: (1)
		array[i];
	(5) given: (2)
		array[i];
	(6) given: (3)
		array[i];
	- note on (2): this will only work if children of hiddendiv do not contain any children
		themselves
	- note: childNodes and getElementsByTagName do not actually return arrays;
		they return HTMLCollection objects
	- Fx and IE: (1) > (2) >>> (3)
	- Opera: (1) >> (2) >>> (3)
	- Fx: (6) >> (4) > (5)
	- IE: (6) >> (5) > (4)
	- Opera: (6) > (4) == (5)
	- conclusion: childNodes and getElementsByTagName is faster in the short run, but slower
		in the long run (when the array is accessed a lot)
	- Fx and Opera: (1) * ~30 (4) == (3) * ~30 (6)
		means that (3) is overall faster than (1) when array is accessed ~30 times
	- IE: (1) * ~15 (4) == (3) * ~15 (6)
		means that (3) is overall faster than (1) when array is accessed ~15 times
* smart array sort:
	(1)	array.sort();
	(2)	if (array.length > 1)
			array.sort();
	- (2) >> (1) if array.length <= 1
	- (1) ~> (2) if array.length > 1
* smart array concatenation:
	(1)	array1.push.apply(array1, array2);
	(2)	if (array2.length)
			array1.push.apply(array1, array2);
	- (2) >> (1) if array.length == 0
	- (1) ~> (2) if array.length > 0
* string.replace() - regular expressions vs. strings:
	(1) given:
			var re = /bob/;
		'abcdebobabcde'.replace(re, '');
	(2) given:
			var re = 'bob';
		'abcdebobabcde'.replace(re, '');
	- Fx: (1) > (2)
	- IE and Opera: (2) > (1)
* Fx bug: createContextFragment(str) returns an empty document fragment if str is a string
	containing only whitespace
* deriving from Array (prototype set to Array):
	(1)	function Array2() {}
		Array2.prototype = new Array();
		array = new Array2();
		alert(array.sort);
		array[2] = 2;
		alert(array.length);
	(2) function Array2() {
			var self = [];
			return self;
		}
		array = new Array2();
		alert(array.sort);
		array[2] = 2;
		alert(array.length);
	- (1), (2): array.sort is defined
	- (1): array.length is 0 after the assignment
	- (2): array.length is 3 after the assignment
	- conclusion:
		- both (1) and (2) inherit Array methods and properties
		- only (2) inherits the automatic length updating aspect of Array
	- IE bug?: methods of objects derived from Array don't work
		(does nothing, has unexpected result, or throws an error)
* [CSS] IE bug: leading underscores are ignored for every term
	- this leads to the well-documented "underscore" hack
	- this also means IE will treat an element with attribute id = "_id" as "id" in CSS
	- this does not affect the DOM, e.g. document.getElementById('_id') works
* [CSS] IE bug: table cells always use the old box sizing model, i.e.
	width doesn't include border and padding
* [HTML] IE bug: colgroup/col element's span attribute treats multi-column cells as if
	they were single-column cells (it sets width for each cell instead of each column),
	so use col elements only and don't use the span attribute
* Function.prototype.toString():
	- IE is very fast at converting functions to strings,
		to the point where using serialized functions as keys in objects feasible
	- Fx and Opera are slower
* Number.prototype.valueOf() and arithmetic:
	given for all:
		var x = new Number(10);
	(1)	x + 1
	(2) x.valueOf() + 1
	(3) x - 1
	(4) x.valueOf() - 1
	- Fx: (4) == (2) ~> (3) > (1)
	- IE: (4) ~> (2) > (3) ~> (1)
	- Opera: (3) == (1) >>> (4) == (2)
	- conclusion:
		- Fx and IE are slightly slower with addition than subtraction,
			probably because + also does string concatenation
		- Fx and IE are faster with valueOf(),
			probably because it tells the engine that it's definitely a number
			and not anything else

* Number.prototype.toString()
	given for all:
		var x = 10;
	(1) x + '1'
	(2) x.toString() + '1'
	- Fx: (1) == (2)
	- IE: (2) ~> (1)
	- Opera: (1) >> (2)
* number primitive vs. Number object
	given for all:
		var obj = new Number(10);
		var prim = 10;
	(1) obj - 1
	(2) prim - 1
	(3) obj.valueOf() - 1
	(4) prim.valueOf() - 1
	- Fx: (2) > (3) ~> (1) >> (4)
	- IE: (2) >>> (3) ~> (1) >> (4)
	- Opera: (1) == (2) >>> (3) >> (4)
	- conclusion:
		- Fx and IE must convert Number objects into number primitives before the arithmetic
		- Fx and IE must convert number primitives into Number objects before calling valueOf()
* x + x vs. x * 2:
	given for all:
		var x = 10;
	(1) x + x
	(2) x * 2
	- Fx and IE: (2) > (1)
	- Opera: (2) == (1)
	- conclusion: finding variables is apparently more expensive then arithmetic
* x / 2 vs. x >> 1 where x is even:
	given for all:
		var x = 10;
	(1) x / 2
	(2) x >> 1
	- (2) ~> (1)
	- Fx: (2) >> (1) if x is odd
* initializing array length:
	(1)	var a = new Array(10);
		for (var i = 0; i < 10; i++)
			a[i] = i;
	(2)	var a = new Array();
		for (var i = 0; i < 10; i++)
			a[i] = i;
	- Fx: (1) > (2)
	- IE and Opera: (1) == (2)
* concatenating to a string primitive is faster than concatenating to a String object,
	presumably because the object needs to be converted into a string primitive
	- manually calling toString() on the String object is just as fast or slightly slower
		than having it automatically called
* IE bug: after adding a text node adjacent to an existing text node,
	normalize() sometimes doesn't work
	- workaround: put "node.childNodes[0];" before calling normalize()
		(can even be before adding the text node), e.g.
		node.childNodes[0];
		node.appendChild(document.createTextNode('foo'));
		node.normalize();
		alert(node.childNodes.length);
		(I don't know why this works...)
	- narrowed it down to this: normalize() doesn't work after adding an adjacent text node
		to a text node that wasn't added via scripting (i.e. already in the document);
		however, there may be other ways to trigger this bug...
* IE bug: modifying text within "whitespace: pre" elements (incl. <pre>) is very buggy:
	- modifying text node's data or adding adjacent text node:
		- adding '\n' doesn't render a new line and instead renders a space
		- adding '\r\n' renders a new line but also renders the space, indenting the text
		- adding '\r' renders a new line without rendering the space, but innerHTML and outerHTML
			don't show the \r at all (in fact, if the text should be "a\nb", it would just show "ab")
	- replacing via innerHTML or innerText or inserting via insertAdjacentHTML:
		- all adjacent whitespace, including new lines (\r\n, \n, \r),
			are reduced to a single space
		- workaround: use outerHTML instead
* IE bug: onreadystatechange on ActiveX objects (like XMLHTTP and DOMDocument)
	cannot be set to null (type mismatch error)
	- workaround: set them to an empty function instead, preferably a closure-less one
* calling XMLHttpRequest's abort() fires onreadystatechange,
	so clear onreadystatechange (since it can't be nulled in IE, set it an empty function)
	before calling abort()
* .constructor of an object instantiated by a class D derived from class B
	refers to class B instead of class D (the "most base" class)
* function object's .length is immutable
	- it is always equal to the normal of formal arguments
	- .length is also immutable for an object derived from a function
		(prototype chain includes a function)
* Fx bug: .length of an object derived from a function is undefined
* instanceof vs. typeof:
	- instanceof works for objects, while typeof works for primitives
	- ('string' instanceof String) == false
	- (new String('string') instanceof String) == true
	- typeof('string') == 'string'
	- typeof(new String('string')) == 'object'
* "equal to one of following" algorithm:
///////////////////////// tests 127 ///////////////////////////////////////////////
* testing whether a string is empty:
	(1)	str == ''
	(2)	str.length = 0
	- (1) > (2)
* regexp: [] vs. |
	(1)	/a|b|c|d|e|f|g|h/
	(2)	/[abcdefgh]/
	(3)	/[a-h]/
	- Fx and Opera: (3) ~> (2) > (1)
	- IE: (3) == (2) > (1)
* setting lastIndex of global regexp vs. searching substr with non-global regexp:
	(1)	given:
			var re = /abcd/g;
			var str = 'abcdabcd';
		re.lastIndex = 4;
		re.exec('abcdabcd');
	(2)	given:
			var re = /abcd/;
			var str = 'abcdabcd';
		re.exec(str.substr(4));
	(3) given:
			var re = /abcd/;
			var str = new String('abcdabcd');
		re.exec(str.substr(4));
	- Fx and IE: (1) > (2) > (3)
	- Opera: (1) > (3) > (2)
* using a $ (end of string) in regexp
	(test whether $ allows regexp to search from end of string)
	given for all:
		var str = <1000-length string>;
	(1)	given:
			var re = /abcd$/;
		re.exec(str);
	(2)	given:
			var re = /abcd/;
		re.exec(str);
	- Fx and IE: (1) == (2)
	- Opera: (2) > (1)
	- conclusion: using $ doesn't help performance (and worsens it for Opera)
* using a ^ (start of string) in regexp
	(test whether ^ allows early exit)
	given for all:
		var str = <1000-length string>;
	(1)	given:
			var re = /^abcd/;
		re.exec(str);
	(2)	given:
			var re = /abcd/;
		re.exec(str);
	- Fx and IE: (1) >>> (2)
	- Opera: (2) >> (1)
	- conclusion: ^ does allow early exit except in Opera, where the ^ actually
		significantly hurts performance for some reason
* substring vs. substr:
	given for all:
		str = 'jhflkajshfklasjhfalksjhfaslkjhflkasjhflkasjfhlkasjdhfalksjdfhaf';
	(1)	str.substr(10)
	(2)	str.substr(10, 5)
	(3)	str.substring(10)
	(4)	str.substring(10, 15)
	- (1) and (3) are functionally equivalent
	- (2) and (4) are functionally equivalent
	- Fx and IE: all take around the same amount of time,
		with (1) and (3) possibly slightly faster than the rest
		(margin of error is too large)
	- Opera: (1) == (3) ~> (2) == (4)
	- conclusion: size of substring apparently does not matter
* IE bug: when a capture subpattern in a regexp fails to match,
	the index it is assigned to in the return array is the empty string, not undefined,
	e.g. /(a)|b/.exec('b')[1] === ''
* regexp: the . pattern is the faster than a single character pattern
	e.g. /./ is faster than /a/
* try/catch vs. labelled statement/break:
	(1)	try {
			//code 1
			if (cond)
				throw 0;
			//code 2
		} catch (e) {
			//code 3
		}
	(2)	lbl: try {
			//code 1
			if (cond)
				break lbl;
			//code 2
		} finally {
			//code 3
		}
	(3)	lbl1: {
			lbl2: {
				//code 1
				if (cond)
					break lbl2;
				//code 2
				break lbl1;
			}
			//code 3
		}
	- Fx and IE: (3) == (2) >> (1)
	- Opera: (3) >> (2) > (1)
	- (1) requires no labels
	- (2) requires 1 label
	- (3) requires 2 labels
* setting .checked of a form control doesn't trigger the onchange event
* function variables (aka local vars) can't be removed with the delete statement since they are readonly
* switch case labels can be any expression
	- if the case label is an assignment expression, the assignment doesn't happen until the switch expr
		is checked against the case
	- for ex, given the following:
			var y;
			switch (x) {
				case 0: statement0;
				case y = 1: statement1;
			}
		if x is 1 or any value that doesn't match any previous cases, y is assigned 1,
		but if x is one of those previous cases (e.g. 0 in this case), y is left undefined
* conditional operator's last two operands have the same precedence as assignment operators,
	i.e. the following two are equivalent
		a ? b = c : d = e
		a ? (b = c) : (d = e)
* array literals can have empty elements
	e.g. [,x,,y] defines an array with the following elements in order:
		undefined, x, undefined, y
	- the first trailing comma in an array literal doesn't increase the array length
		e.g. [x,] is equivalent to [x];
		the remaining trailing commas contribute to the array length
		e.g. [x,,] has an array length of 2
	- IE bug: first trailing comma increases array length, such that [x,] != [x]