99

Concurrent visitors over the last 30 minutes. (~)

Peak concurrent visitors -
Yesterday: 0 / Today: 0

JS Function Core Patterns

JS Function Core Patterns

In JavaScript, functions are called first-class objects, or first-class citizens.
This means you can use functions in ways such as assigning them to variables or passing them to other functions as arguments (values).

Starting from this concept, let's look at various patterns for using functions.

First-class object is a term introduced in the early 1960s, meaning that an element can be called 'first-class' when it meets certain conditions.
It is a concept similar to how, historically, first-class citizens held privileges (most notably the right to vote) in society, politics, the economy, and so on.

First, a first-class object must meet the following conditions.

  • Assignment to a variable
  • Passing as an argument
  • Use as a return value
  • Creation with a literal
  • Use of members (properties or methods) at runtime
JS
1
2
3
4
5
6
7
8
9
10
const a = new Function('cb', 'return cb') // Assignment, Return Value const b = function () { return 1 } // Literal const c = a(b) // Argument c.x = 'B' // set Member console.log(b.x) // get Member console.log(c()) // 'B' // 1
An example that checks the conditions of a first-class object.

# Data and calls

A function is JavaScript data and, at the same time, a callable object that you can call with ().
Before you call it, it is function data, and after you call it, it is the data returned with the return keyword.
So when you use a function, you need to tell clearly whether it is plain function data or the data returned after calling it.

JS
1
2
3
4
5
6
7
8
9
function hello() { return 'Hello~' } console.log(hello) // ƒ hello() console.log(typeof hello) // 'function' console.log(hello()) // 'Hello~' console.log(typeof hello()) // 'string'

# Declarations and expressions

There are two main ways to create a function: declarations and expressions.

A function declaration is a named function created starting with the function keyword.

JS
1
2
3
function hello() { console.log('Hello~') }

A function expression is a function created by assigning it to something like a variable.

JS
1
2
3
const hello = function() { console.log('Hello~') }

In a function expression, the target you assign to has a name, so you usually use an anonymous function.
If you declare a named function and assign it to a variable at the same time, the declared name disappears.

JS
1
2
3
const exp = function dec() {} exp() // OK! dec() // Error: dec is not defined

# Hoisting

Function hoisting is when a function declaration is lifted to the top of its scope.
With hoisting, you can keep complex function logic from appearing first in your code.

Hoisting that happens with variable declarations using the var keyword is mostly a drawback, but hoisting that happens with function declarations can be useful depending on the situation.

JS
1
2
3
4
5
6
7
8
9
10
const fruits = getFruits() // OK! const movies = getMovies() // Error: Cannot access 'getMovies' before initialization // ... function getFruits() { // Function body... } const getMovies = function() { // Function body... }
The difference in hoisting between function declarations and function expressions

The code above is interpreted like this.

JS
1
2
3
4
5
6
7
8
9
10
11
12
// The declaration is lifted to the top function getFruits() { // Function body... } const fruits = getFruits() // OK! const movies = getMovies() // Error: Cannot access 'getMovies' before initialization // ... const getMovies = function() { // Function body... }
Interpreted code

# Return and exit

When you call a function, the return keyword lets you send data from inside the function to the outside.
This is called returning data.

JS
1
2
3
4
5
6
function hello() { return 'Hello~' } const h = hello() console.log(h) // 'Hello~' console.log(hello()) // 'Hello~'

The return keyword also ends the function.
So the statements below the return keyword do not run at all.

JS
1
2
3
4
5
function hello() { return 'Hello~' // The function ends here! console.log('Wow!') // Does not run! } console.log(hello()) // 'Hello~'

If you do not use the return keyword or leave out the data to return, undefined is returned.

A function that returns nothing is usually called a void function.

JS
1
2
3
4
5
6
7
8
9
10
11
12
13
function hello1() { // } function hello2() { return } function hello3() { return undefined } console.log(hello1()) // undefined console.log(hello2()) // undefined console.log(hello3()) // undefined

# Parameter patterns

When you call a function, you can pass data from outside the function to inside it.
The variables that receive this data in order so it can be used inside the function are called parameters.

매개 (the first part of 매개변수, the Korean word for parameter) - means 'to connect two things that are apart' or 'to mediate'.

JS
1
2
3
4
5
6
function sum(a, b) { return a + b } console.log(sum(2, 1)) // 3 console.log(sum(7, 4)) // 11 console.log(sum('A', 'B')) // 'AB'

# Default values

A parameter with a default value uses the default value instead when undefined is passed.

If you pass nothing to a parameter, undefined is assigned implicitly.

JS
1
2
3
4
5
6
function sum(a, b = 1) { return a + b } console.log(sum(2)) // 3 console.log(sum(2, undefined)) // 3 console.log(sum()) // NaN <= undefined + 1

# Destructuring assignment

When you pass an object or an array as a parameter, you can extract specific members with destructuring assignment.

Destructuring assignment is syntax for object and array data, and it is not used only in parameters.
Just remember that you can use this syntax in parameters too.

JS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const user = { name: 'HEROPY', age: 85 } // function getName(user) { // return user.name // } function getName({ name }) { return name } // function getEmail(user) { // return user.email || 'No email.' // } function getEmail({ email = 'No email.' }) { // Destructuring assignment + default value return email } console.log(getName(user)) // 'HEROPY' console.log(getEmail(user)) // 'No email.'

With arrow functions, you can write it much more concisely, like this.

JS
1
2
const getName = ({ name }) => name const getEmail = ({ email = 'No email.' }) => email

Like objects, arrays can also use destructuring assignment.
Unlike objects, arrays have no keys to match directly, so you have to extract parameters by order.
This approach is recommended for relatively simple structures or for extracting the first or second array item.

JS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const fruits = ['Apple', 'Banana', 'Cherry', 'Orange', 'Strawberry'] const numbers = [1, 2, 3, 4, 5, 6, 7] // function getSecond([a, b]) { // The a variable is not used, so you can leave it out! function getSecond([,b]) { return b } console.log(getSecond(fruits)) // 'Banana' console.log(getSecond(numbers)) // 2 // function getFifth([,,,,e]) { // return e // } function getFifth(items) { return items[4] } console.log(getFifth(fruits)) // 'Strawberry' console.log(getFifth(numbers)) // 5

# Rest parameters

With the spread operator, a parameter can receive all the arguments from its matching position onward as an array.
A parameter made this way is called a rest parameter.

It is easy to understand if you think of it as 'take all the rest'!

JS
1
2
3
4
5
6
function sum(a, b, ...rest) { console.log(a, b, rest) // 1 2 [3, 4, 5, 6, 7, 8] return rest.reduce((acc, cur) => acc + cur, 0) } const res = sum(1, 2, 3, 4, 5, 6, 7, 8) console.log(res) // 33
An example that receives all the remaining arguments from the third one onward as an array.

Rest parameters are usually named rest, but you can use any other name.

JS
1
2
3
function sum(a, b, ...xyz) { // ... }

# arguments

arguments is a built-in object available inside every function, and it lets you refer to all the arguments passed into the function even without parameters.
arguments is array-like, not an array, so you cannot use array methods on it, but it is an iterable object, so you can loop over it with a for statement.

If you use rest parameters, you do not need the arguments object.

JS
1
2
3
4
5
6
7
8
9
10
11
// const sum = (...rest) => rest.reduce((acc, cur) => acc + cur, 0) // Much more concise! function sum() { let res = 0 for (const item of arguments) { res += item } return res } const res = sum(1, 2, 3, 4, 5, 6, 7, 8) console.log(res) // 36

# Arrow functions

With arrow functions, you can write functions with simpler, more concise syntax than the regular functions we looked at earlier.
Arrow functions are anonymous functions and expressions by default.

If there is a => symbol, it is an arrow function; if not, it is a regular function.

Regular functions and arrow functions do not differ only in syntax; the way the this keyword works inside the function is completely different.
Of course, all other functional parts are the same.
We will look at this separately below.

JS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Regular functions function hello() { return 'Hello~' } const sum = function(a, b) { return a + b } const log = function (c) { console.log(c) } // Arrow functions const hello = () => 'Hello~' const sum = (a, b) => a + b const log = c => { console.log(c) }

You can leave out the parentheses (()) only when there is exactly one parameter.

JS
1
2
3
const a = () => {} // With no parameters, you cannot leave them out. const b = x => {} // With one parameter, you can leave them out. const c = (x, y) => {} // With two or more parameters, you cannot leave them out.
Patterns for leaving out parentheses.

You can leave out the curly braces ({}) only when you return data right away without any logic in between.

JS
1
2
3
4
5
6
7
8
9
10
11
12
13
const a = x => { // With no logic in between, you can leave them out. return x * x } const b = x => x * x const c = x => { // With logic in between, you cannot leave them out. console.log(x * x) return x * x } const d = () => { return [1, 2, 3] } const e = () => [1, 2, 3] // When left out.
Patterns for leaving out curly braces.

Object literals use curly braces, so in this case you need to wrap the object in parentheses.

JS
1
2
3
4
5
const g = () => { return { a: 1 } } const h = () => ({ a: 1 }) const h = () => { a: 1 } // Error: Unexpected token ':'
Pattern for returning object data.

In the following example, fnA and fnB in the object are regular functions, and fnC is an arrow function.
fnA is the shorthand for a regular function method.

JS
1
2
3
4
5
const obj = { fnA() {}, fnB: function () {}, fnC: () => {} }

# Immediately invoked functions (IIFE)

An immediately invoked function expression (IIFE), or 'immediately invoked function' for short, is syntax that runs (calls) a function right when it is defined.
You can use it for purposes such as preventing pollution of variables in the global scope or blocking access to internal variables from outside the function.

If you do not usually use semicolons (;) at the end of statements, you need to put a semicolon in front of an immediately invoked function to separate it from the previous code.

JS
1
2
3
4
5
6
7
8
9
10
11
12
13
// Define the function (expression) const double = () => { // } // Run (call) the function double() // VS // Define and run the function (IIFE) ;(() => { // })()

There are various patterns for immediately invoked functions, but it is recommended to use only the basic pattern (;(fn)()) whenever possible.
Arrow functions can use only the basic pattern.

JS
1
2
3
4
5
;(() => {})() // (arrowFunction)() ;(function () {})() // (regularFunction)() ;(function () {}()) // (regularFunction()) ;!function () {}() // !regularFunction() ;+function () {}() // +regularFunction()

Of course, you cannot access the internal variables of an immediately invoked function from outside.

JS
1
2
3
4
5
6
7
8
const a = 7 ;(() => { const b = 3 console.log(a, b) })() console.log(a, b) // Error: b is not defined

You can also use outside variables inside under different names.
This approach is sometimes used to make obfuscated code.

JS
1
2
3
4
;((w, d) => { console.log(w.innerWidth) console.log(d.body) })(window, document)

Also, with an immediately invoked function, you can use the await keyword more easily without creating a separate named function.

JS
1
2
3
4
5
;(async () => { const res = await fetch('https://jsonplaceholder.typicode.com/users') const json = await res.json() console.log(json) })()

# Callbacks

A callback function is a function used as an argument (value) of a target function.
It is useful because you can call the callback function at any point you want inside the target function.

In the following example, function b is used as an argument of function a.
Here, function b is called the callback function of function a, and you can also say, for short, 'the callback of a is b'.

JS
1
2
3
4
5
6
7
8
9
10
11
const a = callback => { console.log('A') callback() } const b = () => { console.log('B') } a(b) // 'A' // 'B'
Function b used as an argument of function a

When you call a callback function, you can pass arguments and use them as the callback function's parameters.

JS
1
2
3
4
5
6
7
8
9
function sum(a, b, callback) { // Runs after 1 second setTimeout(() => { callback(a + b) }, 1000) } sum(3, 7, function(value) { console.log(value) // 10 <= 3 + 7 })

Here is an image loading example that uses a callback.
It first shows the Loading... text, then loads the image, caches it in the browser, and shows the image instead of the text.

HTML
1
2
3
<div class="container"> <h1>Loading...</h1> </div>
JS
1
2
3
4
5
6
7
8
9
10
11
12
13
const loadImage = (url, cb) => { const imgEl = document.createElement('img') imgEl.src = url imgEl.addEventListener('load', () => { cb(imgEl) }) } const containerEl = document.querySelector('.container') loadImage('https://picsum.photos/2000/1000', imgEl => { containerEl.innerHTML = '' containerEl.append(imgEl) })

# Scheduling calls

With the timer functions provided by Web (Node) APIs, you can schedule function calls (that is, call a function once after a set time, or call it repeatedly at a set interval).
And when needed, you can also cancel (clear) each schedule (timer).

After you call a timer function, if you no longer need it, be sure to cancel it to prevent unnecessary memory leaks.

Each timer function is provided through Web (Node) APIs.
Because of the asynchronous nature of the call stack and the event loop, clock drift can occur.
If you need to guarantee exact timing, we recommend using a library such as TaskTimer.

Function Description Syntax
setTimeout() Calls the callback once after a set time const timerId = setTimeout(callback, time)
clearTimeout() Cancels a setTimeout timer clearTimeout(timerId)
setInterval() Calls the callback repeatedly at a set interval const timerId = setInterval(callback, time)
clearInterval() Cancels a setInterval timer clearInterval(timerId)
JS
1
2
3
4
5
6
7
8
9
10
11
// Calls the callback once after 5 seconds. const timeout = setTimeout(() => { console.log('Hello~') }, 5000) // Cancels the timer when the button is clicked. const btnEl = document.querySelector('button') btnEl.addEventListener('click', () => { console.log('Clear Timeout!') clearTimeout(timeout) })
setTimeout example.
JS
1
2
3
4
5
6
7
8
9
10
11
// Calls the callback repeatedly every 2 seconds. const timeout = setInterval(() => { console.log('Hello~') }, 2000) // Cancels the timer when the button is clicked. const btnEl = document.querySelector('button') btnEl.addEventListener('click', () => { console.log('Clear Interval!') clearTimeout(timeout) })
setInterval example.

# Recursion

Recursion is when a function calls itself again from inside itself.
You use recursion to implement repeated call patterns where you cannot know exactly when they will end.

재귀 (the Korean word for recursion) - means going back to where you started.

If a recursive function has no condition to end the repetition, it can repeat forever and cause a stack overflow.

Here is an example that starts from userD and finds the topmost parent object.

JS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const userA = { name: 'A', parent: null } const userX = { name: 'X', parent: null } const userY = { name: 'Y', parent: userX } const userB = { name: 'B', parent: userA } const userC = { name: 'C', parent: userB } const userD = { name: 'D', parent: userC } const getRootUser = user => { if (user.parent) { // If the value of the parent property is truthy, call recursively return getRootUser(user.parent) } return user } console.log(getRootUser(userD)) // The userA object!

# Lexical scope

A function's lexical scope, also called static scope, is the range (scope) it can validly access from the place where the function is declared.
Simply put, you can think of it as the scopes of the outer functions that enclose the declared function, including the global scope.

In the following example, the fnC function can refer to all of the window, a, b, and c objects in its lexical scope.
The fnX function is not an outer function of fnC, so fnC cannot access its scope.

JS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const a = { name: 'A', fnA() { const b = { name: 'B', fnB() { const c = { name: 'C', fnC() { console.log(window) // The window object console.log(a) // The a object console.log(b) // The b object console.log(c) // The c object console.log(d) // Error: d is not defined } } return c } } return b }, fnX() { const d = { name: 'D' } } } a.fnA().fnB().fnC()

# this

The this keyword is a special identifier that is determined dynamically inside a function.
The this keyword is defined differently for 'regular functions' and 'arrow functions', so you need to use it carefully.

In a regular function that uses the function keyword, this is defined at the place where the function is called.
In other words, this changes depending on the situation at call time, so it is dynamic.
So you cannot know what this is before the call.

On the other hand, in an arrow function, this is defined (statically) in the lexical scope (parent function) where the function is declared.
In other words, it is fixed to the place where it is declared, so it is static.

Function this is defined Scope
Regular function At the call site Dynamic
Arrow function At the declaration site Static

In the following example,
getFullA, a regular function, has this defined at the call site, so the target object u at the place where it is called (u.getFullA()) becomes this.
getFullB, an arrow function, has this defined in the parent function where it is declared, so the utils object in the user() parent function becomes this.

JS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const utils = { first: 'Neo', last: 'Anderson', user() { console.log(this) // The utils object return { first: 'Heropy', last: 'Park', age: 85, getFullA() { return `${this.first} ${this.last}` }, getFullB: () => `${this.first} ${this.last}` // Where the arrow function's this is defined (at declaration) } } } const u = utils.user() console.log(u.getFullA()) // 'Heropy Park' / Where the regular function's this is defined (at call time) console.log(u.getFullB()) // 'Neo Anderson'

In the following example, the callback of the setTimeout function is a regular function, so this is defined at the call site.
The setTimeout function is provided through Web (Node) APIs, so you cannot know exactly where and how it is called, but in the end, this becomes the window object.

JS
1
2
3
4
5
6
7
8
9
10
11
const timer = { title: 'TIMER!', timeout() { setTimeout(function () { console.log(this.title) // undefined console.log(this) // The window object }, 1000) } } timer.timeout()
Regular function callback

If you use an arrow function for the setTimeout callback like this, this is defined in the parent function where it is declared, so the timer object becomes this.

JS
1
2
3
4
5
6
7
8
9
10
11
const timer = { title: 'TIMER!', timeout() { setTimeout(() => { console.log(this.title) // 'TIMER!' console.log(this) // The timer object }, 1000) } } timer.timeout()
Arrow function callback

If you write the timeout function as an arrow function, the definition of the this keyword changes.

In a JavaScript module, the top-level scope is undefined, not the window object.

JS
1
2
3
4
5
6
7
8
9
10
11
const timer = { title: 'TIMER!', timeout: () => { setTimeout(() => { console.log(this.title) // Error: Cannot read properties of undefined (reading 'title') console.log(this) // undefined }, 1000) } } timer.timeout()
Code in a module

# call, apply, bind

With the .call(), .apply(), and .bind() methods, you can set the this keyword to any object you want when you call a regular function.

An arrow function has already finished defining this at its declaration site, so this does not change even if you use the call, apply, or bind methods.

In the following example, the heropy object does not have a getName method, but it has a name property just like the amy object, so it has a structure that can call the getName method.

JS
1
2
3
4
5
6
7
8
9
const amy = { name: 'Amy', getName(age) { return `${this.name} is ${age}` } } const heropy = { name: 'Heropy' }
An object with a getName method and an object without one

Then, as shown below, the heropy object can also use the getName method of the amy object through the .call(), .apply(), and .bind() methods.
With the .call() and .bind() methods, if arguments are needed, you pass them in order starting from the second argument.
With the .apply() method, if arguments are needed, you put them in order in an array and pass it as the second argument.

All of the examples below print 'Heropy is 85' to the console.

It is easy to think of it as an 'object' borrowing and using (calling) a 'function'.

JS
1
2
3
4
// fn.call(obj, arg1, arg2, ...) console.log( amy.getName.call(heropy, 85) )
The call method
JS
1
2
3
4
// fn.apply(obj, [arg1, arg2, ...]) console.log( amy.getName.apply(heropy, [85]) )
The apply method

.bind() does not call the target function right away; it returns a new function with this bound to the object passed as the first argument.
So you have to call that new function, and it is useful when you need to delay the call.

JS
1
2
3
// const fnBoundToObj = fn.bind(obj, arg1, arg2, ...) const heropysGetName = amy.getName.bind(heropy) console.log(heropysGetName(85))
The bind method

# Closures

A closure is the concept (characteristic) by which a function remembers the lexical scope from when it was declared and can access that scope when it is called.

In the following example, each time you call the count function returned from the createCount function, it refers to the c variable in its lexical scope, and the value goes up by one.
The createCount function has finished after being called, but the value of the internal c variable does not disappear and keeps being maintained.
And since the c variable cannot be accessed from outside, you can also use it for information hiding, like a private access modifier.

JS
1
2
3
4
5
6
7
8
9
10
11
function createCount() { let c = 0 return function () { return (c += 1) } } const count = createCount() console.log(count()) // 1 console.log(count()) // 2 console.log(count()) // 3

Also, each closure keeps its own independent lexical scope.
So you can create and use multiple closures, like this.

JS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function createCount(start) { let c = start return function () { return (c += 1) } } const countA = createCount(0) console.log(countA()) // 1 console.log(countA()) // 2 console.log(countA()) // 3 const countB = createCount(77) console.log(countB()) // 78 console.log(countB()) // 79 console.log(countB()) // 80

If you no longer need a closure, you can remove the reference by assigning null to the function to save memory.
To reassign it, you need the let keyword.

JS
1
2
3
4
5
6
let countA = createCount(0) console.log(countA()) // 1 console.log(countA()) // 2 console.log(countA()) // 3 countA = null // Remove the reference!

Let's look at a more useful example that uses closures.
When you have the following HTML structure,

HTML
1
2
<h1>Hello world!</h1> <h2>Hello world!</h2>

if you do not use closures, you have to manage the data for each event handler separately outside, like this.

JS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const h1El = document.querySelector('h1') const h2El = document.querySelector('h2') // State (data) needs to be managed separately outside! let h1IsRed = false let h2IsRed = false h1El.addEventListener('click', event => { h1IsRed = !h1IsRed h1El.style.color = h1IsRed ? 'red' : 'black' }) h2El.addEventListener('click', event => { h2IsRed = !h2IsRed h2El.style.color = h2IsRed ? 'red' : 'black' })

But with closures, you can easily manage the data for each event handler inside a function.

JS
1
2
3
4
5
6
7
8
9
10
11
12
13
const h1El = document.querySelector('h1') const h2El = document.querySelector('h2') // Organized into a single function! const createToggleHandler = () => { let isRed = false return event => { isRed = !isRed event.target.style.color = isRed ? 'red' : 'black' } } h1El.addEventListener('click', createToggleHandler()) h2El.addEventListener('click', createToggleHandler())

# Function currying

Currying is a technique that splits a function that takes multiple arguments into functions that each take one argument at a time, and calls them in sequence.
This helps make functions more reusable, readable, and flexible.

Currying is named after the mathematician Haskell Curry.

In the following example, the myConsole function takes two arguments and handles them.
Each time it is called, it receives the name of a log, warn, or error method of the console object and uses it.

JS
1
2
3
4
5
6
7
function myConsole(level, message) { console[level](message) } myConsole('log', 'Hello world~') myConsole('warn', 'Danger ahead!') myConsole('error', 'Error: system failure..')
Without currying.

You can apply currying to the example above and change it like this.
Now, instead of a function that takes two arguments, you can split it into functions that each take one argument and call them in sequence.
But looking at how it is called, currying does not seem especially useful compared to passing multiple arguments.

JS
1
2
3
4
5
6
7
8
9
function useConsole(level) { return function(message) { console[level](message) } } useConsole('log')('Hello world~') useConsole('warn')('Danger ahead!') useConsole('error')('Error: system failure..')
With currying.

If you change the currying in the example above like this, it becomes much more useful.
You can assign the functions returned by calling useConsole to variables and call them whenever you need.

JS
1
2
3
4
5
6
7
8
9
10
11
12
13
function useConsole(level) { return function(message) { console[level](message) } } const log = useConsole('log') const warn = useConsole('warn') const error = useConsole('error') log('Hello world~') warn('Danger ahead!') error('Error: system failure..')
With currying.

If you want to turn a function you already use into a curried one, you can use the curry() method of the Lodash library.

HTML
1
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js" integrity="sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
JS
1
2
3
4
5
6
7
8
9
10
11
12
13
function myConsole(level, message) { console[level](message) } const useConsole = _.curry(myConsole) const log = useConsole('log') const warn = useConsole('warn') const error = useConsole('error') log('Hello world~') warn('Danger ahead!') error('Error: system failure..')
Currying with the Lodash curry() method.

# The Function class

You can also create a function by calling the constructor of the Function class.
You can provide the parameters and other logic as strings, so you can create and use functions dynamically from strings fetched from a server, strings entered by a user, and so on.

JS
1
2
3
4
5
6
7
8
9
10
const sum = new Function('a', 'b', ` console.log(a) console.log(b) return a + b `) console.log(sum(1, 7)) // 1 // 7 // 8

Here is an example that calls a function with the value entered in a <textarea> element.

JS
1
2
3
4
5
6
7
8
let text = '' const textareaEl = document.querySelector('textarea') textareaEl?.addEventListener('input', () => { text = textareaEl.value }) textareaEl?.addEventListener('change', event => { new Function(text)() })

# Factory functions

A factory function is a function that returns an object.
With factory functions, you can hide the detailed logic of creating objects, provide a simple interface to users (encapsulation), and make functions more reusable.

JS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const createUser = (full, birth) => ({ firstName: full.split(' ')[0], lastName: full.split(' ')[1], age: new Date().getFullYear() - birth, birth, greet() { return `Hello, My name is ${this.firstName}! I'm ${this.age}.` } }) const userA = createUser('Neo Anderson', 1985) console.log(userA.greet()) // 'Hello, My name is Heropy! I'm 38.' const userB = createUser('Amy Pond', 1916) console.log(userB.firstName) // 'Amy'

# Higher-order functions

A function that takes a function as an argument (a callback) or uses one as its return value is called a higher-order function.
Higher-order functions are a core concept of functional programming, and with them you can modularize logic and write code that aims for data immutability.

JS
1
2
3
4
5
6
7
8
9
10
11
12
// Modularization const add = (a, b) => a + b const sub = (a, b) => a - b const mul = (a, b) => a * b const div = (a, b) => a / b const calculate = (fn, a, b) => fn(a, b) console.log(calculate(add, 1, 2)) // 3 console.log(calculate(sub, 1, 2)) // -1 console.log(calculate(mul, 1, 2)) // 2 console.log(calculate(div, 1, 2)) // 0.5

Array methods such as .map() and .filter() are higher-order functions you will run into often.
Each of these methods returns a new array without directly changing the original data.

JS
1
2
3
4
5
6
7
8
9
10
11
const numbers = [1, 2, 3, 4, 5] // .map() const doubles = numbers.map(item => item * 2) console.log(doubles) // [2, 4, 6, 8, 10] console.log(numbers) // [1, 2, 3, 4, 5] // .filter() const evens = numbers.filter(item => item % 2 === 0) console.log(evens) // [2, 4] console.log(numbers) // [1, 2, 3, 4, 5]