← Home

JavaScript basics

Objects

Property definition

To define an object’s properties:

let john = {
	name: "John",
	age: 123
};

For a multi-word property, use string notation:

let user = {
	name: "John",
	"likes birds": true // string notation
};

A computed property is a property set at runtime by a variable. To define a computed property, use a bracketed variable:

let fruit = prompt("Which fruit to buy?", "apple");

let bag = {
	[fruit]: 5 // computed property in bracketed variable
};

When an object’s key and value (usually a variable) have the same name, you can use shorthand property definition:

function makeUser(name, age) {
	return {
		age //  shorthand for `age: age`
	};
}

Property access

To access a property, use dot access or bracket access:

let user = { name: "john" };
user.name; // dot access
user["name"]; // bracket access

Use bracket access for variables:

let myVar = "name";
user[myVar]; // computed property bracket access

Use bracket access for multi-word properties:

let user = { "likes birds": true };
user["likes birds"]; // bracket access

Property check

To check if an object has a property:

let user = { name: "John", age: 30 };
"age" in user; // → true
"whoa" in user; // → false

To check if an object’s property is its own (i.e., not inherited):

john = { age: 20 };
john.hasOwnProperty("age"); // `age` is defined on `john`

Property deletion

To delete an object’s property:

delete john.age;

Object destructuring

Destructuring means picking properties from an object, declaring variables named after them and initializing them with their values.

let options = {
	title: "Square",
	width: 20,
	height: 30
};

let { title, width, height } = options;
// three new variables, initialized with their values

Destructuring allows for variable renaming:

let options = {
	width: 100,
	height: 200
};

let { width: w, height: h, title } = options;

Destructuring can be done inside parameter brackets:

const fetchRepos => ({
	language,
	minStars,
	maxStars,
	createdBefore,
	createdAfter
}) => {
	// functionality
}

fetchRepos ({
    language: "Javascript",
    maxStars: 5,
    etc.
});

There, destructuring can also set default values for parameters:

const fetchRepos => ({
	language="JavaScript",
	minStars=5,
	maxStars,
	createdBefore,
	createdAfter }) {...}

Operators

Associativity

var a = 2, b = 3, c = 4;

a = b = c;

console.log(a);4
console.log(b);4
console.log(c);4

= has right-to-left associativity, so a = b = c; means “set c (whose value is 4) equal to b (so its value is 4) and set b equal to a (so its value is 4).

In fact, =, as any operator is a function that takes in arguments and returns out a value, so b = c sets b equal to c and outputs 4, meaning that the next part that is evaluated is a = 4, which sets a equal to 4.

  • first call b = c
  • second call a = 4

Interesingly, associativity comes into play with the less than and greater than operators.

console.log(3 < 2 < 1); // → true

This returns true because < has left-to-right associativity, so…

3 < 2; // → false
false < 1;

false is coerced to 0 and thus 0 < 1 → true

Left-to-right associativity also explains the meaning of errors.

var english = {};

english.greetings.greet;
// → undefined.greet → error: Cannot set property "greet" of undefined

Increment and decrement operators

The operators ++ and -- can be placed either before or after a variable.

If the result of increment/decrement is not used, there is no difference in which form to use:

let counter = 0;
counter++;
++counter;
alert(counter); // 2, the two lines above had the same effect

To increase a value and immediately use the result of the operator, use the prefix:

let counter = 0;
alert(++counter); // 1

To increment a value but use its previous value, use the postfix:

let counter = 0;
alert(counter++); // 0

Rest/spread operators

There is an easy way to distinguish between them:

Use ... at a function definition to gather the remaining parameters into a single parameter.

Use ... at a function call to expand an array or object, so that you get the contents of the expanded variable.

In an array:

var originalArray = [1, 2, 3];
var biDimensionalArray = [a1, 4]; // => [[1, 2, 3], 4]
var longerFlatArray = [...a1, 4]; // => [1, 2, 3, 4]

In an object:

var o1 = { a: 1, b: 2 };
var o2 = { ...o1, c: 3 }; // => {a: 1, b: 2, c: 3}

Logical operators

The logical operators are && (and) and || (or).

Short-circuit evaluation

The && operator evaluates from left to right, converts each operand to a boolean, and: if all operands are truthy, it returns the last operand; if any operand if falsy, it returns false.

value1 && value2; // returns value2 if both are truthy
value1 && value2; // returns value1 if value1 is falsy
value1 && value2; // returns `false` if both iares falsy

If you know value2 to be truthy, then you can use value1 as a condition, so that if value1 is false, value2 will not be reached, whereas if value1 is true, value2 will be reached and returned.

Keywords

switch

A switch statement can replace multiple if checks.

switch(x) {
    case 'value1':  // if (x === 'value1')
    ...
    break;
    case 'value2':  // if (x === 'value2')
    ...
    break;
    default:
    ...
    break;
}

undefined and null

undefined means “uninitialized”, i.e. “value does not exist because it has not been assigned”.

null means “empty”, i.e. “value exists and is empty”.

The more precise way to check for undefined is to use typeof. Use comparison to undefined rather than truthiness to test for undefined values.

Primitives

A primitive represents a single value, i.e. it can contain only a single entity (string, number, etc.). In contrast, an object stores a collection of data.

  • undefined
  • null
  • boolean
  • String
  • Number
  • Symbol

JSON

JSON is a string format inspired by JavaScript’s object literal notation, but it is different from it. JSON is intended for sending across the wire/network. It is not an object, but a string. JSON is a faster alternative to XML.

<object>
	<firstname>Mary</firstname>
	<lastname>Johnson</lastname>
</object>

JSON.stringify() converts an object into a JSON string, i.e. a JSON-encoded string, also called “serialized” string. In a JSON string, the object starts and ends with curly braces and, inside it, key and values must always be wrapped in double quotes, except for booleans.

JSON.parse() converts a JSON string back into a JavaScript object literal.

let student = {
	name: "John",
	age: 30,
	isAdmin: false,
	courses: ["html", "css", "js"],
	wife: null
};

let json_string = JSON.stringify(student);

alert(json);
/* JSON-encoded object:
    {
      "name": "John",
      "age": 30,
      "isAdmin": false,
      "courses": ["html", "css", "js"],
      "wife": null
    }
    */

Exporting and importing

Default export

// ES2015 style
export default class Employee { ... }

// CommonJS style
module.exports = class Employee { ... };

To import a default export, omit curly braces:

// ES2015 style
import Teacher from "./teacher";

// CommonJS style
const Teacher = require("./teacher");

Named export

// ES2015 style
export class Employee { ... }

// CommonJS style
module.Employee = class Employee { ... };

To import a named export, use curly braces:

import { Teacher } from "./teacher"; // ES2015 style

In the ES2015 style: If you exported without default, you need the curly braces when importing. If you exported with default, you omit the curly braces when importing.

Named imports require curly braces:

import { stuff } from "./Stuff";

Default imports do not require curly braces:

import faker from "faker";

Use named imports for your own code. Use default imports for third-party libraries.

Exception: Use default exports for React components. This way, importing the component does not require curly braces.

CommentDetail.js
class CommentDetail extends React.Component(props) {
	// ...
}

export default CommentDetail;
app.js
import CommentDetail from "./CommentDetail";

Functions

Callback

A callback is a function that is:

  • accessible by another function, and
  • called after the first function if that first function completes

A nice way of imagining how a callback function works is that it is a function that is “called at the back” of the function it is passed into.

A callback is a secondary function given to a main function, for the secondary function to be run when the main function is completed.

Generator

A generator is function that generates a sequence of values—not all at once, but on a per request basis.

Function declaration and expression

A function declaration, which features the function keyword, is hoisted and is usable anywhere in the script.

greet() // will work, because greet is hoisted
function greet() {...}

A function expression, which features an assignment via =, is created only when the execution flow reaches it and is usable only from then on.

greet();
// Will NOT work: "undefined is not a Function", because the execution conext
// first sets `greet` to `undefined` during its creation phase and only then
// executes code.
var greet = function() {...}
// REMEMBER: functions are hoisted, variable names are hoisted in uninitialized
// state, variable values are NOT hoisted.

Function declaration:

function foo() {
	// ...
}

Function expression - anonymous function:

let foo = function() {
	// ...
};

Function expression - named function:

let foo = function func() {
    ...
};

Arrow function:

let foo = (a, b) => {
	console.log("hello");
	return a + b;
};

// if you return directly, the curly braces can be ommitted

let foo = (a, b) => a + b;

// with a single argument, the brackets can be omitted

let foo = msg => console.log(msg);

Aside: Expressions and declarations

An expression returns a value:

var a;
a = 3; // → 3 (expression, the `=` operator returns a value)
1 + 2; // → 3 (expression, the `=` operator returns a value)

A statement/declaration does not return a value:

if (a === 3) {
	// an if statement containing an expression
	// ...
}

Default parameters

function greet(firstName, lastName, language = "en") {
	// ...
}

Alternative:

function greet(firstName, lastName, language) {
	language = language || "en";
	// ...
}

Immediately invoked function expressions (IIFE)

As the name implies, it is a function expression invoked immediately upon creation.

var greeting = (function(name) {
	console.log(name);
})("John"); // invoking and storing

(function(name) {
	return "Hello " + name;
})(); // invoking inside parens

(function(name) {
	return "Hello " + name;
})(); // invoking outside parens

IIFEs can be used to privatize variables, preventing namespace collisions. IIFEs prevent the global environment from being polluted.

IIFEs can also access the global object:

(function(global, name) {
	console.log(global.greeting);
})(window);

this

Summary: In the global scope or in an ordinary function scope, this points to the global object, but in an object method, this points to the owner object, and yet in a function nested inside an object method, this points to global again (the reference to the object is lost). As for the last point, the arrow function prevents losing the reference to the object, since the arrow function does not have its own this in its execution context, but inherits it from its outer scope.

1. Owner object (implicit binding in method)

The keyword this in a method refers to the object or instance whose method is being called. In a method call, this is always the object before the dot.

let mary = {
    sayHi() {
        alert(this.name);
    }
};

mary.sayName(); `this` is `mary` object

this in nested ordinary functions

The above is valid only if this is directly inside the method. But if this is in an ordinary function nested inside the method, this defaults to the global object.

function Person() {
	this.age = 0; // `this` is `Person` instance

	setInterval(function growUp() {
		// ordinary declaration
		this.age++;
	}, 1000); // `this` inside `growUp` is `global`
}

To fix this, an arrow function does not have its own this. The this value of the enclosing lexical scope is used; arrow functions follow the normal variable lookup rules. So while searching for this which is not present in current scope, an arrow function ends up finding the this from its enclosing scope.

function Person() {
	this.age = 0;

	setInterval(() => {
		// arrow function
		this.age++; // `this` is `Person` instance
	}, 1000);
}

Always use arrow functions for class methods. An arrow function keeps this in reference to the class instance, not the global object. In fact, with arrow functions, you do not need to bind a React component’s method in the constructor.

2. Bound object (explicit binding with special functions)

The keyword this in a method call refers to the instance whose method is being called. The binding between function and object is made by using call(), apply() or bind().

When a function is free-floating (not part of an object), you can call it together with an object using call() and apply() and you can bind the function to the object permanently using bind().

fn.call(obj, arg1, arg2) and fn.apply(obj, args) tell the fn that this refers to obj and its args are those passed in.

  • fn.call() accepts multiple arguments and calls the function,
  • fn.apply() accepts an array of arguments and calls the function,
  • fn.bind() accepts the object and multiple arguments and returns a new function consisting of the original function bound to the object and including any arguments passed in.

Now this refers to the object bound to the function.

let sayName = function() {
	console.log(this.name); // `this` is `global`
};

let john = { name: "John" };

sayName.call(john, otherArg1, otherArg2); // now `this` is `john` object

sayName.apply(john, otherArgsArray); // now `this` is `john` object

let newFn = sayName.bind(john); // now `this` is `john` object

In other words fn.call(obj) sets obj equal to the implicit argument this. This means that call allows us to take control of the this reference.

const obj = {
	num: 3,
	increment: function() {
		this.num++;
	}
};

const otherObj = {
	num: 10
};

obj.increment.call(otherObj);
// `otherObj` is set to `this` so the method of `obj`
// can be used with `otherObj`

Journey: obj is in global memory, increment is in obj, and increment is a function-object combo whose object side has a __proto__ link to Function.prototype where call is.

3. Newly created object: new

When a function is invoked using the new operator, this is set to the new object being created.

The keyword this refers to the newly created object.

let Animal = function(color, name, type) {
	this.color = color; // `this` is new object
	this.name = name; // `this` is new object
	this.type = type; // `this` is new object
};

let zebra = new Animal("black and white", "Zorro", "Zebra");

4. Default window object: global

If you call a function that contains the this keyword and do not provide an object, this will default to the window object.

let sayName = function() {
	console.log(this.name); // `this` is `global`
};

sayName(); // `global.name` is undefined

In this case, this becomes undefined in strict mode.