Enough Modern JavaScript

Also checkout how to do things with Vanilla JS: </dom>.

JS 101

  • {} is object; [] is array
  • null is a value missing intentionally
  • undefined is a value missing unintentionally
  • if () { ... } else { ... }
  • Regular functions: function f() { ... }.
  • Prefer class rather over prototype
  • JavaScript classes are just functions
  • obj.toString() => '[object Object]'

Explicit Return

The return value of function must use return:

function identity(x) {
  return x
}

Retrive value

const object = {a: 1, b: 2, c: 3, d: 4}

object.a
1

object['a']
1

Turn on Strict mode

'use strict' turn on the Stricit mode.

  • No need to use ; to end the line
  • Octal literals not allowed.
  • Cannot introduce global variable.
  • delete can only remove key from object.
  • === to compare things
  • Regular Expression

Debugging

Debug with debugger, console.log(), console.trace().

Searching DOM

  • document.querySelector
  • document.getElementById

Node

  • node.getAttribute

DOM Manipulation

  • insertAdjacentHTML to add HTML beforebegin/afterbegin/beforeend/afterend to any element

Number

  • Arithmetic on undefined returns NaN
  • Operation on a NaN returns NaN
  • isNaN(undefined) is true
  • Number.isNaN is the fixed version of isNaN Disallow specific global variables
  • Number.isFinite over number === Infinity
  • Number.MIN_SAFE_INTEGER and NUmber.MAX_SAFE_INTEGER. Number.isSafeInteger checks if number is within the safe range.

Object

  • Object.keys to list all keys of given object. Modern JavaScript guarantee object key ordering

Computed Property

const name = 'Juanito'
const loginCounts = {[name]: 5}

loginCounts.Juanito
5
const users = [
  { name: 'Juanito', age: 15 },
  { name: 'Bella', age: 16 },
]

function age(user) {
  return { [user.name]: user.age }
}

age(users[0])

Shorthand syntaxes

const name = 'Juanito'
const age = 36
const user = {name, age};

[user.name, user.age]
['Juanito', 36]

Getter function

const user = {
  get name() { return 'Juanito' }
}

user.name
'Juanito'

Setter function

const user = {
  realName: 'Juan',
  set name(newName) { this.realName = newName }
}

user.name = 'Juanito'
user.realName
'Juanito'

let and var

Don’t use var, use let (can change) and const (cannot change).

  • let is properly scoped inside block of code { ... }.
  • const variable cannot be redefined. But you can still mutate the variable elements. E.g. change the elements of const-defined array
    const will find the nearest definitions.

On let vs const.

Loop

for in loop

ESLint rule: Require Guarding for-in (guard-for-in)

for (.. in ..) {} iterates over keys in array.

Skips undefined and null elements.

const keys = []
const letters = []
letters[1] = 'a'
for (const key in letters) {
  keys.push(key)
}

keys
['1']

for of loop

for (.. of ..) {} iterates over values in array.

Returns undefined for undefined or null elements.

const keys = []
const letters = []
letters[1] = 'a'
for (const key of letters) {
  keys.push(key)
}

keys
[undefined, '1']

forEach loop

forEach method will skip over empty slots.

var users = [
  {name: 'Lia'},
  {name: 'Yejin'},
]
var names = []
users.forEach(user => {
  names.push(user.name)
})
names

Strings

'str'
"str"
`str` // called template literals

Template literals can do interpolation:

`1+1=${1+1}`

${1+1} calls toString() on computed value.

template literals can have newline:

const signoff = `
  Best,
  Juanito
`

dedent can remove leading whitespaces.

toString()

[1, 2].toString()
'1,2'


({a: 1}).toString()
'[object Object]'

Function

Example of sum function:

function sum(...numbers) {
  return numbers.reduce((x, y) => x + y, 0)
}
  • Arrow functions: const f = () => { ... }. (no this, this will be the nearest function)

this

Last resort: Use bind to set a function’s this.

Rest arguments

... is called spread operator.

Must come last and only used once in function signature, comes after positional arguments.

f(...args) {

}

Pass in rest args to function

function add(x, y) {
  return x+y
}

const numbers = [1, 9]
add(...numbers)

function generators

The * says numbers function is a generator function.

function* numbers() {
  yield 1
  yield 2
}

for (const n of numbers) {
  console.log(n)
}

Array.from(numbers)
=> [1, 2]

Tagged template literal

function hello(){
  return 'Hello'
}

hello`world!`
=> `Hello`

To deconstruct literal and interpolated values from string:

function extractArguments(strings, ...values) {
  return {
    first: strings,
    second: values,
  }
}

extractArguments`1${2}3`
{ first: ['1', '3'], second: [2] }

Array

  • new Array(1) => [empty] (empty slot)
  • empty is the representation, but undefined will be used as return value for new Array(1)[0] => undefined
  • Create array with prefilled value: new Array(3).fill(1)
  • Find index arr.indexOf(e), -1 if not found
  • findIndex(element => expr) to find element’s index satisfy expr, findIndex returns first match
  • Copy array with slice
  • Use push to add an element to array, push returns the new length of changed array
  • Use join to join elements of array
  • Use flat to flatten one level; flat(Infinity) to flat recursively Source
  • flat + map => flatMap
  • map to do something on argument and returns new array
  • map skips empty slots
  • reduce skips empty slots
  • filter skips empty slots
  • use slice + sort if does not want to change original array
  • sort array: arr.sort((a, b) => a - b)
  • null and undefined become empty strings when join
  • spread operator ...
    const middleNumbers = [6, 7]
    const numbers = [5, ...middleNumbers, 8]
    
  • ['a', 'b'].reduceRight((a, x) => a + x) => 'ba'

Array’s type is 'object' instead of 'array'.

const obj = {a: 1, b: 2}
const keys = []
for (const key in obj) {
  keys.push(key)
}

typeof keys
=> 'object'

Array looks like an implcity object where key is the index:

Object.keys(['a', 'b', 'c'])
['0', '1', '2']

Check element

  • Prefer Set has() over ['a', 'b'].includes('c') (O(N))
  • in operator asks whether the arr[0] has something: 0 in arr

Deconstruct Array

const letters = ['a', 'b']
const [a, b] = letters

// Skip value
const letters = ['a', 'b', 'c']
const [a, , c] = letters
['a', 'c']

// Supply value when missing
const letters = ['a', 'b']
const [a, b, c='C'] = letters
['a', 'b', 'C']

// Deconstruct over Supplied value
const letters = ['a', 'b', 'c']
const [a, b, c='C'] = letters
['a', 'b', 'c']

// String can also be deconstructed
const letters = 'xy'
const [x, y] = letters
x
// => 'x'

some

some always returns false for an empty array.

[].some(x => x > 0)
true

[10, 20].some(x => x > 10)
false

every

every always returns true for an empty array.

[].every(x => x > 0)
true

[10, 20].every(x => x > 10)
false

none

There is no none function in JavaScript. we can implement like so:

!arr.some(f)

![2,3,4].some(number => number === 1)

JSON

JSON.stringify— A key with undefined value will be removed from resulting JSON

JSON.stringify(obj, f). JSON.stringify preserves key order.

JSON.parseparse will look for a toJSON key in object

Class

class Animal {
  constructor(name) {
    this.name = 'name'
  }
}

new Animal().name

Methods on class is called Static Methods.

class User {
  static getUserName(user) {
    return user.name
  }
}

Inheritance

class Person extends Animal {
  constructor() {
    super(name + ' the human')
  }
}

Set

const names = new Set([1,1,2])
names.add()
names.delete()
names.clear()
function benchmark(f) {
  const start = new Date().getTime()
  for (let i=0; i<1000000; i++) {
    f()
  }
  const end = new Date().getTime()
  return end - start
}

Class

  • Cannot define another class inside a class
  • Set union, intersection, difference are O(N), array’s are O(N*N)
class User {
  constructor(name, email) {
    this.name = name
    this.email = email
    this.admin = false
  }
}

class Admin extends User {
  constructor(name, email) {
  super(name, email)
  this.admin = true
  }
}

const admin = new Admin('Juanito', 'juanito@hey.com', true)

admin.admin
=> true

admin instanceof User
=> true

Class will capture surrounding function’s name

function createUser(name) {
  class User {
    constructor() {
      this.name = name
    }
  }
  return new User()
}

createUserr('Guest').name

string keyed methods

const user = {
  'user name'() { return 'Juanito' }
}

user['user name']()
=> 'Juanito'

Set Operations

const set1 = new Set([1, 2, 3])
const set2 = new Set([2, 3, 4])
const unionSet = new Set([...set1, ...set2]);
[unionSet.has(1), unionSet.has(4)]

Array.from(unionSet)
[1, 2, 3, 4]

const set1 = new Set([1, 2, 3])
const set2 = new Set([2, 3, 4])
const intersectionSet = new Set(
  Array.from(set1).filter(n => set2.has(n))
);

Array.from(intersectionSet)
[2, 3]

const set1 = new Set([1, 2, 3])
const set2 = new Set([2, 3, 4])
const differenceSet = new Set(
  Array.from(set1).filter(n => !set2.has(n))
);

Array.from(differenceSet)
[1]

Symbol

  • Symbol.toStringTag => Symbol(Symbol.toStringTag)
  • Built-in toString() => this[Symbol.toStringTag]