Named and Anonymous Functions
Slide navigation: Forward with space bar, →, or PgDn. Backwards with ← or PgUp.
Defining Functions
- Provide:
- Function name
- Parameter names
- Body
- No parameter, return types:
function average(x, y) {
return (x + y) / 2
}
- What if the arguments aren't numbers?
const result = average('6', '7') // Whatever happens, happens
return
exits the function immediately:
function indexOf(arr, value) {
for (const i in arr) {
if (arr[i] === value) return i
}
}
- If a function exits without
return
, it returns undefined
Higher-Order Functions
- Functions are values:
- Can store functions in variables.
- Can pass functions to functions.
- Can return functions from functions.
(...)
operator invokes function:
let f = average
const result = f(6, 7) // result
is 6.5
f = Math.max
result = f(6, 7) // result
is 7
map
method takes a function parameter:
result = [0, 1, 2, 4].map(Math.sqrt) // result
is [0, 1, 1.4142135623730951, 2]
Function Literals
- Tedious to make functions for small jobs:
function multiplyBy10(x) { return x * 10 }
result = [0, 1, 2, 4].map(multiplyBy10)
- Remedy—function literal:
result = [0, 1, 2, 4].map(function (x) { return 10 * x })
- No need for named functions:
const average = function (x, y) { return (x + y) / 2 }
Arrow Functions
- Compact form for function literals:
const average = (x, y) => (x + y) / 2
- Parentheses optional for single parameter:
const multiplyBy10 = x => x * 10
- Empty parentheses if no parameters:
const dieToss = () => Math.trunc(Math.random() * 6) + 1
- Use block and
return
for complex bodies:
const indexOf = (arr, value) => {
for (const i in arr) {
if (arr[i] === value) return i
}
return undefined
}
- Caution: Use parentheses when returning object literal:
const stats = (x, y) => ({
average: (x + y) / 2,
max: Math.max(x, y),
...
})
Function Literals
Functional Programming
const listItems = items
.filter(i => i.trim() !== '')
.map(i => enclose('li', i))
Now put everything in an ul
element:
const list = enclose('ul',
items
.filter(i => i.trim() !== '')
.map(i => enclose('li', i))
.join(''))
No loop!
What, not how.
Closures
setTimeout
executes an action some milliseconds in the future:
setTimeout(() => console.log('Goodbye'), 10000)
- Let's make this more flexible:
function sayLater(text, when) {
const task = () => console.log(text)
setTimeout(task, when)
}
- How does
text
in the arrow expression stay around?
- The arrow body runs long after
sayLater
returned.
- The nested function captures all variables that it needs.
- Closure:
- A block of code
- Parameters
- “Free” variables—declared outside the function
- The arrow expression also captures
console
.
- In JavaScript, the variable is captured:
let text = 'Goodbye'
setTimeout(() => console.log(text), 10000)
text = 'Hello' // After 10 seconds, displays Hello
- Objects + function values + closures = OO.
function createAccount() {
...
return {
deposit: amount => { ... },
withdraw: amount => { ... },
getBalance: () => ...
}
}
- Create any number of accounts:
const harrysAccount = createAccount()
const sallysAccount = createAccount()
sallysAccount.deposit(500)
- Where is the data?
function createAccount() {
let balance = 0
return {
deposit: amount => { balance
+= amount },
...
}
}
Arguments
- Missing argument? Parameter is
undefined
:
function average(x, y) {
return y === undefined ? x : (x + y) / 2
}
- Alternate solution—default argument.
- Used when no argument (or
undefined
) is supplied in a call.
function average(x, y = x) {
return (x + y) / 2
}
- Multiple default arguments ok:
function average(x = 0, y = x) {
return (x + y) / 2
}
- Extra arguments are ignored:
const result = average(3, 4, 5)
- Capture in rest parameter:
function average(...args) {
let sum = 0
for (x of args) sum += x
return sum / Math.max(1, args.length)
}
- What if the arguments are already in an array (or any iterable)?
const numbers = [3, 4, 5]
const result = average(numbers) // Error!
- Use spread operator to “spread out” elements:
const result = average(...numbers)
- Spread also works inside array literals:
const clone = [...numbers]
const characters = [...'Hello 🌐']
Simulating Named Arguments with Destructuring
- No named arguments in JavaScript functions.
- Simulate with object literal:
mkString(array, { leftDelimiter: '(', rightDelimiter: ')' })
- In implementation, look up properties and supply defaults:
function mkString(array, config = {}) {
if (config.separator === undefined) config.separator = ','
...
}
- Neat trick: Use destructuring.
function mkString(array,
{ separator = ',', leftDelimiter = '[', rightDelimiter = ']' } = {}) {
...
}
- Defines three parameter variables
separator
, leftDelimiter
, and rightDelimiter
.
- Each has a default value.
- Note the default
{}
if no configuration is supplied.
Exceptions
The finally
Clause
Advanced Topics
Hoisting
- Scope of local variable/function: From declaration to end of block.
{ // Start of block
... // Attempting to access someVariable
throws a ReferenceError
let someVariable // Scope starts here
... // Can access someVariable
, value is undefined
someVariable = 42
... // Can access someVariable
, value is 42
} // End of block, scope ends here
- Not quite so simple:
{
function doWork() {
console.log(someVariable) // Ok to access variable
...
}
let someVariable = 42
doWork() // Prints 42
}
- Variable is “hoisted” to enclosing block.
- “Temporal dead zone” from beginning of block to declaration.
- Hoisting isn't all bad—can call a function before it was declared:
function isEven(n) { return n == 0 ? true : !isOdd(n -1) }
function isOdd(n) { return n == 0 ? false : !isEven(n -1) }
Tagged Template Literals
- Tagged template literal: Template literal prefixed by function name.
greeting = String.raw`Hello C:\Windows\System`
- Template literals have embedded expressions:
message = `Next year, ${person.name} will be ${person.age + 1}.`
- Tagged template function gets the pieces: n + 1 strings followed by n arguments.
function strong(fragments, ...values) {
let result = fragments[0]
for (let i = 1; i < fragments.length; i++)
result += `<strong>${values[i - 1]}</strong>${fragments[i]}`
return result
}
- In the call
strong`Next year, ${person.name} will be ${person.age + 1}.`
the strong
function is called as
strong(['Next year, ', ' will be ', '.'], 'Harry', 43)
fragments.raw
is an array of fragments in which \
is only an escape character for `
and $
.