Skip to content

Created:

Last modified:

Basic Javascript Concepts

I've found that some Javascript concepts I just don't tend to use day to day, so even if I've learned them, I might not have a great grasp of them. Or at least I might struggle to explain what they are or do. So here I thought I'd run through some concepts, write a little about them and then write a little example code.

Closure

Closures allow child functions to retain access to the lexical scope of the parent function, even once the parent function has completed. According to Mozilla (and they kinda know what they're talking about):

A closure is the combination of a function and the lexical environment within which that function was declared. This environment consists of any local variables that were in-scope at the time the closure was created.

Probably one of my favourite concepts. It's so self-contained, handy and simple. A nice simple example that I've seen a few times is a counter function

> const parentFunc = () => {
		let count = 0;
		console.log('The initial count is: ' + count);

		return childFunc = () => {
			count++;
			console.log('The count is now: ' + count);
	}
}
The initial parent function creates a local variable count and assigns 0 to it and logs the initial value of this variable to the console. It then returns a child function which when called will increase that scoped variable count and log the new value.
> const increaseCount = parentFunc();
< The initial count is: 0
> increaseCount()
< The count is now: 1
> increaseCount()
< The count is now: 2
> increaseCount()
< The count is now: 3
To prove this we create a new variable increaseCount and assign it the return value of the parent function, which is the child function. This makes increaseCount a function we can call, and each time we do it first increments the value of count and logs out this value. We call

The benefits of this type of structure includes better control over the count variable, where the variable cannot be accessed directly, only through predetermined functions.

Here is another little example of creating both an add and a substract function, destructuring the returned object and being able to call them independantly while changing the value of the same count variable.

> const parentFunc = () => {
		let count = 0;
		console.log('The initial count is: ' + count);

		const add = () => {
			count++;
			console.log('The count is now: ' + count);
		}

		const sub = () => {
			count--;
			console.log('The count is now: ' + count);
		}
	return {add, sub}
}
> const {add, sub} = parentFunc();
< The initial count is: 0
> add()
< The count is now: 1
> add()
< The count is now: 2
> sub()
< The count is now: 1
> sub()
< The count is now: 0
> sub()
< The count is now: -1
> add()
< The count is now: 0

Currying

The above mentioned Mozilla page on closure mentions another concept, currying. This makes sense as currying is all about retaining the values of parent functions. Nested functions are returned and all passed arguments are available to child functions respectively. Ie the first child function has access to the parents variables and it's own, but not to any arguments passed into any child function it itself might return.

Here's another super simple example using our favourite high level mathematics concept - addition.

> const parentFunc = (val) => {
		return (num) => {
			return val + num;
	}
}
> parentFunc(10)(4)
< 14
> parentFunc(10)(10)
< 20
Example of currying, where the function is completed by immediately invoking the returned function(s) n times, where n is the number of arguments required to complete the function.
> const add10 = parentFunc(10);
> add10(4)
< 14
> add10(10)
< 20
Example of a partially returned function

Currying in the wild

After learning about currying I thought 'wow, this is funky, a bit odd but nifty none the less'. And then I thought 'when on earth would this be used'. Finally I got around to googling 'currying in the wild Javascript' and it turns out a lot of people have this same question. This Medium post on the subject was a nice example, slightly less abstract at least. It really seems like the granular break down one would do with functional programming, and would be great for running tests against.

In case you can't be bothered clicking on the link, or **gulp** it disappears at some point, I'll add a similar snippet below.

> const url = "https://jsonplaceholder.typicode.com/users/1/todos"

> const fetchAll = async (url) => await fetch(url).then(response => response.json())

> const filterCompletedToDos = (completed) => (item) => item.completed === completed;
> const filterToDos = (todos, completed) => {
		return todos.filter(filterCompletedToDos(completed));
};

> filterToDos(await fetchAll(url), true);
< (11) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
> filterToDos(await fetchAll(url), false);
< (9) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
Here we call the awesome jsonplaceholder site for some fake todos. Then we create the filterCompletedToDos function, which willf first take the variable we'll be comparing later to item.completed, returning a function which will take in the item itself for comparison. This function is then used in filterToDos in the filter method against each of the items in the todos variable.

Yes there are many things wrong with this code, but it's just a simple, more 'out in the wild' type of example.

Scope

There are three types of scope in Javascript: global, modulefunction. Variables declared using let and const also have block scoping, where var and function do not.

Some quick and dirty examples:

Global scope

This is the default scope for anything declared in a Javascript file, unless it is declared in such a way to enable the other scopes. Variables which are in the global scope are accessible anywhere in the code.

Block scope

A block scope is created within an if/else or switch statement, or for/while loops

> const testVar = () => {
	if (Math.random() > 0.5) {
		var ans = "greater";
	} else {
		var ans = "lesser";
	}

	console.log(ans)
}
> testVar()
< greater
> testVar()
< greater
> testVar()
< lesser
var is not bound by block scoping, where the block here is an if/else statement
> const testConst = () => {
	if (Math.random() > 0.5) {
		const ans = "greater";
	} else {
		const ans = "lesser";
	}

	console.log(ans)
}
> testConst()
< VM1244:8 Uncaught ReferenceError: ans is not defined
	at testConst (<anonymous>:8:17)
	at <anonymous>:1:1
const is bound by block scoping and so calling the variables declared in this if/else statement outside of it throws a ReferenceError

Function scope

The scope created by function declaration

> const a = "apple";
> const testFunc = () => {
		console.log(a);
		const b = "banana";
	}

> console.log(a);
< apple
> console.log(b);
< Uncaught ReferenceError: b is not defined
	at <anonymous>:9:13
a is declared in the global scope of the page, where as b is declared within a function. Because of this b can only be accessed within that function (or their child functions) and not outside of it.

Module scope

Variables declared within a module are not accessible outside of the module unless they have been explicitly exported. This helps keep namespaces cleaner and easier to deal with.

Lexical scope

This doesn't seem to be a Mozilla defined scope specifically for Javascript. I'm sure there's a good reason why...

Lexical scope is the scope a child function has for it's parent functions environment variables. The above examples of closure and currying show this scope in use.

Getters and Setters

Once again, Mozilla has a nice, simple explanation of 'get' :

The get syntax binds an object property to a function that will be called when that property is looked up.

In the second example below the property getName is bound to the function () => this.name in order to return the name property on the object. The getName property isn't itself a function, and therefore is called without the parentheses "()".

> const obj = {
	name: 'amelia',
	getName: () => this.name
	}
> obj.getName
< () => this.name
> obj.getName()
< ''
Attempting to access the getName function on the object without using the get syntax
> const obj2 = {
	name: 'amelia',
	get getName() {
		 return this.name
	}
}

> obj2.getName
< 'amelia'
> obj2.getName()
< Uncaught TypeError: obj2.getName is not a function at ...
Using the get syntax to bind the property getName to the function to return the object property name

Interesting, but perhaps not surprising, a "getter" is not allowed to take any arguments.

A setter works much the same way

> const obj = {
	name: 'amelia'}

> obj.name
< 'amelia'
> obj.name = 'bob'
< 'bob'
> obj.name
 < 'bob'
In an object without getters or setters if you want to view or change what is held on a property you simply target it: obj['name'] or obj.name
const obj2 = {
	name: 'amelia',
	get getName() {
		return this.name
	},
	set setName(newName) {
		this.name = newName
	}
}

> obj2.getName
< 'amelia'
> obj2.setname = 'bob'
< 'bob'
> obj2.getName
< 'bob'
With the getter and setters in place, we access and change the name property through getName and setName

More interesting, in my opinion, is that a "setter" must have exactly one argument. Only interesting really because there's not too often there so much restriction within Javascript. At least in my experience.

Error handling

Super exciting basic example of throwing an error manually, without catching it within a try/catch block.

> const test = (num) => {
	if (num > 5) {
		throw {
			error: 'This is an error',
			message: 'num is greater than 5'
		}
	} else return num
}
> test(4)
< 4
> test(6)
< Uncaught {error: 'This is an error', 
	message: 'num is greater than 5'}

Here we use the aforementioned try/catch block to catch the error and handling it ourselves. If anything within the try block throws an error control immediately goes to the catch block. This gives us greater control about what happens if/when something in our code fails.

const test2 = (num) => {
	try {
		if (num > 5) {
				throw {
					error: 'This is an error', 
					message: 'num is greater than 5'
				}
		} else return num
	} catch (e) {
		console.log(e)
	}
}
> test2(1)
< 1
> test2(6)
< VM482:7 {error: 'This is an error', 
		message: 'num is greater than 5'}

Here's a rather silly example of being able to determine what your error message will be

> const errors = {
		'object': 'Nope',
		'string': 'Negative',
		'boolean': 'True, but no'
}

> const testInput = (input) => {
		if (Object.keys(errors).includes(typeof(input))) {
				throw new Error(errors[typeof(input)])
		} else console.log(input)
}
> testInput('hi')
< Uncaught Error: Negative
		at testInput (<anonymous>:3:15)
		at <anonymous>:1:1
> testInput(true)
< Uncaught Error: True, but no
		at testInput (<anonymous>:3:15)
		at <anonymous>:1:1
> testInput({what: 'yeah'})
< Uncaught Error: Nope
		at testInput (<anonymous>:3:15)
		at <anonymous>:1:1
> testInput(123)
< 123

And here with try/catch block

> const errors = {
		'object': 'Nope',
		'string': 'Negative',
		'boolean': 'True, but no'
}

> const testInput = (input) => {
		try {
			if (Object.keys(errors).includes(typeof(input))) {
					throw new Error(errors[typeof(input)])
			} else console.log(input)
		} catch (e) {
		 console.log(e)
	}
}
> testInput('hi')
< Error: Negative
	at testInput (<anonymous>:4:15)
	at <anonymous>:1:1
> testInput(true)
< Error: True, but no
	at testInput (<anonymous>:4:15)
	at <anonymous>:1:1
> testInput({what: 'yeah'})
< Error: Nope
	at testInput (<anonymous>:4:15)
	at <anonymous>:1:1
> testInput(123)
< 123

There is also the finally block. The code within this block will fire whether or not an exception has been thrown, and will fire after the try/catch block before any further code runs.

> const test = () => {
		const ranNum = Math.random();

		try {
			if (ranNum > 0.6) {
				throw new Error("It's too hot");
			} else if (ranNum < 0.3) {
				throw new Error("It's too cold");
			} else console.log('Just right')
		} catch (e) {
				console.log("An error was thrown: " + e);
		} finally {
				console.log("Either way it's porridge")
		}
	}
> test()
< An error was thrown: Error: It's too hot
< Either way it's porridge
> test()
< An error was thrown: Error: It's too cold
< Either way it's porridge
> test()
> Just right
< Either way it's porridge

Async await vs promises

Promises have can have one of three states: pending, fulfilled and rejected, with pending always being it's initial state.

> const url = 'https://jsonplaceholder.typicode.com/todos/1'
> const promiseMeThis = new Promise((resolve, reject) => {
		const response = fetch(url).then(response => response.json()) ?? null;
		if (response) {
			resolve(response)
		} else reject('Sad panda')
	}
)
> promiseMeThis
	.then(result => console.log(result))
	.catch(error => console.error(error))
< {userId: 1, id: 1, title: 'delectus aut autem', completed: false}
< Promise {<fulfilled>: undefined}

Async / await wraps the Promise method and gives a more user-friendly approach. The async keyword is used to declare an asynchronous function and await to delare which part is being waited on. Async functions always return a promise

> const url = 'https://jsonplaceholder.typicode.com/todos/1'
> const asyncMeThis = async () => {
		try {
			return await fetch(url).then(response => response.json())
		} catch (error) {
			console.error(error)
		}
}
> await asyncMeThis()
< {userId: 1, id: 1, title: 'delectus aut autem', completed: false}