knowledge-kitchen

Introduction Contemporary Javascript for Programmers of Other Languages

For programmers of other languages.

  1. Overview
  2. The Basics
  3. Objects
  4. Regular Functions
  5. Anonymous Functions
  6. Arrow Functions
  7. Immediately-Invoked Function Expressions
  8. Arrays
  9. Higher-Order Functions
  10. Asynchronicity
  11. Using APIs
  12. Destructuring
  13. Importing & Exporting Modules
  14. Implementation Differences
  15. Conclusions

Overview

Concept

Javascript, also known as ECMAScript, has a few unusual features that trouble programmers of other languages.

Try it yourself

Try out the code examples in this deck for yourself: https://github.com/nyu-software-engineering/javascript-crash-course

The Basics

Variable declaration

Three keywords to declare a variable:

Let example

// non-constant variables are declared with the let keyword
let it = true // it's true!

it = it ? false : true // reassign it to be its opposite using the ternary operator

console.log(`It is ${it}!`) // expected output: 'It is false!'

Const example

// constants are declared with the const keyword
const data = [10, 20, 30] // a variable that can't be reassigned!

data[1] = "ha!" // but this is not variable reassignment!

console.log(data) // expected output: [10, 'ha!', 30]

Scope example

Imagine the following code?

let x = 5

{
  let x = 10
}

console.log(x)

What is output?

Output to console

Basic output to the console

let stuff = "some text"

console.log("This is " + stuff + ".") // expected output: 'This is some text.'

String templates

Template syntax enapsulating a string with backtics

let stuff = "some text"

console.log(`This is ${stuff}.`) // expected output: 'This is some text.'

Template syntax will accept any expression

const x = 5
const y = 10

console.log(`The result is ${x + y}.`) // expected output: 'The result is 15.'

For loops

The expected behavior.

// iterate 5 times
for (let i = 1; i < 6; i++) {
  console.log(`For loop iteration #${i}`)
}
// infinite loop
for (let i = 1; true; i++) {
  console.log(`For loop iteration #${i}`)
}
// finite loop
for (someValue in someArray) {
  console.log(`The value is ${someValue}`)
}

… there are more for loop variations when it comes to arrays

While loops

The expected behavior.

// iterate 5 times
let i = 1
while (i < 6) {
  console.log(`While loop iteration #${i}`)
  i++
}
let i = 0
while (true) {
  console.log(`While loop iteration #${i}`)
  i++
}

Equality

The == operator compares by value with type coercion.

1 == "1" // true
true == 1 // true
undefined == null // true

Equality

The === operator compares by value without any type coercion.

1 === "1" // false
true === 1 // false
undefined === null // false

Semi-colons

Semi-colons are optional, as long as each statement is written on its own line.

1 === "1" // false
true === 1 // false
undefined === null // false

Regular Functions

The function keyword.

// define a function the most simple way
function doSomething1() {
  console.log("doSomething1 is running")
}
doSomething1() // call the function

And with parameters/arguments…

// define a function the most simple way with two parameters
function doSomething2(x, y) {
  console.log(`doSomething2 is running with x=${x} and y=${y}.`)
}
doSomething2("hello", "world") // call the function with arguments

And with a return value…

// define a function the most simple way with two parameters and a return value
function doSomething3(x, y) {
  const msg = `doSomething3 is running with x=${x} and y=${y}.`
  return msg
}
console.log(doSomething3("hello", "world")) // call the function and use its return value

Anonymous Functions

Functions in Javascript can be nameless.

// define an anonymous function
function () {
  console.log("doSomething1 is running")
}

You may be wondering how you call a function with no name.

One way to call an anonymous function is to assign a variable to point to it.

// define an anonymous function, but assign a variable to refer to it
const doSomething1 = function () {
  console.log("doSomething1 is running")
}

doSomething1() // call the function

And with parameters

// define an anonymous function with parameters, but assign a variable to refer to it
const doSomething2 = function (x, y) {
  console.log(`doSomething2 is running with x=${x} and y=${y}.`)
}

doSomething2("hello", "world") // call the function with arguments

And with a return value…

// define an anonymous function with parameters and a return value, but assign a variable to refer to it
const doSomething3 = function (x, y) {
  const msg = `doSomething3 is running with x=${x} and y=${y}.`
  return msg
}

console.log(doSomething3("hello", "world")) // call the function and use its return value

Arrow functions

A simplified function syntax without the function keyword.

// an arrow function with no parameters or return value
const doSomething1 = () => {
  console.log("doSomething1 is running")
}

doSomething1() // call the function

And with parameters…

// an arrow function with parameters
const doSomething2 = (x, y) => {
  console.log(`doSomething2 is running with x=${x} and y=${y}.`)
}

doSomething2("hello", "world") // call the function with arguments

And with a return value…

// an arrow function with parameters and a return value
const doSomething3 = (x, y) => {
  const msg = `doSomething3 is running with x=${x} and y=${y}.`
  return msg
}

console.log(doSomething3("hello", "world")) // call the function and use its return value

Function syntax can be even further reduced if all it does is return a value.

const doSomething4 = (x, y) => `doSomething4 is running with x=${x} and y=${y}.`

console.log(doSomething4("hello", "world")) // call the function and use its return value

Even further reduced syntax with no parentheses around a single parameter:

const doSomething5 = x => `doSomething5 is running with x=${x}.`

console.log(doSomething5("hello")) // call the function and use its return value

Note that with this shorthand return statement, if the value returned is an object, that object must be encapsulated in parentheses.

const getFoo = () => ({ first: "Foo", last: "Barstein" })

Immediately-Invoked Function Expressions

Wrapping a function in parentheses turns it into an expression.

;(function doSomething() {
  console.log("doing something")
})

Note that it is convention to place a semi-colon (;) before an immediately-invoked function in order to ensure it is interpreted as a separate statement from whatever comes above it in the code.

This allows it to be immediately called, if desired.

;(function doSomething() {
  console.log("doing something")
})()

An anonymous arrow function turned into an IIFE:

;(() => {
  console.log("doing something")
})()

Put onto one line.

;(() => {
  console.log("doing something")
})()

Similar, but different.

;(message => console.log(message))("doing something")

Objects

Instantiating objects

Javascript objects are usually instantiated directly, without the need for a class.

// an object
const me = {
  name: "Foo Barstein",
  phone: "212-666-1212",
  age: 63,
  isRobot: false,
}

Methods can be tacked onto extant objects.

me.speak = function (message) {
  console.log(`${this.name} says, "${message}".`)
}

me.speak("Hello!") // expected output: 'Foo Barstein says, "Hello!".'

Instance methods

There is a special shorthand syntax if declaring methods within objects

const fido = {
  name: "Fido",
  breed: "Schnauzer",
  bark(message) {
    console.log(`${this.name} the ${this.breed} says, '${message}'`)
  },
}

fido.bark("Woof!")

Creating an object from another object

Javascript uses prototypal inheritance, where one object uses another as the prototype from which it is based.

// make a copy of an object - this is prototypal inheritance
const robotMe = Object.create(me) // creates a new object based on the original as its prototype
robotMe.isRobot = true // modify this new object in some way

console.log(`me ${me.isRobot ? "is indeed" : "is not"} a robot.`) // expected output: "My name is Foo Barstein"
console.log(`robotMe ${robotMe.isRobot ? "is indeed" : "is not"} a robot.`) // expected output: "My name is Foo Barstein"

Changes to methods or properties can be added onto the cloned object to differentiate it from its “parent”.

// add robot-specific speach abilities
robotMe.speak = function (message) {
  console.log(
    `${this.name} the robot states in monotonous voice, "${message}".`
  )
}

Creating an object from another object (continued)

Instead of using Object.create(me), we could have constructed the robotMe from the me prototype manually, using the special __proto__ field:

const robotMe = {
  isRobot: true, // override the prototype's isRobot property
  speak(message) {
    // override the prototype's speak method
    console.log(
      `${this.name} the robot states in monotonous voice, "${message}".`
    )
  },
  __proto__: me, // specify which object to use as the prototype
}

Whichever way you do it, the properties and methods of the prototype are inherited, accessible, and writeable, e.g.:

Methods in objects

The this keyword refers to the object a method is called on.

// an object with a method belonging to it
const fido = {
  name: "Fido",
  breed: "Schnauzer",
  bark(message) {
    console.log(`${this.name} the ${this.breed} says, '${message}'`)
  },
}

// call the bark method on the fido object
fido.bark("Woof!") //expected output: "Fido the Schnauzer says, 'Woof!'"

Functions are classes… or classes are functions?!

Objects can alternatively be instantiated by calling a “constructor” function using the new keyword.

function Dog(name, breed) {
  this.name = name
  this.breed = breed
  this.bark = message => {
    console.log(`${this.name} the ${this.breed} says, '${message}'`)
  }
}

// instantiate an object and call its method
let fido = new Dog("Fido", "Schnauzer")
fido.bark("Woof!")

See more about prototypal inheritance and constructor functions at MDN’s excellent Javascript documentation

Constructing objects with a class definition

Objects can alternatively be instantiated from a class definition. This is syntactic sugar and not a real feature of the language.

class Dog {
  constructor(name, breed) {
    this.name = name
    this.breed = breed
  }
  bark(message) {
    console.log(`${this.name} the ${this.breed} says, '${message}'`)
  }
}

// instantiate an object and call its method
let fido = new Dog("Fido", "Schnauzer")
fido.bark("Woof!")

Arrays

Basics

Arrays behave as expected.

// generate an array from a string
let fruits = "avocado,tomato,banana".split(",") // returns ['avocado, 'tomato', 'banana']
console.log(`Do you love ${fruits[1]}?`) // expected output "Do you love tomato?"
// add an element to an array
fruits.push("pepper") // fruits now has ['avocado, 'tomato', 'banana', 'pepper']
// find index position of element by value
const fruit = "pepper"
let pos = fruits.indexOf(fruit) // returns 3
console.log(`${fruit} is located in the array at index ${pos}.`)

Basics

Continued.

// delete last element from the array
fruits.pop() // fruits now has ['avocado, 'tomato', 'banana']
// indexOf returns -1 if value not found in arraay
pos = fruits.indexOf("pepper") // 'pepper' is no longer there, so -1 is returned
console.log(`${fruit} is now located in the array at index ${pos}.`)
// join all elements in array into a comma-separated string
const stringAgain = fruits.join(",") // returns 'avocado,tomato, banana'
console.log(`The fruits are ${stringAgain}.`)

forEach function

Calls a function for each element in an array.

const numbers = [65, 44, 12, 4]

numbers.forEach(n => {
  console.log(n)
})

Map function

Creates a new array containing the results of a function call for each array element. Useful for transforming array values.

const numbers = [65, 44, 12, 4]

const newNumbers = numbers.map(n => n * 10)

// newNumbers now refers to [650, 440, 120, 40]

Filter function

Create a subset of an array.

let fruits = "avocado,tomato,banana".split(",") // ['avocado, 'tomato', 'banana']

// create a new array with a subset of the original values
fruits = fruits.filter((val, i, arr) => {
  return val != "banana" // returns true for all fruits except 'banana'
})

console.log(fruits) // expected output: ['avocado', 'tomato']

Arrays of objects

A common data structure used by APIs to pass data between clients and servers is an array of objects.

const products = [
  {
    id: 1,
    title: "Boa, emerald green tree",
    price: "$31.82",
    description: "Sed ante. Vivamus tortor. Duis mattis egestas metus.",
  },
  {
    id: 2,
    title: "Bleu, blue-breasted cordon",
    price: "$35.66",
    description:
      "Praesent blandit. Nam nulla. Integer pede justo, lacinia eget, tincidunt eget, tempus vel, pede.",
  }, // imagine there were more product objects...
]

Iterating through an array of objects

Any of the standard array iteration methods (forEach, map, or filter) can be used to iterate through an array of objects, e.g.:

// loop through each product
products.forEach(product => {
  // print the title and price of each product
  console.log(`${product.title} - ${product.price}`)
})

Higher Order Functions

Functions can be passed as arguments to other functions.

// a simple function that prints out its argument
const foo = val => {
  console.log(`val = ${val}`)
}
// another function that takes another function as its argument
const bar = (func, arg) => {
  func(arg) // call the function that was received, whatever it is, and pass it the argument that was received
}
// pass the foo function to the bar function as an argument
bar(foo, 5)

Asynchronicity

Overview

Javascript has three features to handle asynchronicity in code.

Callbacks

Callbacks are functions that will be called when a certain task completes.

// a function that accepts two callbacks - one for success, one for failure
let doIt = (callbackSuccess, callbackFailure) => {
  const someCondition = doWhateverWorkWeNeedToDo() // imagine this function did an asynchronous task and then...
  if (someCondition)
    callbackSuccess("hooraah") // call the success callback function
  else callbackFailure("boo!") // call the failure callback function
}

// call the function, pass two callback functions to handle its success or failure
doIt(
  res => {
    console.log(`Success: ${res}`)
  },
  err => {
    console.log(`Failure: ${err}`)
  }
)

Promises

Promises are a language feature that allow programmers to specify two functions: one to be called automatically when a certain asynchronous task is successfully completed and another to be called when that task fails.

// a function that returns a Promise
let doIt = () => {
  return new Promise((resolve, reject) => {
    const someCondition = doWhateverWorkWeNeedToDo() // imagine this function did an asynchronous task and then...
    if (someCondition) resolve("hooraah") // call the success callback function
    else reject("boo!") // call the failure callback function
  })
}

// call the function, pass functions to handle its success or failure, whenever it completes
doIt()
  .then(res => {
    console.log(`Success: ${res}`)
  })
  .catch(err => {
    console.log(`Failure: ${err}`)
  })

Async

The async keyword works on top of Promises to make their syntax simpler to read.

let doIt = async () => {
  // this function automatically returns a Promise, even if it doesn't say so
  doWhateverWorkWeNeedToDo() // imagine this function did an asynchronous task and then...
  return "hoorah!" // 'hoorah' will be automatically passed as the argument to the resolve function of the Promise
}

// we can the function call as if it returns a Promise, which it does.
doIt()
  .then(res => {
    console.log(`Success: ${res}`)
  })
  .catch(err => {
    console.log(`Failure: ${err}`)
  })

Await

The await keyword forces the program to wait for a Promise to complete before moving on to the next line of code. It allows us to write code in a familiar manner, even when dealing with asynchronous Promises.

let doIt = async () => {
  // this function automatically returns a Promise, even if it doesn't say so
  doWhateverWorkWeNeedToDo() // imagine this function did an asynchronous task and then...
  return "hoorah!" // 'hoorah' will be automatically passed as the argument to the resolve function of the Promise
}

try {
  const res = await doIt()
  console.log(`Success: ${res}`) // if the line above does not throw an error, then the promise was resolved and we have success!
} catch (err) {
  console.log(`Failure: ${err}`) // the promise was rejected, so we have failure
}

Await (continued)

There is only one complication: await can only be used within an async function.

let doIt = async () => {
  // this function automatically returns a Promise, even if it doesn't say so
  doWhateverWorkWeNeedToDo() // imagine this function did an asynchronous task and then...
  return "hoorah!" // 'hoorah' will be automatically passed as the argument to the resolve function of the Promise
}

// await is only allowed within an async function, so make an async function
let start = async () => {
  try {
    const res = await doIt() // run the function that returns a Promise, wait for completion
    console.log(`Success: ${res}`) // handle success
  } catch (err) {
    console.log(`Failure: ${err}`) // handle failure
  }
}
start() // immediately call the async function

Await (continued again)

A standard try/catch statement is the simplest way to handle both success and failure of a Promise when using await.

let doIt = async () => {
  // this function automatically returns a Promise, even if it doesn't say so
  doWhateverWorkWeNeedToDo() // imagine this function did an asynchronous task and then...
  return "hoorah!" // 'hoorah' will be automatically passed as the argument to the resolve function of the Promise
}

let start = async () => {
  // await is only allowed within an async function
  try {
    const res = await doIt() // run the function that returns a Promise, wait for completion
    console.log(`Success: ${res}`) // handle success
  } catch (err) {
    console.log(`Failure: ${err}`) // handle failure
  }
}
start()

Using APIs

Concept

When a client requests data from a server, that data must be returned in a particular format the client can parse.

Fetching data from APIs requires asynchronous programming. Node.js’s built-in https module can be used to fetch data asynchronously, but its syntax leaves a lot ot be desired.

const https = require("https") // using Node's built-in http module for http requests
const apiUrl = "https://my.api.mockaroo.com/users.json?key=d9ddfc40" // a mock-API for demo purposes that returns a JSON array of objects

// make an HTTP request to the API server
https
  .get(apiUrl, response => {
    response.on("data", chunk => {
      // this callback function is called automatically every time a chunk of data has been recieved from the server.
    })

    response.on("end", () => {
      // this callback function is called automatically when all data has been received from API
    })
  })
  .on("error", err => {
    // this callback function is called automatically if there is an error
  })

A syntactically simpler way to fetch data from APIs would be to use the 3rd-party axios module, which must be downloaded an installed into a project (e.g. npm install axios).

const axios = require("axios") // using the axios module for http requests
const apiUrl = "https://my.api.mockaroo.com/users.json?key=d9ddfc40" // a mock-API for demo purposes that returns a JSON array of objects

// make an HTTP request to the API server
axios
  .get(apiUrl)
  .then(response => {
    // this callback function is called automatically when all data has been received from API
    // do something when the Promise is resolved and response.data is received successfully
  })
  .catch(err => {
    // this callback function is called automatically if there is an error
  })

The same code, but using async/await:

const axios = require("axios") // using the axios module for http requests
const apiUrl = "https://my.api.mockaroo.com/users.json?key=d9ddfc40" // a mock-API for demo purposes that returns a JSON array of objects

// make an HTTP request to the API server
// define an async function so we can use the await keyword to wait for the API's response
const start = async () => {
  try {
    const response = await axios.get(apiUrl) // await the response before moving on
    // do something when the Promise is resolved and response.data is received successfully
  } catch (err) {
    // do something when the Promise is rejected, meaning there is an error
  }
}
start() // call the async function immediately

Destructuring

Arrays

Values in an array can be extracted into separate variables upon assignment:

const [a, b] = [10, 20, 30, 40, 50] // a=10, b=20

The remaining values can be lumped together - this is rest syntax:

const [c, d, ...rest] = [10, 20, 30, 40, 50] // c=10, d=20, rest=[30,40,50]

Function parameters can follow this same syntax:

function foo(a, b, ...args) {
    console.log(`a = ${a}, b = ${b}`, args = ${args}`) // args = [30, 40, 50]
}
foo(10, 20, 30, 40, 50) // pass values as separate arguments

Spread syntax

Spread syntax is the opposite of rest syntax - it dissolves an array into separate values in particular contexts.

Such as when passing an array of arguments to a function.

// a function that accepts two separate arguments
function sum(x, y) {
  return x + y
}
let args = [10, 20]
let total = sum(...args) // spread the values in the array into multiple arguments

Or copying values from one array into another…

let myVals = [1, 2, 3, ...[4, 5, 6]] // [1, 2, 3, 4, 5, 6]

Objects

Values within an object can be destructured.

Take this object with a nested sub-object as a starting point…

const person = {
  age: 72,
  name: "Foo Barstein",
  email: "fb1258@teleworm.us",
  address: {
    street: "92 Rue Jamil Sedki",
    city: "Beni Brahim",
    country: "Tunisia",
    postalCode: 7040,
  },
}
const { name, age } = person // name='Foo Barstein', age=72
console.log(`name = ${name}`) // expected output: 'Foo Barstein'
console.log(`age = ${age}`) // expected output: 72

Objects

Variable names do not need to match field names:

const { name: fullName, age: apparentAge } = person // { fullName: 'Foo Barstein', apparentAge: 72 }
console.log(`fullName = ${fullName}`) // expected output: 'Foo Barstein'
console.log(`apparentAge = ${apparentAge}`) // expected output: 72

Nested objects can be destructured as well:

const {
  email,
  address: {
    city, // create a variable for this field only
  },
} = person
console.log(`email = ${email}`) // expected output: 'fb1258@teleworm.us'
console.log(`city = ${city}`) // expected output: 'Beni Brahim''

Importing & Exporting

Confusão organizada

Javascript today allows modular reuse of your own code or that of 3rd parties through its module functionality.

CommonJS exporting

Basic CommonJS exporting syntax:

// imagine you had a variable foo, a function bar, and an object baz - all can be exported

// export these all to make them available for import into another Javscript file
module.exports = { foo, bar, baz } // note the CommonJS syntax!

With CommonJS, you could also have exported each variable individually:

module.exports.foo = 5 // a number
module.exports.bar = () => {
  console.log("hello world!")
} // a function
module.exports.baz = { x: 5, y: 10 } // an object

Or even without the module prefix:

exports.foo = 5 // a number
exports.bar = () => {
  console.log("hello world!")
} // a function
exports.baz = { x: 5, y: 10 } // an object

CommonJS importing

Import named exports using the require function (note the destructuring syntax):

const { foo, bar, baz } = require("./10.exporting")

Or import the entire module and access the named exports separately:

const myModule = require("./10.exporting") // import module as a whole

console.log(myModule.foo) // access a value exported in the module
myModule.bar() // access a function exported in the module

ES6 exporting

Basic ES6 exporting syntax:

// imagine you had a variable foo, a function bar, and an object baz - all can be exported

// export these all to make them available for import into another Javscript file
export { foo, bar, baz } // note the ES6 syntax!

With ES6, you could also have exported each variable individually:

export let foo = 5 // a number
export let bar = () => {
  console.log("hello world!")
} // a function
export let baz = { x: 5, y: 10 } // an object

Or you can specify one of them as the default export, if desired:

export let foo = 5 // a number
export default let bar = () => { console.log('hello world!') } // a function
export let baz = { x: 5, y: 10 } // an object

ES6 importing

Import named exports using the import keyword (note the destructuring syntax):

import { foo, bar, baz } from "./10.exporting"

Or give each import a different name than in the module:

import { foo as f, bar as b1, baz as b2 } from "./10.exporting"

If the module specified a default export, we can import only that - note the syntax:

import bar from "./10.exporting"

We can also import the default export in combination with some named exports:

import bar, { foo, baz } from "./10.exporting" // the default export is 'bar'

Which import/export standard should you use?

The ES6 way is now standard for new projects. Use import and export in your code, and not require and module.exports.

You may still encounter the CommonJS way in older code.

Implementation Differences

Differences

Javascript can be run either within a web browser (i.e. as client-side code within a web page) or in a more open environment outside of the browser (including as server-side code). There are some key differences.

Differences (continued)

Browser example

Imagine the following HTML code for a button and a picture of a donkey.

<button id="clickme">Click me please</button>
<img id="neigh" src="images/donkey.jpg" />

And let’s say the following CSS code hid the donkey:

/* this will cause the donkey to be hidden when the page first loads */
img#neigh {
  display: none;
}

The following client-side Javascript, using the popular JQuery framework, will wait for the user to click the button and then will run the callback function:

$("button#clickme").click(function () {
  // make donkey will appear
  $("img#neigh").show()
})

Server example

The following server-side code uses the popular Express.js framework.

// set up a 'route'... i.e. a callback function to run when a particular request comes in
app.get("/foo", (request, response) => {
  response.send("bar") // send the response
})

// another one just for fun... this one waits for an HTTP POST request for the same URL
app.post("/foo", (request, response) => {
  // let's assume the client has sent some POST data with the user's name
  response.send(`Hello, ${req.body.name}!`) // send the response
})

Conclusions

You are now primed dive into the web browser’s implementation of Javascript, as well as configuring projects with NPM and Express.js and React’s implementations!