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 false

  • Objects 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 with valueOf over toString if both methods exist.

  • Check for undefined with typeof, 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 + operator

  • The following coercion rules apply when using ==

Argument Coerces to
string, number, boolean number
Date toString, valueOf
Non-Date object valueOf, toString
  • null == undefined
  • null and undefined evaluate to false when compared to anything other than null or undefined
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 loop

  • Semi-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, and charCodeAt
𝄞 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. Since i continues to change, the inner functions see the final value of i
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)