Functions - pengmai/javascript-notes GitHub Wiki
Defining Functions
There are two ways to define functions in modern JavaScript: using the function keyword and using arrow functions. For simplicity, I'll refer to using the function keyword as the "regular" way of defining functions (after all, this method came first).
Consider the following Python function that adds 1 to its parameter:
def add1(x):
return x + 1
print(5) # Prints 6
Note: I use the term parameter to describe the variable inside the function's parentheses ("x" in this case) and the term argument to describe the data that gets passed to the function (5 in this case).
In JavaScript, you could express this function in two ways:
// Defining a function using the "function" keyword, a.k.a. the "regular" way
function add1_function_keyword(x) {
return x + 1;
}
// Defining an arrow function
const add1_arrow_v1 = (x) => {
return x + 1;
}
console.log(add1_function_keyword(5)); // Also prints 6
console.log(add1_arrow_v1(5)); // Same here
As you can see, calling a function in JavaScript looks pretty much the same as in Python.
In the case that you want to write an arrow function with only one statement that immediately returns a value, you can omit the curly braces and return keyword. JS assumes that the expression will be returned (this is generally called an implicit return):
const add1_arrow_v2 = (x) => x + 1;
console.log(add1_arrow_v2(5)); // This is the same as above
If you have exactly one argument, you can even omit the parentheses () around the argument:
const add1_arrow_v3 = x => x + 1;
console.log(add1_arrow_v3(5)); // Still prints 6
Just like Python, functions can have 0 arguments or many arguments:
// Arrow functions require parentheses when there are 0, 2, or more arguments.
const func_no_arguments = () => 5;
function func_two_arguments(x, y) {
return x + y;
}
console.log(func_no_arguments()); // Prints 5
console.log(func_two_arguments(2, 5)); // Prints 7
Should I use arrow functions or regular functions?
There are some differences between arrow functions and regular functions, but they can be quite subtle. Most of the time you can freely use either, but we'll see some cases later on where you have to use one form and not the other. In general, it's probably best to stick to arrow functions if you can so you don't have to spend time trying to decide which one to use.
As a preview/rule of thumb, you should be extra careful if (we'll explain all of these later!):
- Your function body contains the
thiskeyword - You're writing a method on a class
- You're using the
argumentskeyword (this is quite rare)
Optional: you can read about the differences between arrow functions and regular functions here
Quirks with calling functions
Unlike Python (and also unlike many other programming languages), JavaScript does not require that functions be called with the same number of arguments as parameters. By default, if you call a function with fewer arguments, the extra parameters will be set to undefined. If you pass in more arguments than function parameters, any extra arguments will be ignored:
const is_undefined = arg => arg === undefined;
// is_undefined expects 1 argument, but none were given! This doesn't throw an error.
console.log(is_undefined()) // true, "arg" is set to undefined.
console.log(is_undefined("hello")) // false
console.log(is_undefined(undefined, 6)) // true, the second argument is ignored
The fact that this happens is somewhat controversial because it can easily be a source of bugs if you accidentally use the wrong number of arguments. Fortunately, there are tools like TypeScript that can alert you of cases where you're calling a function differently than it was defined.
First-class Functions and Callbacks
In JavaScript, functions are a first-class construct, which means they are values that can be assigned to variables and passed around (we'll see examples of this shortly). Python is actually the same way, but we'll nonetheless go into some detail about first-class functions because they aren't something you'd usually see in a first year "Intro to Programming" course.
As an example, this is a program that schedules a console statement 3 seconds (or 3000 milliseconds) from now:
// Note that unlike before, there's no function name right after the "function" keyword.
setTimeout(function() {
console.log("Sorry I'm late!");
}, 3000);
// Equivallently expressed as an arrow function:
setTimeout(() => {
console.log("Sorry I'm late!");
}, 3000);
Quick aside: a function without a name like we see in the cases above is called an anonymous function.
setTimeout() is a built-in function that takes two arguments: a function to execute once the timer has elapsed, and a number representing the number of milliseconds to wait before executing the function. A naïve implementation might look like this:
function setTimeoutImplementation(func, ms) {
waitFor(ms); // This is a made-up function that delays execution.
func(); // setTimeout actually calls "func" for us!
}
The real implementation is much more complex, but the algorithm is essentially what we see above.
If you're new to this concept, take a second to let that sink in. The function, setTimeout(), takes in a function as an argument. This is different than all the previous examples because if you have, for example, console.log(is_undefined()), the is_undefined() function is executed before being passed in. As a result, the actual value of the argument to console.log() is a boolean. However, in the most recent example, the function is not executed, so the value of the argument to setTimeout() is a function.
To really illustrate the difference, try running the following snippet:
function return_five() {
return 5;
}
console.log(return_five()); // Immediately executed. This prints 5.
console.log(return_five); // Not executed! What do you see?
Depending on your background, this could seem mind-bending, confusing, or you could be thinking "so what?". Well, the idea of functions being passed as arguments to other functions is one of the key aspects of Functional Programming, which is an immensely powerful programming paradigm. Functional programming is a complex topic that we likely won't go too much into, but its achievements include heavily influencing the design of frameworks like React.
Callbacks
When one function, say f, is passed into another function, say g, such that the code looks like g(f), we say that f is a callback given to g.
The intention is that
gwill callf"back" at some point in the future.
Even outside of the world of functional programming, callbacks are immensely important in JavaScript. They're used to do things like handle user input and when communicating with external databases and servers over the internet. You'll see tons of them as you continue in your JavaScript careers, so it's important to get comfortable with them.
Aside: I personally struggled a lot with this concept when I was first learning JS. There were countless aggravating moments when I accidentally called a function I was supposed to just pass along. Watch those parentheses!
Footnote: functions are values
Note that the syntax I've shown to create an arrow function is pretty much the same as the syntax for assigning to any other constant. In fact, since we can assign different types of values to the same variable, this code is totally legal:
// "myvar" starts life as a humble string.
let myvar = "I'm a string";
console.log(myvar); // I'm a string
myvar = (x, y) => x + y;
console.log(myvar(5, 4)) // 9: "myvar" is a function now!
myvar = true;
console.log(myvar); // true: Now, myvar is a boolean.
We just assigned a function to a variable that used to hold a string. This is another aspect of first-class functions: they're just values, same as strings, numbers, and booleans.
Footnote: if I can pass in a function, can I return one, too?
Yes! This is also totally valid in JavaScript:
const adder = x => {
return y => x + y;
};
const add3 = adder(3);
console.log(add3(5)); // 8
console.log(adder(2)(3)); // 5: note the double call!
This is less common in everyday JS code, but is immensely useful if you're writing more sophisticated programs. Given that it's less common, it's okay if you don't immediately see the benefit in doing this.