Skip to content

Instantly share code, notes, and snippets.

@MatthewKosloski
Last active February 27, 2017 01:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MatthewKosloski/79b0fc8e3efe1cb985b4da6bf36763aa to your computer and use it in GitHub Desktop.
Save MatthewKosloski/79b0fc8e3efe1cb985b4da6bf36763aa to your computer and use it in GitHub Desktop.
JavaScript Guide

JavaScript Guide

Table of Contents

Coercion

These are both comparison operators. == is a simple comparison, while === is a strict comparison. In contrast to the former, the latter returns true if the operands are equal and of the same type. When in doubt, it is convention to always use ===. Learn more about comparison operators here.

The following returns false because they are different objects. 1 is a Number and '1' is a String.

console.log(1 === '1') // false

The following returns true because both operands are equivalent.

console.log(1 == '1') // true

Var vs. let

Var is globally scoped as opposed to let.

for(var i = 1; i <= 5; i++) {
  console.log(i); // 1-5
}
console.log(i); // 6

In ES6, for loops are much cleaner. They don't leave behind and variables such as i because it is only scoped to the code block (for loop). Read more about it here.

for(let i = 1; i <= 5; i++) {
  console.log(i); // 1-5
}
console.log(i); // i is undefined

Function Declaration vs. Function Expression

These are two ways used to create functions.

Declaration

A concise example:

function foo() { ... }

With function declarations, the function is hoisted to the top of the code. This means you can call it before or after it is created:

bar(); // 42
function bar() { return 42; }
bar(); // 42

Expression

A concise example:

var foo = function() { ... }

With function expressions, the functions are not hoisted. As a result, you cannot call the function before you create it:

baz(); // baz is not a function
var baz = function() { return 42; }
baz(); // 42

Prototype.method vs. this.method

Sometimes when you want to create objects in JavaScript, you create a constructor function. These are useful for instantiating multiple instances of things such as sliders and modals that possess their own properties and methods, or functions associated with objects. Additionally, with objects, these things have the ability to inherit from parent objects; however, we will talk about that at a different time.

An example of a constructor with both types of methods:

function Rectangle(x, y) {
    this.x = x;
    this.y = y;
    this.getArea = function() {
        return this.x * this.y;
    }
}

// prototype method
Rectangle.prototype.getAreaPrototype = function() {
    return this.x * this.y;
}

Prototype.method

With prototypes, you define the methods one time, and all of the instantiated objects will inherit them. You can also modify the prototype method at any time:

// define constructor
function Rectangle(x, y) {
  this.x = x;
  this.y = y;
  this.getArea = function() {
    return this.x * this.y;
  }
}

// apply prototype methods for inheritance
Rectangle.prototype.getAreaPrototype = function() {
    return this.x * this.y;
}

// instantiate objects
var rect1 = new Rectangle(1, 3);
var rect2 = new Rectangle(2, 5);

console.log(rect1.getArea()); // 3
console.log(rect1.getAreaPrototype()); // 3

console.log(rect2.getArea()); // 10
console.log(rect2.getAreaPrototype()); // 10

// redefine the prototype method
Rectangle.prototype.getAreaPrototype = function() {
    return this.x * this.y + ' units'
}

console.log(rect1.getAreaPrototype()); // 3 units
console.log(rect2.getAreaPrototype()); // 10 units

As you can see, redefining the prototype yields different results.

In constrast, methods attached via this are created with every instantiated object. This is bad for performance, for if you were to instantiate 1000 Rectangles, the this.getArea will be defined 1000 times, whereas Rectangle.prototype.getAreaPrototype will only be defined once. Despite this obvious disadvantage, methods applied to this have one benefit; they have access to private functions and values. Consider the following:

function Citizen(name, age) {
  var countryOfOrigin = 'United States';
  function _isLegalAge(age) {
    if(countryOfOrigin === 'United States') {
        return age === 21;
    } else {
      return age === 19;
    }
  }
  this.name = name;
  this.age = age;
  this.greet = function(anotherPerson) {
    if(countryOfOrigin === 'United States') {
      return `Hello, ${anotherPerson}!`;
    } else {
      return `Hallo, ${anotherPerson}.`;
    }   
  }
  this.drink = function(){
    var canDrink =  _isLegalAge(this.age);
    if(canDrink) {
        return `You can drink.`;
    } else {
        return `You cannot drink!`;
    }
  }
}

var Matthew = new Citizen('Matthew', 19);
console.log(Matthew.greet('Tom')); // Hello, Tom!
console.log(Matthew.drink()); // You cannot drink!

// private properties
console.log(Matthew.countryOfOrigin); // undefined
console.log(Matthew._isLegalAge(19)); // is not a function

As you can see, private values such as countryOfOrigin and methods like _isLegalAge are only accessible from within the constructor.

Immediately-Invoked Function Expression

IIFEs are function expressions that are immediately called. For example, the following is a function declaration; however, it is not called, so nothing will be logged.

function foo(){ 
    var i = 0;
    console.log(i);
};

In order to immediately call this code, one may assert that adding () at the end would work, right? Kinda like foo()?

function foo(){ 
    var i = 0;
    console.log(i);
}();

Nope! The above will yield the following error:

  Uncaught SyntaxError: Unexpected token )

There are a few ways to get around this, but first, one should understand why an exception is thrown. According to Ben Alman:

"When the parser encounters the function keyword in the global scope or inside a function, it treats it as a function declaration (statement), and not as a function expression, by default. If you don’t explicitly tell the parser to expect an expression, it sees what it thinks to be a function declaration without a name and throws a SyntaxError exception because function declarations require a name."

Essentially, the problem is that it is being evaluated as a statement, not an expression. To fix this, one can wrap the entire function in () to force the parser to interpret it as an expression.

(function foo(){ 
    var i = 0;
    console.log(i);
}());

You can also prefix the function with a unary operator; however, this is less common.

+function foo(){ 
    var i = 0;
    console.log(i);
}();

Closures and Lexical Scope

According to MDN, "Closures are functions that refer to independent (free) variables (variables that are used locally, but defined in an enclosing scope). In other words, these functions 'remember' the environment in which they were created."

Closure Examples

const greet = (name) => `Hello, ${name}!`;
function init() {
    let name = 'foobarbaz';
    const closure = () => greet(name);
    return closure();
}

console.log(init()); // 'Hello, foobarbaz!'

In the above code, closure() is a closure because it refers to the variable name, which is defined locally, or in the enclosing function.

Here is another example of closures using an object. Increment, decrement, and value are all closures that have access to the _change() private method.

function Counter() {
	let i = 0;
	function _change(n) {
		i+=n;
	}
	return {
		increment() {
			_change(1);
		},
		decrement() {
			_change(-1);
		},
		value() {
			return i;
		}
	};
}

let counter1 = new Counter();
let counter2 = new Counter();
counter1.increment();
counter2.decrement();
console.log(counter1.value()); // 1
console.log(counter2.value()); // -1

Recursion

Recursion occurs when a function calls itself repeatedly until otherwise stated.

Looping Example

let i = 0;

function repeat(func) {
	if(func() !== undefined) {
		return repeat(func);
	}	
}

repeat(function(){
	if(i < 5) {
		console.log(i);
		i++;
		return true;
	}
});

This code is identical to the following:

   for(let i = 0; i < 5; i++) {
   	console.log(i);
   }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment