|
| 1 | +# Advanced Function Concepts |
| 2 | + |
| 3 | +| Contents | |
| 4 | +| :--- | |
| 5 | +| [Pure Functions and Side-Effect](#pure-functions-and-its-side-effect) | |
| 6 | +| [Factory Functions](#factory-functions) | |
| 7 | +| [Closures](#closures) | |
| 8 | + |
| 9 | + |
| 10 | + |
| 11 | +:abacus: [Understand with Code](summary-with-code/app.js) |
| 12 | + |
| 13 | +## [Pure Functions and Side-Effects](https://drive.google.com/uc?export=view&id=1J1QRcr3UMC-h4zeQhgv6INAfitnKWa2_) |
| 14 | + |
| 15 | +In JavaScript, a pure function is a function that: |
| 16 | + |
| 17 | +- Given the same inputs, always returns the same output. |
| 18 | +- Has no side effects, meaning it doesn't modify anything outside of its own scope, including global variables, DOM elements, or any other external state. |
| 19 | + |
| 20 | +Here's an example of a pure function: |
| 21 | + |
| 22 | +```javascript |
| 23 | +function sum(a, b) { |
| 24 | + return a + b; |
| 25 | +} |
| 26 | +``` |
| 27 | + |
| 28 | +The `sum` function takes two arguments and always returns their sum. It doesn't have any side effects because it doesn't modify anything outside of its scope. |
| 29 | + |
| 30 | +On the other hand, an impure function is a function that: |
| 31 | + |
| 32 | +- Doesn't always return the same output for the same inputs. |
| 33 | +- Has side effects, meaning it modifies something outside of its own scope. |
| 34 | + |
| 35 | +Here's an example of an impure function: |
| 36 | + |
| 37 | +```javascript |
| 38 | +let counter = 0; |
| 39 | + |
| 40 | +function increment() { |
| 41 | + counter++; |
| 42 | + return counter; |
| 43 | +} |
| 44 | +``` |
| 45 | + |
| 46 | +The `increment` function modifies the global counter variable every time it is called, which is a side effect. Additionally, it doesn't always return the same output for the same input, since it returns a different value each time it is called. |
| 47 | + |
| 48 | +Side effects can be problematic in code because they can make it harder to reason about what a function does and can introduce bugs. It's generally a good practice to write as many pure functions as possible and minimize the number of impure functions. |
| 49 | + |
| 50 | +Readings: |
| 51 | + |
| 52 | +- [Pure and Impure Functions in JavaScript: A Complete Guide](https://www.syncfusion.com/blogs/post/pure-and-impure-functions-in-javascript-a-complete-guide.aspx) |
| 53 | + |
| 54 | +- [Pure vs Impure Functions in Functional Programming – What's the Difference?](https://www.freecodecamp.org/news/pure-function-vs-impure-function/) |
| 55 | + |
| 56 | +## Factory Functions |
| 57 | + |
| 58 | +A factory function is a function that returns another function or object. It's called a factory function because it creates and returns new objects without having to use the `new` keyword or a class constructor. |
| 59 | + |
| 60 | +Here's an example of a factory function that creates objects representing cars: |
| 61 | + |
| 62 | +```javascript |
| 63 | +function createCar(make, model, year) { |
| 64 | + return { |
| 65 | + make, |
| 66 | + model, |
| 67 | + year, |
| 68 | + getInfo: function() { |
| 69 | + return `${this.year} ${this.make} ${this.model}`; |
| 70 | + } |
| 71 | + }; |
| 72 | +} |
| 73 | + |
| 74 | +const myCar = createCar('Honda', 'Civic', 2022); |
| 75 | +console.log(myCar.getInfo()); // "2022 Honda Civic" |
| 76 | +``` |
| 77 | + |
| 78 | +In this example, `createCar` is a factory function that takes three arguments: `make`, `model`, and `year`. It returns an object that has properties for `make`, `model`, and `year`, as well as a method `getInfo` that returns a string representation of the car's make, model, and year. |
| 79 | + |
| 80 | +To create a new car object, we simply call the `createCar` function with the appropriate arguments, and assign the returned object to a variable (`myCar` in this case). We can then call the `getInfo` method on the `myCar` object to get a string representation of the car's make, model, and year. |
| 81 | + |
| 82 | +Here's an example of a factory function that returns a function: |
| 83 | + |
| 84 | +```javascript |
| 85 | +function createMultiplier(multiplier) { |
| 86 | + return function(number) { |
| 87 | + return number * multiplier; |
| 88 | + } |
| 89 | +} |
| 90 | + |
| 91 | +const double = createMultiplier(2); |
| 92 | +const triple = createMultiplier(3); |
| 93 | + |
| 94 | +console.log(double(5)); // output: 10 |
| 95 | +console.log(triple(5)); // output: 15 |
| 96 | +``` |
| 97 | + |
| 98 | +Factory functions can be useful in situations where you need to create many similar objects, or where you want to encapsulate the creation of an object so that it can be customized or reused in different contexts. |
| 99 | + |
| 100 | +Readings: |
| 101 | + |
| 102 | +- [What are factory functions in JavaScript ?](https://www.geeksforgeeks.org/what-are-factory-functions-in-javascript/) |
| 103 | + |
| 104 | +- [JavaScript Factory Functions](https://www.javascripttutorial.net/javascript-factory-functions/) |
| 105 | + |
| 106 | +## [Closures](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures) |
| 107 | + |
| 108 | +In JavaScript, a closure is created when a function is defined inside another function and the inner function accesses variables from the outer function's scope. The inner function has access to the outer function's variables, parameters, and even other inner functions, even after the outer function has returned. This behavior is possible because of how JavaScript handles scope and variable declaration. |
| 109 | + |
| 110 | +Here's an example to illustrate closures in JavaScript: |
| 111 | + |
| 112 | +```javascript |
| 113 | +function createCounter() { |
| 114 | + let count = 0; |
| 115 | + |
| 116 | + function increment() { |
| 117 | + count++; |
| 118 | + console.log(count); |
| 119 | + } |
| 120 | + |
| 121 | + return increment; |
| 122 | +} |
| 123 | + |
| 124 | +const counter = createCounter(); |
| 125 | + |
| 126 | +counter(); // logs 1 |
| 127 | +counter(); // logs 2 |
| 128 | +counter(); // logs 3 |
| 129 | +``` |
| 130 | + |
| 131 | +In this example, `createCounter()` is a function that returns another function called `increment()`. Inside `createCounter()`, we define a variable called `count` and initialize it to 0. We also define `increment()`, which increments `count` by 1 and logs its value to the console. |
| 132 | + |
| 133 | +We then return `increment()`, which means that calling `createCounter()` actually returns the `increment()` function. We assign this function to a variable called `counter`, which allows us to call `increment()` as many times as we want. |
| 134 | + |
| 135 | +When we call `counter()` for the first time, it logs `1` to the console. This is because `counter()` is actually calling the `increment()` function that was defined inside `createCounter()`. This inner function has access to the `count` variable that was defined in the outer function's scope, so it can increment and log its value. |
| 136 | + |
| 137 | +When we call `counter()` again, it logs `2` to the console, because the `count` variable is still accessible to the `increment()` function. We can call `counter()` as many times as we want, and the value of `count` will continue to increment and be logged to the console. |
| 138 | + |
| 139 | +This is an example of a closure in JavaScript, because the `increment()` function has access to the `count` variable even after `createCounter()` has returned. The inner function "closes over" the outer function's scope and remembers its variables, allowing us to create a counter that persists between function calls. |
| 140 | + |
| 141 | +> All functions in JavaScript are closures. This is because every function in JavaScript has access to the variables and parameters of its outer or parent scope, even after the outer function has returned. |
| 142 | +
|
| 143 | +### Closures and Memory Management |
| 144 | + |
| 145 | +If every function logs all surrounding variables, it could potentially lead to a negative impact on memory, especially in large applications with many variables. A function may log in many variables that it doesn't actually use, but since the function closes over them, JavaScript won't get rid of them. |
| 146 | + |
| 147 | +This could result in a memory issue, but modern JavaScript engines are designed to optimize this behavior by tracking variable usage. If a variable isn't used by any functions or elsewhere in the code, the engine will safely dispose of it. This way, you don't need to manually reset all unused variables as the engine will handle it for you. The JavaScript engines are intelligent and efficient enough to optimize this process without affecting your program's stability. |
| 148 | + |
| 149 | +Readings: |
| 150 | + |
| 151 | +- [Learn JavaScript Closures with Code Examples](https://www.freecodecamp.org/news/lets-learn-javascript-closures-66feb44f6a44/) |
| 152 | + |
| 153 | +- [JavaScript Closures](https://www.javascripttutorial.net/javascript-closure/) |
| 154 | + |
| 155 | +### Immediately Invoked Function Expression (IIFE) |
| 156 | + |
| 157 | +In JavaScript, especially in older scripts, you sometimes find a pattern described as **IIFEs**. **IIFE** stands for "Immediately Invoked Function Expression" and the pattern you might find looks like this (directly in a script file): |
| 158 | + |
| 159 | +```javascript |
| 160 | +(function() { |
| 161 | + var age = 30; |
| 162 | + console.log(age); // 30 |
| 163 | +})() |
| 164 | + |
| 165 | +console.log(age); // Error: "age is not defined" |
| 166 | +``` |
| 167 | + |
| 168 | +What's that? |
| 169 | + |
| 170 | +We see a function expression which calls itself (please note the `()` right after the function). |
| 171 | + |
| 172 | +It's NOT a function declaration because it's wrapped in `()`. That happens on purpose since you can't immediately execute function declarations. |
| 173 | + |
| 174 | +**But why would you write some code?** |
| 175 | + |
| 176 | +Please note that the snippet uses `var`, not `let` or `const`. Remember that `var` does not use block scope but only differ between global and function scope. |
| 177 | + |
| 178 | +As a consequence, it was hard to control where variables were available. Variables outside of function always were available globally. Well, IIFEs solve that problem since the script (or parts of it) essentially are wrapped in a function and function scope is used. |
| 179 | + |
| 180 | +**Nowadays, this is not really required anymore.** With `let` and `const` we got block scope and if you want to restrict where variables are available (outside of functions, `if` statements, `for` loops etc, where you automatically have scoped variables since these structures create blocks), you can simply wrap the code that should have scoped variables with `{}`. |
| 181 | + |
| 182 | +```javascript |
| 183 | +{ |
| 184 | + const age = 30; |
| 185 | + console.log(age); // 30 |
| 186 | +} |
| 187 | + |
| 188 | +console.log(age); // Error: "age is not defined" |
| 189 | +``` |
| 190 | + |
| 191 | +Not something you see too often but something that is possible. |
0 commit comments