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);
}
}
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
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 callThe 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
n times, where n is the number of arguments required to complete the function.> const add10 = parentFunc(10);
> add10(4)
< 14
> add10(10)
< 20
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) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
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 ReferenceErrorFunction 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()
< ''
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 ...
get syntax to bind the
property getName to the function to return the object property nameInteresting, 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'
obj['name'] or obj.nameconst obj2 = {
name: 'amelia',
get getName() {
return this.name
},
set setName(newName) {
this.name = newName
}
}
> obj2.getName
< 'amelia'
> obj2.setname = 'bob'
< 'bob'
> obj2.getName
< 'bob'
name property through getName and setNameMore 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}