more stuff on functions
We can have “default” parameters for functions. One way to achieve this is with || operator short-circuiting. For example…
const restuarantBookings = [];function createBooking(firstName, secondName, time, vegetarian) { // short circuiting to provide a default value if not passed into the function as an argument
vegetarian = vegetarian || "not specified";
const booking = {
firstName,
secondName,
time,
vegetarian
};restuarantBookings.push(booking);
};
createBooking("Paul", "Smith", 22.30);
console.log(restuarantBookings);
So we’ve created a default value for vegetarian of “not specified” if has not been confirmed if the person is a vegetarian or not.
This way of doing it was the old way.
The new way is as follows….
const restuarantBookings = [];function createBooking(firstName, secondName, time, vegetarian = "not specified") { const booking = {
firstName,
secondName,
time,
vegetarian
};restuarantBookings.push(booking);
};
createBooking("Paul", "Smith", 22.30);
console.log(restuarantBookings);
We can also dynamically calculate values to be pushed into the array based on the arguments. For example…
const restuarantBookings = [];function createBooking(
vegetarian = "not specified",
birthday = false,
discount = (birthday ? 10 : 0)
) {const booking = {
vegetarian,
birthday,
discount
};
restuarantBookings.push(booking);
};createBooking(true);
console.log(restuarantBookings);
In the code above, we passed in one argument (true) into the createBooking function, which stated if the person was vegetarian, but we didn’t pass in a second argument to say if it was the persons birthday, and therefore the ternary operator, basically calculated that the discount for the meal should be zero as a default value.
{vegetarian: true, birthday: false, discount: 0}
If on the other hand we pass in the second argument, as true, then the value will be dynamically updated…
const restuarantBookings = [];
function createBooking(
vegetarian = "not specified",
birthday = false,
discount = (birthday ? 10 : 0)
) {
const booking = {
vegetarian,
birthday,
discount
};
restuarantBookings.push(booking);
};
createBooking(true, true);
console.log(restuarantBookings);
which results in this….
{vegetarian: true, birthday: true, discount: 10}
This is ok for when the default parameter is the last parameter, but what if we want to skip parameters in the middle?
In this case, we can simply pass in undefined as the function argument where we want to skip….
const restuarantBookings = [];
function createBooking(firstName, secondName = "no surname", time = "time not confirmed", vegetarian = "not specified") {
const booking = {
firstName,
secondName,
time,
vegetarian
};
restuarantBookings.push(booking);
};
createBooking("Paul", undefined, undefined, true);
console.log(restuarantBookings);
which produces the result…
{firstName: 'Paul', secondName: 'no surname', time: 'time not confirmed', vegetarian: true}
Primitive values vs objects inside functions
If we pass a primitive value into a function, then the value itself is copied, however, if we pass an object into a function and then change it, we are actually changing the object itself.
for example…
// primitive value - copied
const birthYear = 1985;// object - changed by function
const person = {
name: "bob",
age: 21,
male: true
}// first confirm values in console log
console.log(birthYear);
console.log(person.name);// passing these into a function
const personData = function (personsBirthYear, personsName) {
personsBirthYear = 1990;
personsName.name = "fred";
}
personData(birthYear, person);// re-check values in console log
console.log(birthYear);
console.log(person.name);
this returns….
1985
bob
1985
fred
So basically, we passed two arguments to the ‘personData’ function (‘birthYear’ which is a primitive value, and ‘person’ which is an object) and the personData function used these as the two parameters.
Inside the ‘personData’ function, we assigned 1990 to personsBirthYear (which was passed in as the variable birthYear) and we also assigned “fred” to personsName.name (which was passed in as the person object)
You can see that when we re-check the values of the primitive variable of ‘birthYear’, it hasn’t changed, because the value inside the function is just a copy, local to the function, but the actual value for the person.name key has now changed, so it proves that when you are referencing objects and manipulating them inside functions, then it directly impacts the object itself.
Higher order functions
Higher order functions can call upon other functions to do the work for them.
For example…
const lowerCaseChanger = function(str) {
return str.toLowerCase();
}
// Higher Order function
const transformer = function(str, fn) {
console.log(`my transformed string is: ${fn(str)}`);
}transformer("HELLO WORLD", lowerCaseChanger);
so the transformer function is expecting two arguments, the string, and the function, so we supply the string to change, and the name of the function that will do the work.
So the string gets converted to lowercase, and then can be used in the ‘transformer’ function.
So it’s basically the same as this…
console.log(`my transformed string is: ${lowerCaseChanger("HELLO WORLD")}`);
callback functions
callback functions are exactly what they sound like….functions called when something happens
For example…
const makeScreenTurnRed = function() {
document.querySelector("body").style.backgroundColor = "red";
}// callback function when button is clicked
document.querySelector(".button").addEventListener("click", makeScreenTurnRed);
So in the above example, the addEventListener() is the higher order function, and ‘makeScreenTurnRed’ as the callback function.
functions returning functions
It is possible for a function to ‘return’ another function. In doing this, you can effectively assign the ‘return’ (i.e. the function) to a variable, which means that you can then call the variable as a function.
For example…
const greet = function(greeting) {
return function (personsName){
console.log(`${greeting} ${personsName}!`);
}
};// now assign greet function with "Welcome" as argument to variable
const greeter = greet("Welcome");// now call this variable with an argument for personsName
greeter("Bob");
which returns “Welcome Bob!”
This greet function could also be shortened here with arrow functions….so basically it’s exactly the same as this….
const greet = greeting => personsName =>
console.log(`${greeting} ${personsName}!`);// now assign greet function with "Welcome" as argument to variable
const greeter = greet("Welcome");// now call this variable with an argument for personsName
greeter("Bob");
call() apply() bind() methods
call()
The call() method can be used when we want to specify a particular object, when we are faced with multiple objects, of which some of those objects might have functions in them.
The objects which have functions in them can take advantage of the ‘this’ keyword to reference values inside the same object, however if we assign the function in the object to a variable, and then call the function, we are attempting to use the function that uses the ‘this’ keyword which won’t work.
This makes more sense with an example.
The following will NOT work, because when we attempt to call the ‘bookTable’ function, we are attempting to use this on a function expression, which cannot take advantage of the ‘this’ keyword.
const restaurantOne = {
firstName: "bob",
lastName: "smith",
vegetarian: true,
bookings: [],
// function in the object
book: function(time, day) {
console.log(`${this.firstName} ${this.lastName} booked a meal at ${time} on ${day}`);
this.bookings.push(`reservation made for ${this.firstName} at ${time}`);
}
};const restaurantTwo = {
firstName: "bob",
lastName: "smith",
vegetarian: true,
bookings: []
};// assign the function in first object to variable...
const bookTable = restaurantOne.book
//now attempt to book with the function variable...
bookTable(2230, "wednesday");
In order to make the above code work, we need to use the call() method to explicitly state that we want to use the function expression in the restaurantOne object.
const restaurantOne = {
firstName: "bob",
lastName: "smith",
vegetarian: true,
bookings: [],
// function in the object
book: function(time, day) {
console.log(`${this.firstName} ${this.lastName} booked a meal at ${time} on ${day}`);
this.bookings.push(`reservation made for ${this.firstName} at ${time}`);
}
};const restaurantTwo = {
firstName: "bob",
lastName: "smith",
vegetarian: true,
bookings: []
};// assign the function in first object to variable...
const bookTable = restaurantOne.book
//now attempt to book with the function variable...
bookTable.call(restaurantOne, 2230, "wednesday");
Which returns…
bob smith booked a meal at 2230 on wednesday
We can also confirm that the function pushed the information to the array in object ‘restaurantOne’
console.log(restaurantOne.bookings);
which returns…
['reservation made for bob at 2230']
By using this call method, it now means we can take advantage of the function expression, even if we call a different object.
For example…
const restaurantOne = {
firstName: "bob",
lastName: "smith",
vegetarian: true,
bookings: [],
// function in the object
book: function(time, day) {
console.log(`${this.firstName} ${this.lastName} booked a meal at ${time} on ${day}`);
this.bookings.push(`reservation made for ${this.firstName} at ${time}`);
}
};
const restaurantTwo = {
firstName: "bob",
lastName: "smith",
vegetarian: true,
bookings: []
};
// assign the function in first object to variable...
const bookTable = restaurantOne.book//now attempt to book with the function variable...
bookTable.call(restaurantTwo, 1730, "friday");console.log(restaurantTwo.bookings);
which returns the following in the console…
bob smith booked a meal at 1730 on friday
['reservation made for bob at 1730']
So we can see that the function expression in object one was used to push to the array of object two!
Apply() method
The apply method is very similar to the call method, however rather than passing in individual arguments, you pass in an array of the values.
For example…
const restaurantOne = {
firstName: "bob",
lastName: "smith",
vegetarian: true,
bookings: [],
// function in the object
book: function(time, day) {
console.log(`${this.firstName} ${this.lastName} booked a meal at ${time} on ${day}`);
this.bookings.push(`reservation made for ${this.firstName} at ${time}`);
}
};
const restaurantTwo = {
firstName: "bob",
lastName: "smith",
vegetarian: true,
bookings: []
};
// assign the function in first object to variable...
const bookTable = restaurantOne.book// an array of the data you wish to pass in
const guestInfo = [1415, "sunday"];//now attempt to book with the function variable...
bookTable.apply(restaurantOne, guestInfo);
console.log(restaurantOne.bookings);
However this is no longer used very often, as you can just use the ‘…’ spread operator with the call() method.
const restaurantOne = {
firstName: "bob",
lastName: "smith",
vegetarian: true,
bookings: [],
// function in the object
book: function(time, day) {
console.log(`${this.firstName} ${this.lastName} booked a meal at ${time} on ${day}`);
this.bookings.push(`reservation made for ${this.firstName} at ${time}`);
}
};
const restaurantTwo = {
firstName: "bob",
lastName: "smith",
vegetarian: true,
bookings: []
};
// assign the function in first object to variable...
const bookTable = restaurantOne.book// an array of the data you wish to pass in
const guestInfo = [1415, "sunday"];//now attempt to book with the function variable...
bookTable.call(restaurantOne, ...guestInfo);
console.log(restaurantOne.bookings);
bind()
Bind does not call the function in an object, but instead “binds” a function to a particular object. It’s actually creating a brand new function each time you bind it to an object.
For example…
const restaurantOne = {
firstName: "bob",
lastName: "smith",
vegetarian: true,
bookings: [],
// function in the object
book: function(time, day) {
console.log(`${this.firstName} ${this.lastName} booked a meal at ${time} on ${day}`);
this.bookings.push(`reservation made for ${this.firstName} at ${time}`);
}
};const restaurantTwo = {
firstName: "bob",
lastName: "smith",
vegetarian: true,
bookings: []
};// assign the function in first object to variable...
const bookTable = restaurantOne.book// now create (bind) the function to the objects...const bookRestaurantOne = bookTable.bind(restaurantOne);
const bookRestaurantTwo = bookTable.bind(restaurantTwo);// now just use the one you want...bookRestaurantOne(2245, "tuesday");
bookRestaurantTwo(1120, "friday");console.log(restaurantOne.bookings);
console.log(restaurantTwo.bookings);
which returns….
bob smith booked a meal at 2245 on tuesday
bob smith booked a meal at 1120 on friday
['reservation made for bob at 2245']
['reservation made for bob at 1120']
You can also use bind to create new functions that will include some preset values, which might help creating variations of functions.
for example, with a function declaration (not inside any object):
// simple calculator function
const calculator = function(x,y,z) {
return (x + y) * z;
}// now assign the function to a variable
const calcFunc = calculator;// now use bind method to assign to a variable with preset values (2 and 3) and null used as the first argument, as there is no object to reference!
const presetValueCalc = calcFunc.bind(null, 2, 3);// now use the function with the default variables
console.log(presetValueCalc(4));
which returns…
20
20
In this case we used ‘null’ as the first argument, as the bind method expects the ‘this’ to be stated as the first argument, which is the object that the function would bind to, however the function isn’t being bound to an object, therefore we use null to effectively skip that first parameter.
Here is very similar code, only this time the function is inside the object, so in this case the first parameter in the bind method, states the object to which the function should be bound….
// calculator function INSIDE an object
const calculatorObjectOne = {
name: "my first calculator object",
calculator: function(x,y,z) {
return (x + y) * z;
}
};// another object to deomonstrate binding...
const calculatorObjectTwo = {
name: "my second calculator object",
};// now assign the function in the first object to a variable
const useCalc = calculatorObjectOne.calculator;// now bind the function to both these objects, with the first argument being the 'this' which is the object to bind to
const calcDefaultOne = useCalc.bind(calculatorObjectOne, 2,3);
const calcDefaultTwo = useCalc.bind(calculatorObjectTwo, 2,3);// now use the function from both objects
console.log(calcDefaultOne(4));
console.log(calcDefaultTwo(4));
Which returns the same result, only this time we gave the object to bind to as the first parameter.
IIFE (Immediately Invoked Function Expression)
IIFE functions only run ONCE!
When compared to a normal function declaration which can be called multiple times, the IIF gets called once, and immediately…
// normal function declaration which can be called multiple times
const normalFunction = function() {
console.log("called!");
}// call the function as many times as you want!
normalFunction();
normalFunction();
normalFunction();
normalFunction();
normalFunction();// IIFE function - called only once!
(function () {
console.log("called only once...immediately!");
})();
The IIFE can’t be called again as there’s nothing to reference!
Note the special syntax of the IIFE, which is to wrap the entire function expression in (), and then end with another ()
Closures
Closures make it possible for a function to have “private” variables. A closure is a function having access to the parent scope, even after the parent function has closed.
To better understand this, it’s best to show an example of an issue where the following code won’t work….
// Function to increment counter
function add() {
let counter = 0;
counter += 1;
console.log(counter);
}// Call add() 3 times
add();
add();
add();
At first glance, it might appear that the expectation from the console log would be to see the following:
1
2
3
But this isn’t the case at all….What you would see is:
1
1
1
this is because the counter variable, will increment to one, and then this would be stored, however once this function has completed, then essentially this counter variable no longer exists…. so when you call the function again, using add(), you basically start over.
The “counter” variable is local to the function. It’s not a global variable. So for example, the expected result could be generated by moving the variable outside of the function…
let counter = 0;// Function to increment counter
function add() {
counter += 1;
console.log(counter);
}
// Call add() 3 times
add();
add();
add();
One potential issue with this approach, is that you could potentially change the value of counter with a completely different function, because that variable is public to the code.
This “issue” can be tackled with closures….
The following is an example of nesting one function inside another function. The “nested” function will have access to the parent function variable. So whilst the variable itself isn’t a public “global” variable, it can remain private, but the nested function will still have access to it even though the variable itself is not inside the nested function.
// IIFE Function to increment counter
const add = (function () {
let counter = 0;
// nested function
return function () {
counter += 1;
console.log(counter)
}
})();add();
add();
add();
We could also assign the complete function to a variable, and then call that variable…
// Function to increment counter
const add = function () {
let counter = 0;
// nested function
return function () {
counter += 1;
console.log(counter)
}
};// Assign function to variable
const addFunction = add();addFunction();
addFunction();
addFunction();
This works because the new ‘addFunction’ variable has access to the execution context of the ‘add’ function. Because of this, it has always access to the ‘counter’ variable.
this is why it wouldn’t work if the function wasn’t assigned to a variable….
// Function to increment counter
const add = function () {
let counter = 0;
// nested function
return function () {
counter += 1;
console.log(counter)
}
};// will NOT work as expected
add();
add();
add();
We can also do a console.dir on the ‘addFunction’ variable, to see that it does indeed have access to the counter variable inside the ‘add’ function scope.
console.dir(addFunction);
anonymous()
length: 0
name: ""
prototype: {constructor: ƒ}
arguments: (...)
caller: (...)
[[FunctionLocation]]: app.js:783
[[Prototype]]: ƒ ()
[[Scopes]]: Scopes[1]
0: Closure (add) {counter: 3}
[[Scopes]] is an internal property which cannot be accessed from our code, but is still being preserved by JavaScript.