Functions, parameters and return values
You have been writing functions for two lessons already: every listener was one. This lesson treats them as tools in their own right, reusable pieces of code that take some input and hand back a result. By the end you will have a small calculator that turns a front side bus (FSB) speed and a multiplier into a clock speed, the same sum the hardware guides on this site do by hand.
Declaring and calling
function greet() {
console.log('Hello from the bench');
}
greet(); // prints the message
greet(); // and again
The word function, a name, a pair of parentheses, then a body in braces. Writing the declaration does nothing on its own. The code inside runs when you call the function: its name followed by parentheses.
Function declarations are hoisted, meaning the browser registers them before it runs any of the script. You can call greet() on a line above the declaration and it still works, which lets you put the main flow at the top of a file and the details further down.
Parameters and arguments
Parameters are the names listed between the parentheses of the declaration. Arguments are the values you pass in when you call it.
function clockSpeed(fsb, multiplier) {
return fsb * multiplier;
}
const mhz = clockSpeed(333, 9);
console.log(mhz); // 2997
Inside the function, fsb is 333 and multiplier is 9 for this call. Check it by hand: 333 x 9 = 2,997 MHz. The real bus clock is 333.33 MHz, which is why a chip with these settings is listed as 3.0 GHz.
JavaScript does not check arguments for you. Leave one out and that parameter is undefined, so clockSpeed(333) multiplies by undefined and returns NaN, short for "not a number". Pass extra arguments and they are ignored. A default value covers the missing case:
function clockSpeed(fsb, multiplier = 9) {
return fsb * multiplier;
}
clockSpeed(333); // 2997, the multiplier falls back to 9
clockSpeed(266, 8); // 2128
What return does
return has two jobs. It sends a value back to whoever called the function, and it ends the function on the spot, so nothing after it runs. A function without a return gives back undefined, which is why console.log(greet()) prints the greeting and then undefined.
Returning a value instead of printing it keeps a function useful. clockSpeed does not care whether its answer ends up in the console, on the page or inside another calculation:
function toGHz(mhz) {
return (mhz / 1000).toFixed(2);
}
console.log(toGHz(clockSpeed(333, 9))); // "3.00"
2,997 divided by 1,000 is 2.997, and toFixed(2) rounds it to two decimals. It returns a string, which is exactly what you want for display.
Function expressions and arrows
The previous lesson introduced anonymous and arrow functions. You can store either kind in a variable and call it by that name:
const doubled = function (n) {
return n * 2;
};
const area = (width, height) => width * height;
area(4, 3); // 12
When an arrow function's body is a single expression with no braces, the value of that expression is returned automatically. Add braces and you must write return yourself. Forgetting that is a classic way to get a function that mysteriously returns undefined.
Unlike declarations, these are not available before their line. Calling area(4, 3) above const area = ... throws a ReferenceError about accessing area before initialization.
Scope in one paragraph
Variables declared with let or const inside a function exist only inside it. That is a feature: two functions can each have a variable called result without stepping on each other. A function can read variables from the code around it, though, which is how your listeners use a button or field declared at the top of the script.
Worked example: a clock speed calculator
<div class="calc">
<label>FSB (MHz) <input id="fsb" type="number" value="333"></label>
<label>Multiplier <input id="multi" type="number" value="9" step="0.5"></label>
<p id="clock-result"></p>
</div>
const fsbInput = document.querySelector('#fsb');
const multiInput = document.querySelector('#multi');
const result = document.querySelector('#clock-result');
function clockSpeed(fsb, multiplier = 9) {
return fsb * multiplier;
}
function showResult() {
const fsb = Number(fsbInput.value);
const multiplier = Number(multiInput.value);
const mhz = clockSpeed(fsb, multiplier);
result.textContent = `${fsb} MHz x ${multiplier} = ${mhz.toFixed(0)} MHz`;
}
fsbInput.addEventListener('input', showResult);
multiInput.addEventListener('input', showResult);
showResult();
Read it in this order:
- The three
constlines find the elements once, at the start. clockSpeeddoes the arithmetic and nothing else.showResultreads both fields, converts them to numbers, callsclockSpeedand writes the answer onto the page.- The
inputlisteners from the events lesson rerunshowResulton every change, and the last line runs it once so the result is there when the page opens.
Two details in showResult are worth a closer look. An input's value is always a string, even with type="number". Multiplication happens to convert strings to numbers for you, but addition joins them instead: '333' + 9 is '3339'. Converting with Number() up front avoids that whole family of bugs.
The text in backticks is a template literal. Anything inside ${ } is evaluated and dropped into the string, which reads far better than gluing pieces together with +. With a multiplier of 9.5, the line reads "333 MHz x 9.5 = 3164 MHz", because toFixed(0) rounds 3,163.5 up.
Try it
Build the calculator in the online HTML editor, then add the toGHz function and show both units on the result line. Finally, clear the FSB field completely. The result drops to 0 MHz, because Number('') is 0, and deciding what to show for an empty field instead is exactly what the if statements in the next lesson are for.