professional-programming/training/front-end/01-modern-javascript.md
2020-07-22 18:11:56 +02:00

11 KiB
Raw Blame History

Table of Contents

Modern JavaScript

Note: run code quickly with https://codesandbox.io/s/

Quirks

Undefined everywhere!

There are no required arguments in JavaScript:

function hello(name) {
  return name;
}

// No raise, will log "undefined"
console.log(hello());

// Here's how to compare to undefined
console.assert(typeof undefined === "undefined");

Printing and interacting with the console

// Do not leave console.log in your code! There are linters such as eslint that will check for their absence
console.log("hello");
console.assert(true === true);

Comparing scalar, arrays, and objects

Always use triple comparators (===) instead of double (==)

// ???
console.assert("1" == 1); // this is true!

// Better
console.assert(!("1" === 1));
console.assert("1" !== 1);

Comparing non-scalar

Applied on arrays and objects, == and === will check for object identity, which is almost never what you want.

console.assert({ a: 1 } != { a: 1 });
console.assert({ a: 1 } !== { a: 1 });

const obj = { a: 1 };
const obj2 = obj;
// This is true because obj and obj2 refer to the same object ("identity")
console.assert(obj == obj2);
console.assert(obj === obj2);

Use a library such as lodash to properly compare objects and array

import _ from "lodash";

console.assert(_.isEqual({ a: 1 }, { a: 1 }));
console.assert(_.isEqual([1, 2], [1, 2]));

Object methods

Object.assign, spread operator

Object.assign (ES 2015)

Array methods

Array.includes (ES7)

Object literals, assignment and destructuring

Objects

const toaster = { size: 2, color: "red", brand: "NoName" };

// Get ("destructure") one object key
const { size } = toaster;
console.assert(size === 2);

// Note: this also works with functions
function destructuredFunction({ color }) {
  return color;
}

console.assert(destructuredFunction({ color: "red" }) === "red");

// Get the rest with ...rest
const { color, brand, ...rest } = toaster;
console.assert(_.isEqual(rest, { size: 2 }));

// Set default
const { size2 = 3 } = toaster;
console.assert(size2 === 3);

// Rename variables
const { size: size3 } = toaster;
console.assert(size3 === 2);

Enhanced object literals:

const name = "Louis";
const person = { name };
console.assert(_.isEqual(person, { name: "Louis" }));

// Dynamic properties
const person2 = { ["first" + "Name"]: "Olympe" };
console.assert(_.isEqual(person2, { firstName: "Olympe" }));
// Btw, you can include quotes although nobody does this
console.assert(_.isEqual(person2, { firstName: "Olympe" }));

// Short form function
const es5Object = {
  say: function () {
    console.log("hello");
  },
};
es5Object.say();

const es6Object = {
  say() {
    console.log("hello");
  },
};
es6Object.say();

// Prototype and super()
const firstObject = {
  a: "a",
  hello() {
    return "hello";
  },
};

const secondObject = {
  __proto__: firstObject,
  hello() {
    return super.hello() + " from second object";
  },
};

console.assert(secondObject.hello() === "hello from second object");

Array

const theArray = [1, 2, 3];
const [first, second] = theArray;
const [first1, second2, ...rest] = theArray;

console.assert(first === 1);
console.assert(second === 2);
console.assert(_.isEqualWith(rest, [3]));

let and const

const constantVar = "a";

// Raises "constantVar" is read-only
constantVar = "b";

let theVar = "a";
theVar = "a";

// Note: this will work ok
const constantObject = { a: 1 };
constantObject.a = 2;
constantObject.b = 3;

// Raises: "constantObject" is read-only
constantObject = { a: 1 };

// const and let are block scoped. A block is enclosed in {}
{
  const a = "a";
  console.log({ a });
}
// Raises: ReferenceError: a is not defined
console.log({ a });

Note: try to use const as much as you can.

  • Those variables can't be reassigned. More constraints leads to safer code.
  • You can't define a const without providing its initial value.
  • Most people do this in modern JS.

Never use var:

  • var variables are initialized with undefined, while let and const vars are not initialized and will raise an error if used before definition.
  • var is globally or function-scoped, depending on whether it is used inside a function.
  • let and const are block-scoped
  • let and const cannot be reused for the same variable name

Hoisting

See Hoisting on MDN

Arrow functions

The first advantage of arrow function is that they're shorter to write:

// You can define a function this way:
const myFunction = function () {
  console.log("hello world");
};

// With an arrow function, you save a few characters:
const myArrowFunction = () => {
  console.log("hello world");
};

// Some things, like params parentheses, and function code brackets, are optional
const myFunctionToBeShortened = function (a) {
  return a;
};

// Shorter arrow function
const myFunctionToBeShortenedArrowV1 = (a) => {
  return a;
};

// Shortest arrow function
// Remove single param parenthesis, remove function code bracket, remove return
const myFunctionToBeShortenedArrowV2 = (a) => a;
console.assert(myFunctionToBeShortenedArrowV2(1) === 1);

How this works in arrow functions

Best practices

  • I usually keep the parameters parenthesis. If you add a parameter, you'll have to add them back.

Classes

class Toaster {
  constructor(color) {
    this.color = color;
  }

  dring() {
    return "dring";
  }
}

// Don't forget new!
// Raises: TypeError: Cannot call a class as a function
// const toaster = Toaster('red');

const toaster = new Toaster("red");
console.log(toaster.dring());

// Inheritance

class BunToaster extends Toaster {
  dring() {
    return super.dring() + " dring";
  }
}

const bunToaster = new BunToaster("red");
console.assert(bunToaster.dring() === "dring dring");

Those are my opinions about other class features:

  • Avoid using static methods, use plain functions instead.
  • Avoid using more than one level of inheritance.
  • Avoid using getter and setters (get and set).
  • Avoid using classes altogether.

Prototypal inheritance

Template literals

const longString = `multi
line
string`;

const name = "Louis";
// Template interpolation
const hello = `Hello ${name}`;

// You can have expressions
const hello1 = `Hello ${name + "!"}`;
const hello2 = `Hello ${name === "Louis" ? name : "Noname"}`;

Template tags

They are used in some libraries, like Apollo and Styled Components.

function templateTag(literals, ...expressions) {
  console.assert(_.isEqual(literals, ["hello ", ""]));
  console.assert(_.isEqual(expressions, [3]));
  return _.join(_.flatten(_.zip(literals, expressions)), "");
}

const result = templateTag`hello ${1 + 2}`;
console.assert(result === "hello 3");

Loops

for... of

Note: prefer using some functional constructs such as map, reduce, etc.

for (const i of [1, 2, 3]) {
  console.log({ i });
}
// 1, 2, 3

for (const key in { a: "aaa", b: "bbb" }) {
  console.log({ key });
}
// 'a', 'b'

Promises

This is only going to be an introduction to the magnificent world of promise.

  • Async functions (seen later) use promises as a building block.
  • Promise are indeed async in nature: the calling code continues executing the promise does its thing.
  • Some Web API return promises, including Fetch

Creating a promise

Note: we use TypeScript in this example, to clarify what's return. You can ignore the type annotations for now.

let isDone: boolean = true

const thePromise = new Promise((resolve, reject) => {
  if (isDone) {
    resolve("the work is done");
  } else {
    reject("this is still pending");
    }
    }

console.assert(thePromise === 'the work is done')

TODO

Consuming a promise

Chaining promises

Async functions

Modules

CommonJS syntax:

const lodash = require("lodash");

ES Module syntax:

import lodash from "lodash";

Imports

// Import all and provide under name
import * as toaster from "./toaster";

// Named import (same as object destructuring!)
import { defaultColor, defaultSize } from "./toaster";

// Renaming imports
import { defaultBrand as toasterDefaultBrand } from "./toaster";

// Default import
import createToaster from "./toaster";

// Import both defaults and other
// import createToaster, {defaultColor} from './toaster'

Exports

In toaster.js:

// Shorthand definition + export
export const defaultSize = 4;

// Alternative export syntax
const defaultBrand = "Moulinex";
export { defaultBrand };

// Default export
const createToaster = ({ size, color }) => ({ size, color });
export default createToaster;

// Note that you have a shorthand default export, but it's not recommended to
// use it as the export won't have a name.
// export default () => ({})

Other features

Optional chaining

References