Accustoming Yourself to JavaScript
1. Know Which JavaScript You Are Using
Strict Mode only works the
"use strict";
is included at the top of a function or the top of a file.Since concatenation doesn't gaurantee order, wrap your file in an IIFE:
(function() {
"use strict";
function f() {
//...
}
})();
2. Understand JavaScript's Floating Point Numbers
All numbers are double-precision floating point numbers, 64-bit doubles
bitwise operators implicitly convert arguments to 32-bit integers
Binary representation of a number can be detemined with
toString
and passing in a radix of 2:
(8).toString(2); // => "1000"
3. Implicit Coercions
Arithmetic operators attempt to convert arguments to numbers
+
is overloaded to perform numeric addition or string concatenation.
2 + "3"; // => "23" If there is a string, number is cast to string
"2" + 3; // => "23"
1 + 2 + "3"; // => "33" Addition is "left-associative" and groups from the left
"17" * 3; // => 51, the string is coerced into a number
"18" / 3; // => 6
null * 3; // => 0, null coerces to 0
undefined * 3;// => NaN, undefined coerces to NaN
"foo" * 3; // => NaN, try parseInt("foo")
NaN === NaN
evalutes to falseObjects convert to strings by implictly calling
toString
"J" + { toString: function() { return "S"; }}; // => "JS"
- Objects convert to numbers by implictly calling
valueOf
2 * { valueOf: function() { return 3; }}; // => 6
With
+
, object will coerced withvalueOf
overtoString
if both methods exist.Check for
undefined
withtypeof
, not truthiness:
if (typeof x === 'undefined')
4. Primitives over Primitive Wrappers
- A String object is only equal to itself
var s1 = new String("hello");
var s2 = new String("hello");
s1 === s2; // false
s1 == s2; // false
- JavaScript implicitly coerces primitives so that they may be used with methods on the Primitive wrapper.
"hello".toUpperCase(); // "HELLO"
- You can set properties on primitives, as they are implicitly wrapped. After the set, the object is destroyed, so the properties don't persist.
"hello".someProperty = 17;
"hello".someProperty; // undefined
5. Avoid using == with mixed types
Explicitly coerce primitives to numbers before comparison with unary
+
operatorThe following coercion rules apply when using ==
Argument | Coerces to |
---|---|
string, number, boolean | number |
Date |
toString, valueOf |
Non-Date object |
valueOf, toString |
null == undefined
null
andundefined
evaluate to false when compared to anything other thannull
orundefined
var date = new Date("1999/12/31"); // date => date.toString() => "Fri Dec 31 1999 00:00:00 GMT-0800 (PST)"
date == "1999/12/31"; // false
6. Limits of Semicolon Insertion
- Semicolons are only inserted:
- before }
- after one more more newlines
- end of program
- next input token cannot be parsed
a = b
(f()); // => a = b(f()); // parses OK - no ; is inserted
a = b
f(); // => a = b f(); // doesn't parse, ; is inserted
- Five problematic characters:
(
,[
,+
,-
,/
- Never omit before a statement with problem character
- Can act as an expression operator, so newlines starting with these won't have semi-colon inserted
a = b
["r", "g", "b"].forEach(...)
a = b['r', 'g', 'b'].forEach() // a = b['b'].forEach()
Never put newline before argument to
return
throw
,break
,continue
,++
, or--
Semi-colons never inserted in head of
for
loopSemi-colons never inserted as empty statement
function infiniteLoop() { while (true) } // parse error
// must add ; after while()
7. 16-bit Code Units
- JavaScript strings consist of 16-bit code units
- One code point such as 𝄞 can contain two code units (known as surrogate pairs)
- Cannot rely on
length
,charAt
, andcharCodeAt
𝄞 | c | l | e | f | ||
0xd834 | 0xdd1e | 0x0020 | 0x0063 | 0x006c | 0x0065 | 0x0066 |
Variable Scope
8. Minimize Use of Global Object
- Use global for feature detection, like
if (!this.JSON)
- Calling a constructor without new may introduce variables into global scope
function Person(name) {
this.name = name
}
var person = Person("Tuan"); // this binds to global scope
console.log(person instanceof Person); // false
console.log(typeof person); // "undefined"
console.log(name); // "Tuan" - defined under window.name
9. Always Declare Local Variables
- Variables not initialized with
var
are created on global scope
10. Avoid with
with
treats an object as if it represented a variable scope- Tempting to use to "import" variables
function f(x, y) {
with (Math) {
return min(round(x), sqrt(y)); // hosed if someone defines (Math.x = 0)
}
}
- Explicitly bind local variables to object properties instead of implicitly binding with
with
11. Get comfortable with Closures
- Function that keeps track of variables in its containing scope.
- Closures can outlive the function that creates them.
- Closures store references to outer variables. Updates to variables are visible to any closures that have access to them.
12. Understand Variable Hoisting
- Lexical scoping, scoped to containing function, not block scoping
- Variable declaration consists of declaration (hoisted to top) and assignment
- Exception* to function-scope
catch
block is block-level scoping
function test(){
var x = "var", result = [];
result.push(x);
try {
throw "exception";
} catch (x) {
x = "catch";
result.push(x);
}
result.push(x);
return result; // => ['var', 'catch', 'var']
}
- Place all variables at the top
13. Use IIFE to Create Local Scope
Entering a scope at runtime allocates a slot in memory for each variable binding.
A common problem with loops and closures:
- The value
i
is not used, the reference is. Sincei
continues to change, the inner functions see the final value ofi
- The value
function wrapEle(a) {
var result = [], i, n;
for (i = 0, n = a.length; i < n; i++) {
result[i] = function() { return a[i]; };
}
return result
}
- Use an IIFE (iffy) to force the creation of a local scope to hold the value of
i
function wrapEle(a) {
var result = [], i, n;
for (i = 0, n = a.length; i < n; i++) {
(function () {
var j = i;
result[i] = function() { return a[j]; };
})();
}
return result
}
14. Beware of Unportable Scoping of Named Functions
- Difference between anonymous and named function is the latter binds its name as a local variable
var f = function find(k, v){
// internally, there is a local variable `find`
}
Externally, we cannot reference the internal name
In some old ES3 engines, merely naming a function expression brings all properties of
Object.prototype
into scope.var constructor = function() { return null; } var f = function f() { // Object.prototype.constructor is in scope return constructor(); // new object is created }
Avoid using variables with names of
Object.prototype
properties
15. Beware of Unportable Scoping of Block-Local Function Declarations
- Don't put function declarations inside of local blocks (if statements)