In case of passing primitive type (e.g. a string or a number) to the function, the argument is a copy of the variable value, and not the original variable.
But, when a reference type is passed (e.g. an object or an array), the argument is a reference to the object in the memory heap.
So, it works just same as copying the variable outside of the function.
JavaScript does not having passing by references, only passing by value, even in reference types case, in which it passes a value to the reference and not the reference itself.
const flight = "LH234";const jonas = { name: "Jonas Schmedtmann", passport: 24739479284,};const checkIn = function (flightNum, passenger) { flightNum = "LH999"; passenger.name = "Mr. " + passenger.name; if (passenger.passport === 24739479284) { alert("Checked in"); } else { alert("Wrong passport!"); }};checkIn(flight, jonas);// jonas object name was modified by the function, while the flight string didn't.console.log(flight); // 'LH234'console.log(jonas); // {name: 'Mr. Jonas Schmedtmann', passport: 24739479284}
First-Class vs. Higher-Order Functions
First-class Functions
JavaScript treats functions as first-class citizens. (language feature)
This means that functions are simply values.
Functions are just another Functions are just another “type” of object.
Functions can be stored in variables or properties.
Functions can be passed as arguments to other functions.
// Functions can be stored in variablesconst add = (a, b) => a + b;// Functions can be stored in propertiesconst counter { value: 23, inc: function() { this.value++; }};// Functions can be passed as arguments to other functionsconst greet = () => console.log("Hello");btnClose.addEventListener('click', greet); // greet is passed to addEventListener
We can return functions from functions.
We can also call methods on functions. counter.inc.bind(someOtherObject);
Higher-order Functions
A function that receives another function as an argument, that returns a new function, or both.
This is only possible because of first-class functions.
// Function that receives another functionconst greet = () => console.log("Hello");// addEventListener is higher-order function, while greet is a callback functionbtnClose.addEventListener("click", greet);// Function that returns new functionfunction count() { // Higher-order function let counter = 0; return function () { // Returned function counter++; };}
Abstraction in Programming with First Class Functions
JavaScript uses first class functions and a higher order functions to implement callback functions to allow us to create abstractions
We can create a higher level logic by hiding things that a function does that does not need to be shown. We can do this by having functions that can then be used as callback functions in another function.
A ‘higher level’ function will not care on how the argument it passes works. We can just use another function in it, by abstracting and thus delegating functions to other ‘lower’ level functions.
This is important in OOP to ensure that the code base is more focused.
Code Examples
Functions Accepting Callback Functions
const oneWord = function (str) { return str.replace(/ /g, "").toLowerCase();};// Higher-order functionconst transformer = function (str, fn) { console.log(`Original string: ${str}`); console.log(`Transformed string: ${fn(str)}`); console.log(`Transformed by: ${fn.name}`);};transformer("JavaScript is the best!", oneWord);
Functions Returning Functions
const greet = function (greeting) { return function (name) { console.log(`${greeting} ${name}`); };};const greeterHey = greet("Hey");greeterHey("Jonas"); // Hey Jonasgreet("Hello")("Jonas"); // Hey Jonas// Same with arrow functionsconst greetArr = greeting => name => console.log(`${greeting} ${name}`);greetArr("Hi")("Jonas"); // Hi Jonas
Call and Apply
Function methods explicitly set this keyword by setting the object that function will be calling with.
They do the same thing, but the difference is that call accepts additional arguments, while apply doesn’t receive a list of arguments after this keyword, but instead it takes an array of arguments.
Apply method is less used in modern JavaScript, because we can do the same thing with call and spread operator.
const lufthansa = { airline: "Lufthansa", iataCode: "LH", bookings: [], // book: function() {} book(flightNum, name) { console.log(`${name} booked a seat on ${this.airline} flight ${this.iataCode}${flightNum}`); this.bookings.push({ flight: `${this.iataCode}${flightNum}`, name }); },};const eurowings = { airline: "Eurowings", iataCode: "EW", bookings: [],};// Set a new variable to book function valueconst book = lufthansa.book;// Does NOT work, because this points to undefined// book(23, 'Jonas');// Call methodbook.call(eurowings, 23, "Jonas"); // this = eurowings// Apply methodconst swiss = { airline: "Swiss Air Lines", iataCode: "LX", bookings: [],};const flightData = [583, "George Cooper"];book.apply(swiss, flightData);console.log(swiss);// Call with spreadbook.call(swiss, ...flightData);
Bind
Just like call and apply, bind allows to manually set this keyword for any function call, the difference is that it doesn’t immediately call the function but instead, it returns a new function where this keyword is bound.
So, to use a function that requires this with an event listener we need to use bind.
// With Event Listenerslufthansa.planes = 300;lufthansa.buyPlane = function () { console.log(this); this.planes++; console.log(this.planes);};document.querySelector(".buy").addEventListener("click", lufthansa.buyPlane.bind(lufthansa));
Partial Application
Partial application means pre-setting the function parameters.
bind can be used for partial application without the need for setting this by simply setting it to null.
// Partial applicationconst addTax = (rate, value) => value + value * rate;console.log(addTax(0.1, 200)); // 220const addVAT = addTax.bind(null, 0.23);// Achieving the same as previous line without bind// addVAT = value => value + value * 0.23;console.log(addVAT(100)); // 123
Immediately Invoked Function Expressions (IIFE)
IIFE
Sometimes in JavaScript, we need a function that disappears right after it’s called once).
This can be done by writing the function expression without assigning it to any variable then wrapping all of this into parentheses (transforming the statement into an expression).
It’s useful for encapsulating data, since inner scope won’t be accessed by any outer one.
This technique will be needed with async/await.
// IIFE// Normal function(function () { console.log("This will never run again"); const isPrivate = 23;})();// Arrow function(() => console.log("This will ALSO never run again"))();
In modern JavaScript IIFE is not used widely because blocks can be used for achieving the same behavior with variables.
{ const isPrivate = 23; var notPrivate = 46;}// console.log(isPrivate); // Can't be accessedconsole.log(notPrivate); // Can be accessed
/** * Working with functions */// Function declaration:// globalfunction doSomeMath(a, b) { let c = a + b; return c;}// Function expression:// have same scope as variableconst doMoreMath = function (a = 3, b = 2) { let c = a * b; return c;};console.log("Do some math:", doSomeMath(5, 6));console.log("Do more math:", doMoreMath(5, 6));// Immediately Invoked Function Expression (IIFE)(function () { let a = 4; let b = 6; let c = doSomeMath(a, b); console.log(`The sum of a and b is: ${c}`);})();
/** * A standard function * @link https://developer.mozilla.org/en-US/docs/Glossary/Function */const greenPack = { name: "Frog Pack", color: "green", volume: 8, pocketNum: 3,};const addPack = function (currentPack) { const newArticle = document.createElement("article"); newArticle.innerHTML = ` <h1>${currentPack.name}</h1> <ul> <li>Volume: ${currentPack.volume}</li> <li>Color: ${currentPack.color}</li> <li>Number of pockets: ${currentPack.pocketNum}</li> </ul> `; return newArticle;};const main = document.querySelector("main");main.append(addPack(greenPack));
Arrow function expressions
Arrow functions are just a simpler way of writing anonymous functions, they produce a lot cleaner code.
Function declarations can be hoisted, meaning you can call the function before it is declared in JavaScript. Arrow functions on the other hand can only be called after they have been declared.
You can’t use arrow functions when declaring methods in an object. Inside an object, if you have a method you need to use a proper anonymous function declaration.
You can reduce and simplify the arrow function syntax to the point where it becomes really hard to understand what’s going on (just the parameter and the arrow and it points directly as the output).
// Traditional Anonymous Functionfunction (a){ return a + 100;}// Arrow Function Break Down// Note: Each step along the way is a valid "arrow function".// 1. Remove the word "function" and place arrow between the argument and opening body bracket(a) => { return a + 100;}// 2. Remove the body braces and word "return" -- the return is implied.(a) => a + 100;// 3. Remove the argument parenthesesa => a + 100;
If you’re using ‘this’ in a method within an object, and you then get an odd result, try turning the function into an arrow function to see if that solves the problem. Most likely, you’re dealing with the wrong scope and an arrow function will help you get the correct scope because it doesn’t carry its own scope with it.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this#arrow_functions
/** * How arrow functions help us with scoping. */// Define a default volume for the window (the entire DOM):window.volume = 20;const greenPack = { name: "Frog Pack", color: "green", volume: 8, pocketNum: 3, newVolume: function (volume) { console.log("this.volume in the method:", this.volume); // 8 this.volume = volume; console.log("this.volume after update:", this.volume); // 5 // hoisted up to the global scope. (function () { console.log("this.volume in nested function:", this.volume); // 20 })(); // Whereas the arrow function stays within the current scope // this is happening because an arrow function does not have its own 'this.' It does not know what this means and it will refer to the closest available scope which in this case is the object. (() => { console.log("this.volume in nested function:", this.volume); // 5 })(); },};console.log(greenPack.newVolume(5));
Pass data to a function with parameters
/** * Passing data to functions through parameters. * @link https://developer.mozilla.org/en-US/docs/Glossary/Function */const tipCalculator = (sum, percentage, currency, prefix) => { let tip = sum * (percentage / 100); let total = sum + tip; if (prefix) { console.log(` Sum before tip: ${currency}${sum} Tip percentage: ${percentage}% Tip: ${currency}${tip.toFixed(2)} Total: ${currency}${total.toFixed(2)} `); } else { console.log(` Sum before tip: ${sum}${currency} Tip percentage: ${percentage}% Tip: ${tip.toFixed(2)}${currency} Total: ${total.toFixed(2)}${currency} `); }};tipCalculator(29.95, 18, "kr", false);
Return values from a function
/** * Passing data to functions through parameters. * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat * * List of ISO language codes: * @link http://www.lingoes.net/en/translator/langcode.htm */const formatter = (locale = "en-US", currency = "USD", value) => { let formattedValue = new Intl.NumberFormat(locale, { style: "currency", currency: currency, }).format(value); return formattedValue;};const tipCalculator = (sum, percentage, locale, currency) => { let tip = sum * (percentage / 100); let total = sum + tip; console.log(` Sum before tip: ${formatter(locale, currency, sum)} Tip percentage: ${percentage}% Tip: ${formatter(locale, currency, tip)} Total: ${formatter(locale, currency, total)} `);};tipCalculator(29.95, 18, "de-DE", "EUR");
Callbacks
// Callback receives finalTip object, creates and outputs table on the DOM.const printHTML = finalTip => { const tipTable = document.createElement("table"); tipTable.innerHTML = ` <tr> <td>Sum before tip:</td> <td>${finalTip.sum}</td> </tr> <tr> <td>Tip percentage:</td> <td>${finalTip.percentage}</td> </tr> <tr> <td>Tip:</td> <td>${finalTip.tip}</td> </tr> <tr> <td>Total:</td> <td>${finalTip.total}</td> </tr> `; document.querySelector("main").append(tipTable);};// Create a finalTip object with all the data. Send it to the printHTML callback.const tipCalculator = (sum, percentage, locale, currency, callback) => { let tip = sum * (percentage / 100); let total = sum + tip; const finalTip = { sum: formatter(locale, currency, sum), percentage: percentage + "%", tip: formatter(locale, currency, tip), total: formatter(locale, currency, total), }; callback(finalTip);};tipCalculator(29.95, 18, "de-DE", "EUR", printHTML);
There’s a good chance when you set up some more complex code that you may have different callback functions you want to use for different purposes.
So in this particular circumstance we want to use the print HTML function but there could be several different versions of the print HTML function and then you want to use them for different purposes and by calling a callback like this, you can pass in exactly the function you want into the other function.
So we’re effectively saying here is the precise function I want you to use once you’re done processing your information right now but later it could be a different function.