Skip to main content

JavaScript

Basic structures#

Hoisting#

  • JS moves var declarations all the way up to the top of the scope
  • solution: use let instead of var

if (mojo) {    var dojo = 1}
// becomesvar dojoif (mojo) {  dojo = 1}

Assignment#

let person = {    name: 'Kunio Otani'}
// assign copies the values from one ore more source to a target object// returns the target objectObject.assign(person, {    age: 42,    nationality: 'JP'})
// expression, returns the right side of the operatorperson.age = 42person.nationality = 'JP'

THIS reference#

foo();          // "this" in foo refers to the global objecttest.foo();     // "this" in foo refers to test new foo();      // "this" in foo will refer to a newly created object foo.apply(bar, [1, 2, 3]);  // "this" in foo is set to barfoo.call(bar, 1, 2, 3);     // "this" in foo is set to barfoo = () => { ... }         // "this" is set to the surrounding scope

Arguments#

  • every function scope can access a special variable arguments which holds a list of all function arguments
  • arrow functions, however, have only access to the arguments of the nearest non-arrow parent function
function foo(a, b, c) {    console.log(arguments[0]) // will print a}

Six falsy values#

  • any other values is true
  • false, 0, '', null, undefined, NaN

Six primitive values (+ Object)#

  • Boolean, Null, Undefined, Number, String, Symbol

For loops#

  • for-in - works for any object, not only arrays. The order is undefined
for(let key in activeUsers) {    console.log(activeUsers[key])}
  • for-of - works only for objects that have [Symbol.iterator]
for(let user of activeUsers) {    console.log(user)}

Destructuring assignment#

  • array destructuring - allows to extract multiple array elements
let [ name1, name2 ] = names
  • ignoring second element
let [ name,, name3 ] = names
  • setting default values
let [ a = 1, b = 2, c = 3 ] = names
  • object destructuring - allows to extract attributes
// assign properties - bad waylet user = buildUser('Sam', 'Williams')let first = user.firstlet last = user.last// assign properties - good waylet { first, last, fullName } = buildUser('Sam', 'Williams')let { fullName } = buildUser('Sam', 'Williams') // we need only fullName

Template strings#

let fullName = `${first} ${last}` // ` backtick must be used!!!

Rest and Spread operators#

  • denoted with three ellipses or periods
  • rest is used to represent an infinite number of arguments
  • spread is used to allow an iterable object to be expanded
  • rest destructuring
function fn(num1, num2, ...args) { } /// args always goes last
  • destructured rest
function fn(...[n1, n2, n3]) {}
  • spread operator
function myFunction(n1, n2, n3) { ... } const values = [ 1, 2, 3 ] myFunction(...values)
  • object destructuring
const { firstNamne, lastName } = obj
  • destructuring into a new variable
const { firstName: first, lastName } = objconsole.log(first)

Arrow functions#

  • anonymous, not bound to an identifier
  • are callable but not constructable (you can't use new operator)
// old wayTagComponent.prototype.render = function(){    getRequest(this.urlPath, function(data) {        // this points to getRequest() scope        displayTags(this.targetElement, ... tags)     })}
// a regular functionconst fn1 = function(a, b) { return a + b }
// arrow functionconst fn2 = (a, b) => { return a + b }
// arrow function without return keywordconst fn3 = (num1, num2) => num1 + num2

Array operations#

  • mutating arrays
const mutatingAdd = [1, 2, 3]mutatingAdd.push(4) // [1, 2, 3, 4]mutatingAdd.unshift(0) // [0, 1, 2, 3, 4]
// Immutable operations over arrays const arr1 = [1, 2]const arr2 = [3, 4]
const arr3 = arr1.concat(arr2) // [1, 2, 3, 4]const arr3 = [...arr1, ...arr2]// [1, 2, 3, 4]const arr4_altern = [0, ...arr1] //[0, 1, 2]
  • remove strings from an array
const arr = [0, 1, 2]const tailArr = arr.splice(-1) // [2]console.log(arr) // [0, 1]
  • slice and update
const numbers = [0, 1, 2, 3, 4]const lessThanThree = numbers.slice(0, 3) // [0, 1, 2]const moreThanTwo = numbers.splice(2, numbers.length) // [2, 3, 4] 
  • other functions
concat, entries, fill, filter, find, flat, flatMap, forEach, join, includes, keys, map, push, pop, reduce, reverse, shift, slice, sort, splice, unshift, values
  • sorting arrays
// sorting function: // if < 0, then a < b if == 0, then a == b if > 0, then b > anums.sort((a,b) => {  return a-b // ascending })
nums.sort((a,b) => {  return b-a // descending })
  • generating a sequence
const indices = Array.from(Array(10).keys())console.log(indices) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Maps#

  • when using objects as maps, their keys are always converted to strings
  • Maps can't be serialized into JSON, yet they can be iterated via for-of loop
let totalReplies = new Map()totalReplies.set(user1, 5) totalReplies.set(user2, 42)let has = totalReplies.has(user1)totalReplies.delete(user1)
  • WeakMap - only objects can be passed as keys, aren't iterable, better with memory

Sets#

let tags = new Set()tags.add('JavaScript')tags.add({version : '2015'})
let [first] = tags // first item

Classes#

  • old way (ES5)
function SponsorWidget(name, description, url){    this.name = name    this.description = description    this.url = url}
Sponsor.prototype.render = function(){    }
  • new way (ES6+), class is only a sugar syntax
class SponsorWidget {    myVar = 12    myOtherVar        constructor(name description, url){        this.name = name        this.description = description        this.url = url    }        render(){             let link = this._buildLink(this.url)    }        _buildLink(url) { // underscore is a convention for private method            }}
class SponsorWidget extends Widget {    constructor(name, description, url) {        super()    }        render() {        super.render() // parent version        let parsedName = this.parse(this.name)        let css = this._buildCss()    }}

Promises#

  • three states - pending, fulfilled, rejected
  • handlers - then(), catch(), finally()
  • when Promise.then() is called, it returns a new promise in the pending state
  • when Promise.catch() is called, it internally calls Promise.then(undefined, rejectHandler)
const myPromise = new Promise((resolve, reject) => {    reject(new Error())})
const myPromise = new Promise((resolve, reject) => {    setTimeout(() => { resolve('Done!')}) // also works})
  • error handling
myPromise.then(() => {}, error => console.log(error))
Promise.resolve('Resolve').then(console.log) // prints ResolvePromise.reject('Reject').catch(console.log) // prints Reject
  • combined promises
await Promise.all([    resolveAfter1Second(),    resolveAfter2Seconds(),])

Async/await#

  • async function returns a promise - all async functions can have then() and catch() handlers
  • if we don't return a promise, JS will do it automagically!
async function example1() {    return 'Hello' // JS will wrap this into a promise} async function example2() {    return Promise.resolve('World')}
  • await can be used only within an async function
  • error rejection
try {    const value1 = await Promise.reject('Error')} catch(err) {    }

Advanced structures#

Curried functions#

  • multiple arrow functions, can be used to wrap event handlers with additional parameters
const three = a => b => c => a + b + cthree(1)(2)(3)
// equivalent:const three = (a) => {    return (b) => {        return (c) => {            return a + b + c        }    }}

Iterators#

  • we have to define a function that takes a collection in as the parameter and returns an object which must have property next
  • when next is called, the iterator steps to the next value in the collection and returns an object with the value and the done status of the iteration
function createIterator(array){    let currIdx = 0    return {        next() {            return currIdx < array.lenth ? {                value: array[currIdx++], done: false,            } : { done: true }        },    }}

Generators#

  • provide an iterative way to build a collection of data
  • can be used for asynchronous processing also
function *nameList(){    yield 'Sam'   // { done : false, value: 'Sam' }    yield 'Tyler' // { done : false, value: 'Tyler' }}
for(let name of nameList()) {...} // usagelet names = [...nameList()]
function *gen() {    let i = 0    while(true) {        yield i++     }}

Tagged template literals#

  • can be parsed with tag functions and can return a manipulated string
function tagFunction(strings, param) {    return strings[0] + param} const tagged = tagFunction`We have ${num} param`

Enhanced object properties#

  • ES6 added 3 ways to simplify the creation of object literals
  • object properties
function getPersonES6(name, age, height) {    return {        name,        age,        height      }}
  • function declaration
function getPersonES5(name, age, height) {    return {        getAge: function() { return age }    }} function getPersonES6(name, age, height) {    return {        getAge() { return age }    }}
  • computed properties
const personES5 = {    lastName: 'Smith'}personES5[varName] = 'John' const personES6 = {    lastName: 'Smith',    [varName]: 'John',}

Readonly properties#

var a = {}Object.defineProperty(a, 'mojo', {  value: 15,  writable: false})

Getters and setters#

function Foobar () {    var _foo //  true private property      Object.defineProperty(obj, 'foo', {      get: function () { return _foo },      set: function (value) { _foo = value }    })}

Proxies#

function Foo() {    return new Proxy(this, {      get: function (object, property) {        if (Reflect.has(object, property)) {          return Reflect.get(object, property)        } else {          return function methodMissing() {            console.log(`You called ${property} but it doesn't exist!`)          }        }      }    })}

Modules#

  • old way (ES5)
// anonymous namespaces (function () {    // a self contained namespace     window.foo = function() {        // exposed closure     }})() // execute immediately
  • new way (ES6+)
    • we can use require or import
// flas-message.jsexport default function(message){    alert(message)}
// app.jsimport flashMessage from './flash-message'flashMessage('Hello')

Tips and tricks#

Creating own trim function#

String.prototype.trim = function(){return this.replace(/^s+|s+$/g, '')}  

Transforming arguments object into an array#

var argArray = Array.prototype.slice.call(arguments)

Closures inside loops#

  • wrong
var funcs = []for (var i = 0; i < 3; i++) {  funcs[i] = function() {    console.log('i value is' + i) // i value is 3, always!  }}
  • good
for (var i = 0; i < 3; i++) {    funcs[i] = (function(value) {      console.log('i value is ' + i) // 0, 1, 2    })(i)}
  • even better
for (let i = 0; i < 3; i++) {    funcs[i] = () => {      console.log('i value is ' + i) // 0, 1, 2    }} 

Remove duplicities from an array#

const arr = [...new Set([1, 2, 3, 3])] // [1, 2, 3]

Get the last item in an array#

let array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]console.log(array.slice(-1)) // Result: [9]

Assigning operator in a constructor#


class Polygon {  constructor(options) { // many attributes    Object.assign(this, options)  }}

Freezing properties of an object#


const obj = { foo1: 'bar1', foo2: { value: 'bar2' } };
Object.freeze( obj );
obj.foo = 'foo'; // doesn't change the property, doesn't throw Errorobj.foo2.value = 'bar3'; // does change the value - it's nested!

Deep cloning and comparing#

JSON.stringify(myObj1) === JSON.stringify(myObj2) 

Weird parts of JavaScript#

false.toString() // false function Foo() { } Foo.bar = 1 
2.toString()   // SyntaxError, dot here means a floating point literal (2).toString() // OK 2 .toString()  // OK2..toString()  // OK
var foo = {} // new object, derives from Object.prototype 
// property access var foo = { name : 'kitten' }foo.name    // OK foo['name'] // OK var get = 'name'foo[get]    // OK foo.1234    // ERROR foo['1234'] // OK
// delete is the only way how to remove a property// undefined or null only removes the VALUE var obj = { bar: 1 }obj.bar = undefined // removes value delete obj.bar // removes key
// you can't delete global variables declared by var!var mojo = 12// use assignment to a global property instead, // if you need to create a global variableglobal.mojo = 12
// arrays new Array(3)   // [undefined, undefined, undefined], new Array('3') // ['3']

// equality operator '' == '0'              // false 0 == ''                // true 0 == '0'               // truefalse == 'false'       // false false == '0'           // true false == undefined     // false false == null          // false null == undefined      // true { } === {}             // false new String('foo') === 'foo' // false 10 == '10'             // true 10 == '+10'            // true 10 == '010'            // true isNan(null) == false   // true, null converts to 0new Number(10) === 10  // false, object !== number 
// typeof operator 'foo'                  // string new String('foo')      // object true                   // boolean [1, 2, 3]              // object new Function()         // function 
// casting '' + 10 === '10'       // true !!'foo'                // true !!true                 // true
// boolean evaluationnew Boolean()           // falsenew Boolean(0)          // false (for any falsy parameter)// but careful, (new Boolean(anything) === true/false) -> always FALSEnew Boolean(true)        // true (for any truthy parameter)

// why is this comma recommended? Because it prevents any previous code from // executing your code as the arguments to that function;(async () => { ... }