Speaking Javascript 2nd Pass

Named blocks
leBlock: {
 
  let x = 1
  for (;;) {
    log("outer loop")
    for (;;) {
      log("inner loop")
      break leBlock
    }
  }
 
}
 
 outer loop
 inner loop
JavaScript has only six types

Therefore, constructors technically don’t introduce new types, even though they are said to have instances.

Primitive Values
Object Values
False
Primitives Borrow Their Methods from Wrappers
  • In strict mode, methods from the wrapper prototype are used transparently
"abc".charAt === String.prototype.charAt  true
 
String.prototype.hello = () => log("hello")
"abc".hello()  hello
Technically, primitive values do not have their own properties, they borrow them from wrapper constructors. But that is something that goes on behind the scenes, so you don’t normally see it.
the + operator examines its operands. If one of them is a string, the other is also converted to a string and both are concatenated, Otherwise, both operands are converted to numbers (see Converting to Number) and added:
"foo" + 3  'foo3'
3 + true  4
The typeof operator distinguishes primitives from objects and determines the types of primitives.
The instanceof operator determines whether an object is an instance of a given constructor.
Typeof bug
typeof null  'object'
NaN is the only value that is not equal to itself
NaN === NaN  false
Newer engines optimize string concatenation via + and use a similar method internally. Therefore, the plus operator is faster on those engines
substring() should be avoided in favor of slice(), which is similar, but can handle negative positions and is implemented more consistently across browsers.
Extract a length of string from a string
// if multiple separators, we can preserve them to reconstruct string
let strA = "a,b.c|d,e.f|"
let a = strA.split(/([,.\|])/)
log(a)  [ 'a'',''b''.''c''|''d'',''e''.''f''|''' ]
 
let strB = a.join("")
log(strB)  'a,b.c|d,e.f|'
 
log(strA === strB)  true
Transforming a string, all non-destructive
Search and Compare
// does 'b' exist in str?
// sucks
log("abc".indexOf("b") >= 0)  true
// better
log(/b/.test("abc"))  true
 
log("abc".search(/b/))  1
log("abc".search("b"))  1
log("abc".search(/B/))  -1
Test, Match and Replace with Regex
log("abc".match(/B/))  null
log("abc".match(/b/))  [ 'b', index: 1, input: 'abc' ]
log("abc".match("b"))  [ 'b', index: 1, input: 'abc' ]
log("abc".match(/b/g))  [ 'b' ]
log("abcbc".match(/b/g))  [ 'b''b' ]
 
log("abc".replace("a""A"))  Abc
log("abc".replace(/a/, "A"))  Abc
log("abcabc".replace(/a/, "A"))  Abcabc
log("abcabc".replace(/a/g, "A"))  AbcAbc
 
"abc".replace(/a/, x => log(`found ${x}`))  found a
"abc".replace(/(a)/, (x, p1, offset, whole) => {
  log(`found ${x} ${p1} ${offset} ${whole}`)
})  found a a 0 abc
JS has a do-while loop, that I keep forgetting about.
let i = 0,
  a = []
do {
  a.push(i++)
} while (i < 10)
log(a)  [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
Best practice: don’t use for-in for arrays and be careful with objects
switch statement
// case value can be any evaluated expression
switch ("red") {
  case "blue":
    log("color is blue")
    break
  case ["r""e""d"].join(""):
    log("color is red")
    break
  default:
    log("not found")
 color is red
 
// use fallthrough for multiple conditions
switch ("red") {
  case "red":
  case "white":
  case "blue":
    log("color is american")
    break
  default:
    log("unamerican")
 color is american
 
// use instead of confusing if else if else chains
 
let x = 10000
switch (true) {
  case x < 10:
    log("small number")
    break
  case x < 100:
    log("sizable number")
    break
  case x < 1000:
    log("big number")
    break
  default:
    log("huge number")
}
Don't throw just anything

JavaScript has special constructors for exception objects . Use those or subclass them. Their advantage is that JavaScript automatically adds a stack trace (on most engines) and that they have room for additional context-specific properties. The simplest solution is to use the built-in constructor Error():

When Error is used like a function -- without new, it will return an Error object. Therefore, a mere call to Error will produce the same output that constructing an Error object via the new keyword would.

Optional finally clause, it is always called, even if try has a return statement. Can be used for function cleanup.

try {
  throw Error("help"
} catch (e) {
  log("caught ", e.stack)
} finally {
  log("always")
}
If you don't return anything from a function undefined is returned
new is another way to call any function, but not always appriopriate
Functional expressions can be named
let f = function me() {
  console.log(me)
}
 
f()  [Function: me]
console.log(me)  ReferenceError: me is not defined
console.log(f.name)  f
Way to write iffys that doesn't confuse prettier
!!(function() {
  console.log("hi")
})()  hi
delete deletes a property from an object
  • delete affects only the direct (“own,” noninherited) properties of an object. Its prototypes are not touched
  • Use the delete operator sparingly. Most modern JavaScript engines optimize the performance of instances created by constructors if their “shape” doesn’t change (roughly: no properties are removed or added). Deleting a property prevents that optimization.
let o = {a: 1}
delete o.a
log(o)  {}
Avoid the object constructor; an empty object literal is almost always a better choice
var obj = new Object() // AVOID 
var obj = {} // prefer
When you call a function, this is always an (implicit) parameter:
Function.prototype.call calls a function.
Function.prototype.bind returns a new function.
Function.prototype.apply is now obsolete.
Fancy term for Function.prototype.bind is partial function application
Proper way to extract a method.
let o = {
  x: 1,
  add: function(x) {
    return x + 1
  }
}
 
let a1 = o.add.bind(o)
 
log(a1(3))  4
arr.forEach(f(), this) takes an optional this argument
Toolkit for inheritance
  • Object.create(proto [, propDescObj]
  • Object.getPrototypeOf(jane)
Use Object.keys(o) to get own properties of and object.
use "prop in o" to find if o has a property (looks at prototypes too)
Avoid invoking hasOwnProperty() directly on an object, as it may be overridden (e.g., by an own property whose key is hasOwnProperty):
  • Object.prototype.hasOwnProperty.call(obj, 'foo')
  • {}.hasOwnProperty.call(obj, 'foo')
The instanceof operator allows us to check whether an object is an instance of a given constructor:
Avoid replacing a constructors prototype because its already set up correctly, like the constructor property.
// Avoid:
C.prototype = {
  method1: function() {}
}
 
// Prefer:
C.prototype.method1 = function() {}
If you completely replace prototype then reset the constructor property.
C.prototype = {
  constructor: C,
  method1: function() {}
}
Crockford privacy pattern
function Constructor() {
 
  let data = [] // private
 
  this.fun = function() {
    data // access to private data
  }
 
}
Singleton Constructor
function Singleton() {
 
  // relies on function properties
  if (Singleton.inst) {
    return Singleton.inst
  } else {
    Singleton.inst = this
  }
 
  this.random = Math.random()
 
}
 
// Another singleton pattern, but without constructor
let o = function() {
  let instance
  return {
    getInstance: function() {
      if (!instance) {
        instance = {n: Math.random()}
      }
      return instance
    }
  }
}
 
let o2 = o()
log(o2.getInstance())
Revealing module pattern
let o = function() {
  function f() {}
  return {f}
}
Objects that are "array like", implement indexing and a length property, can use many Array methods, but not all.
  • concat
  • every
  • filter
  • forEach
  • indexOf
  • join
  • lastIndexOf
  • map
  • pop
  • push
  • reduce
  • reduceRight
  • reverse
  • shift
  • slice
  • some
  • sort
  • splice
  • toLocaleString
  • toString
  • unshift
let o = {
  length: 3,
  0: "a",
  1: "b",
  2: "c"
}
 
let v = Array.prototype.map.call(o, x => x)
log(v)  [ 'a''b''c' ]
The dict Pattern: Objects Without Prototypes Are Better Maps
var dict = Object.create(null)
Do not delete elements of arrays because it creates holes, use splice instead.
a = [1, 2, 3][(1, 2, 3)]
delete a[1]  bad!
 [ 1, <1 empty item>, 3 ]
Test for array with Array.isArray
log(Array.isArray([]))  true
Array.prototype.push takes multiple arguments

Array.prototype.concat is another option, but it is not destructive.

let a = []
a.push(1, 2, 3)
log(a)  [ 1, 2, 3 ]
 
a.push(...[4, 5, 6])
log(a)  [ 1, 2, 3, 4, 5, 6 ]
 
a = a.concat([7, 8, 9])
log(a)  [ 1, 2, 3, 4, 5, 6 ]
Array.prototype.splice( start [, deleteCount [, element1 [, element2] ] ] )

start can be negative, which counts from the end.

deleteCount is optional, if missing splice deletes all elements after start

inserts the optional elements

Array.prototype.concat(arr1 [, arr2])
  • Non destructive
  • Takes multiple arguments
  • Arguments can be arrays or elements, will automatically splat arrays
  • With no arguments creates a shallow copy.
indexOf(search [, start]) exists for both arrays and strings
  • Same with lastIndexOf
  • === is used
Iteration examination methods
  • foreach(callback [, thisValue])
    • Does not support break
    • To break use some() instead and return true to break and ignore returned true
  • every(callback [, thisValue])
    • returns true if callback returns true for every element
    • returns true if array is empty
  • some(callback [, thisValue])
    • returns true if callback returns true for at least one element
    • returns false if array is empty

function callback(element, index, array)

Iteration transformation methods
  • map(callback [, thisValue])
  • filter(callback [, thisValue])

function callback(element, index, array)

Reduction methods
  • reduce(callback [, initialValue])
  • reduceRight(callback [, initialValue])

function callback(previousValue, element, index, array)

profile with console.time([name]) and console.endTime([name])
console.time("a")
for (let i = 0; i < 1000; i++) {}
console.timeEnd("a" a: 0.305ms
Regexp.prototype.exec(str)
  • If using /g flag holds state so can be use to find successive matches
  • Make sure to compile regex outside a loop or it resets state
  • exec is complicated!
let str = "abc123"
 
let re = /(a)/g
log(re.exec(str))  [ 'a''a', index: 0, input: 'abc123' ]
log(re.exec(str))  null