Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,24 @@ Before ECMAScript 6, JavaScript had a single keyword to declare a variable, `var
In ECMAScript 6, the keywords `let` and `const` were introduced. While `var` variables were function scoped, these allow you to define variables that are **block scoped** - basically, scoping the variable to only be available within the closest set of `{ curly braces }` in which it was defined. These braces can be those of a `for` loop, `if-else` condition, or any other similar construct, and are called, a block. Let's see an example to sum this all up.

```javascript
let globalAge = 23; // This is a global variable
// This is a global variable
let globalAge = 23;

// This is a function - and hey, a curly brace indicating a block
function printAge (age) {
var varAge = 34; // This is a function scoped variable
function printAge(age) {
// This is a function scoped variable
var varAge = 34;

// This is yet another curly brace, and thus a block
if (age > 0) {
// This is a block-scoped variable that exists
// within its nearest enclosing block, the if's block
// within its nearest enclosing block: the if's block
const constAge = age * 2;
console.log(constAge);
}

// ERROR! We tried to access a block scoped variable
// not within its scope
// outside its scope
console.log(constAge);
}

Expand All @@ -57,20 +59,23 @@ Take a while to brew on that example. In the end, it's not some mind-blowing con
The best way to approach this would be to start with an example - take a look at this piece of code below.

```javascript
function makeAdding (firstNumber) {
function makeAdding(firstNumber) {
// "first" is scoped within the makeAdding function
const first = firstNumber;
return function resulting (secondNumber) {

return function resulting(secondNumber) {
// "second" is scoped within the resulting function
const second = secondNumber;
return first + second;
}
}
// but we've not seen an example of a "function"
// being returned, thus far - how do we use it?
```

But we've not seen an example of a "function" being returned thus far - how do we use it?

```javascript
const add5 = makeAdding(5);
console.log(add5(2)) // logs 7
console.log(add5(2)); // logs 7
```
Comment on lines +62 to 79
Copy link
Member

@ManonLef ManonLef Sep 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accidentally came by your PR. It could be me but I'm very not charmed by the function naming here. Since this lesson is being worked on, could we consider something else? For the returned function I think add or addSecondNumber would work better and is about as descriptive as it gets. resulting does not sound like/is a conventional js name for a function.

For the enclosing makeAdding function I'm not sure yet. Some ideas: makeClosureAdder, makeAddFunction although I'm not a fan of using the function word inside the name but it's very descriptive, createAdder

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glossed over this but I agree re: the function names. I also think there's no need for the extra variables - the params themselves are variables scoped to their own functions.

How does this look:

// firstNumber is scoped to the makeAdder function
function makeAdder(firstNumber) {

  // secondNumber is scoped to addSecondNumber
  // This function doesn't need to be named, we're just
  // making it easier to refer to this function later
  return function addSecondNumber(secondNumber) {
    return firstNumber + secondNumber;
  }
}

Alternatively, after a more thorough read of this section on closures, I think the section could be simplified and perhaps given a second example at the end that's more practical. This adding function is more appropriate for a simple rundown of the mechanics.

I think part of the simplification could be making clear this is no different from any other use of functions. If you want to create a string based off some args, you write a function that returns a string. Want an array based off args? Same thing - write a function that returns an array. These are all reusable. Want a function based off some args? Write a function that returns a function.

I've found this perspective pretty successful in the server for demistifying closures. I feel this may be a little out of this PR's scope, so if you agree with the above, I think it'll be best to handle that in a separate PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I'll have a little deeper think about this section and repurpose this PR as more content clarification with a side of tidyup, rather than the reverse.


A lot going on, so let's break it down:
Expand Down Expand Up @@ -99,24 +104,26 @@ Because of that, constructors have become unpopular in favor of a pattern that i
These fancy-sounding functions work very similar to how constructors did, but with one key difference - they levy the power of closures. Instead of using the `new` keyword to create an object, factory functions set up and return the new object when you call the function. They do not use the prototype, which incurs a performance penalty - but as a general rule, this penalty isn’t significant unless you’re creating thousands of objects. Let's take a basic example to compare them to constructor functions.

```javascript
const User = function (name) {
function User(name) {
this.name = name;
this.discordName = "@" + name;
}
// hey, this is a constructor -
// then this can be refactored into a factory!
```

Hey, this is a constructor... then this can be refactored into a factory!

function createUser (name) {
```javascript
function createUser(name) {
const discordName = "@" + name;
return { name, discordName };
}
// and that's very similar, except since it's just a function,
// we don't need a new keyword
```

This is all very similar to the constructor, except this is just a normal function, meaning we do not need to call it with the `new` keyword.

<div class="lesson-note" markdown="1">

### The object shorthand notation
#### The object shorthand notation

Some may get confused by the way the returned object is written in the factory function example. In 2015, a shortcut to creating objects was added to JavaScript. Say we wanted to create an object with a name, age, and color, we would write it as follows:

Expand All @@ -134,36 +141,43 @@ However, now, if we have a variable with the same name as that of the property t
const nowFancyObject = { name, age, color };
```

An added advantage to this is that it's now possible to console.log values neatly!
An added advantage to this is that it's now possible to console.log values neatly! If you wanted to log the name, age and color variables together earlier, you would have done something like:

```javascript
// If you wanted to log these values, earlier,
// you would have done the following
console.log(name, age, color);
// which would have resulted in a mess - Bob 28 red
```

This would log something like `Bob 28 red`. Not *bad*, just not the clearest as to what's what. Instead, try wrapping it in some curly braces now, which makes it an object:

// Try wrapping it in some { curly braces } now,
// which makes it an object!
```javascript
// shorthand for console.log({ name: name, age: age, color: color })
console.log({ name, age, color });
// now it logs as - { name: "Bob", age: 28, color: "red" }
```

Now it'll log as `{ name: "Bob", age: 28, color: "red" }` which is much clearer, and we didn't need to manually add labels!

### Destructuring

Yet another expression allows you to "unpack" or "extract" values from an object (or array). This is known as **destructuring**. When you have an object, you can extract a property of an object into a variable of the same name, or any named variable for an array. Take a look at the example below:

```javascript
const obj = { a: 1, b: 2 };
const { a, b } = obj;
// This creates two variables, a and b,
// which are equivalent to

// equivalent of doing
// const a = obj.a;
// const b = obj.b;
const { a, b } = obj;
```

And with arrays:

```javascript
const array = [1, 2, 3, 4, 5];
const [ zerothEle, firstEle ] = array;
// This creates zerothEle and firstEle, both of which point
// to the elements in the 0th and 1st indices of the array

// equivalent of doing
// const zerothEle = array[0];
// const firstEle = array[1];
const [zerothEle, firstEle] = array;
```

The [MDN documentation on destructuring assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) has some great examples and should be a good read for this concept.
Expand All @@ -175,12 +189,12 @@ The [MDN documentation on destructuring assignment](https://developer.mozilla.or
Now you may be thinking - where does closure come into all of this? Factories seem to be returning an object. This is where we can extend our `User` factory to add a few more variables and introduce "private" ones. Take a look at this, now:

```javascript
function createUser (name) {
function createUser(name) {
const discordName = "@" + name;

let reputation = 0;
const getReputation = () => reputation;
const giveReputation = () => reputation++;
const giveReputation = () => { reputation++; };

return { name, discordName, getReputation, giveReputation };
}
Expand All @@ -189,11 +203,11 @@ const josh = createUser("josh");
josh.giveReputation();
josh.giveReputation();

// logs { discordName: "@josh", reputation: 2 }
console.log({
discordName: josh.discordName,
reputation: josh.getReputation()
});
// logs { discordName: "@josh", reputation: 2 }
```

We’ve introduced a new metric for a new user - a reputation. Notice that the object we return in the factory function does not contain the `reputation` variable itself, nor any copy of its value. Instead, the returned object contains two functions - one that reads the value of the `reputation` variable, and another that increases its value by one. The `reputation` variable is what we call a "private" variable, since we cannot access the variable directly in the object instance - it can only be accessed via the closures we defined.
Expand All @@ -215,41 +229,51 @@ Note that you could technically also use closure in constructors, by defining th
In the lesson with constructors, we looked deeply into the concept of prototype and inheritance, and how to give our objects access to the properties of another. With factory functions too, there are easy ways to do that. Take another hypothetical scenario into consideration. We need to extend the `User` factory into a `Player` factory that needs to control some more metrics - there are some ways to do that:

```javascript
function createPlayer (name, level) {
function createPlayer(name, level) {
const { getReputation, giveReputation } = createUser(name);

const increaseLevel = () => level++;
const increaseLevel = () => { level++; };
return { name, getReputation, giveReputation, increaseLevel };
}
```

And there you go! You can create your User, extract what you need from it, and re-return whatever you want to - hiding the rest as some private variables or functions! In case you want to extend it, you can also use the [`Object.assign` method](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) to add on the properties you want!

```javascript
function createPlayer (name, level) {
function createPlayer(name, level) {
const user = createUser(name);

const increaseLevel = () => level++;
const increaseLevel = () => { level++; };
return Object.assign({}, user, { increaseLevel });
}
```

### The module pattern: IIFEs

<div class="lesson-note lesson-note--warning" markdown="1" >
<div class="lesson-note lesson-note--warning" markdown="1">

#### ES6 modules

ECMAScript 6 introduced a new JavaScript feature called "modules" - which are a set of syntax for importing and exporting code between different JavaScript files. While they are important and powerful, they are covered a bit later in the curriculum. We are not talking about them in this section.
ECMAScript 6 (released in 2015) introduced a new JavaScript feature called "modules", which are a set of syntax for importing and exporting code between different JavaScript files. For now, we will be talking more generally about the module pattern using IIFEs, which you will still see out in the wild. In a later lesson, we will cover using ES6 modules for similar purposes.

</div>

Oftentimes, you do not need a factory to produce multiple objects - instead, you are using it to wrap sections of code together, hiding the variables and functions that you do not need elsewhere as private. This is easily achievable by wrapping your factory function in parentheses and immediately calling (invoking) it. This immediate function call is commonly referred to as an Immediately Invoked Function Expression (duh) or IIFE in short. This pattern of wrapping a factory function inside an IIFE is called the module pattern.
Oftentimes, you do not need a factory to produce multiple objects - instead, you are using it to wrap sections of code together, hiding the variables and functions that you do not need elsewhere as private. This is easily achievable by wrapping your factory function in parentheses and immediately calling (invoking) it. This immediate function call is commonly referred to as an Immediately Invoked Function Expression (duh) or IIFE in short. IIFEs are quite literally just function expressions that are called immediately:

```javascript
// This is an IIFE! Though not particularly useful, of course.
(() => console.log('foo'))();
```

A more helpful use of IIFEs is the pattern of wrapping "private" code inside an IIFE: the module pattern. This is often done with factory functions:

```javascript
const calculator = (function () {
const add = (a, b) => a + b;
const sub = (a, b) => a - b;
const mul = (a, b) => a * b;
const div = (a, b) => a / b;

return { add, sub, mul, div };
})();

Expand All @@ -268,6 +292,10 @@ This is where we encounter the word **encapsulation** - bundling data, code, or

Take the calculator example into consideration. It's very easy to imagine a scenario where you can accidentally create multiple functions with the name `add`. What does `add` do - does it add two numbers? Strings? Does it take its input directly from the DOM and display the result? What would you name the functions that do these things? Instead, we can easily encapsulate them inside a module called `calculator` which generates an object with that name, allowing us to explicitly call `calculator.add(a, b)` or `calculator.sub(a, b)`.

#### Why the IIFE?

But then why not just write the factory function then call it once? Why bother with the IIFE? Well without the IIFE, we'd have to give the function a name so we can call it afterwards. Now it has a name, it's both taken a name up in that scope and also means it's reusable in that scope. That may not be desirable - we may purposely want a **singleton**. By wrapping the factory in an IIFE, we achieve the same code flow, except we no longer need to name the function, which also means it can't be referenced later. We are packing the code that creates a calculator into what's effectively a module, then exposing only what needs to be used later in the code: the `calculator` object.

### Assignment

<div class="lesson-content__panel" markdown="1">
Expand Down