Functions are objects
Inspect JavaScript functions as callable objects: name, length, inferred names, custom properties, constructors, toString, and safe forwarding wrappers.
- 01Read a function's propertiesRead a function's
name,length, and code as text, and check it withtypeofandinstanceof Function. - 02Use names and your own properties with careExplain how unnamed functions get names, what a function's inner name does, and why names can change when a site is built.
- 03Write safe wrappersPass on
thisand every argument withapply, and explain whynew Functionis risky.
Functions are objects you can call
You already know functions as actions. You call them with round brackets, (), pass values in, and get a result back. This lesson shows the other side. In JavaScript, a function is also an object. An object is a value that holds named properties, like user.name.
So a function can do the same things as any other object:
- It has properties you can read, like
save.nameandsave.length. - You can add your own properties to it, like
save.count = 0. - It has methods you can call, like
save.toString()andsave.bind(...).
And unlike most objects, you can still call it: save().
save is one object. You can call it with (). You can also read its properties, like any object: save.name is "save" and save.length is 1.A phone contact does two jobs. You can tap it to make a call. You can also read its name and add a note to it. A JavaScript function is the same. You can call it, and you can read and add properties.
- In real life: Tap the contact to call
- In JavaScript: Call the function:
save() - In real life: The contact has a name
- In JavaScript:
save.nameis the function's name - In real life: The contact lists what it needs
- In JavaScript:
save.lengthtells how many inputs the function expects - In real life: Add a note to the contact
- In JavaScript: Add your own property, like
save.count
This lesson covers these topics, in this order:
nameandlength, two properties every function has.- How JavaScript gives a name to a function that has none.
- Your own properties on a function.
- Named function expressions.
new Function, which builds a function from text.- The constructors for generator and async functions.
toString(), which gives a function's code as text.- Wrappers: functions that add a little work around another function.
name and length: the name and the input count
STEP THROUGHEvery function has two built-in properties:
nameis a string. It is the function's name, such as"save".lengthis a number. It counts the function's parameters: the input names in its round brackets. But it only counts the parameters that come before the first one with a default value.
Two words you need for the rule:
- A default value is written with
=, likepriority = "normal". It is used when the caller leaves that input out. - A rest parameter is written with three dots, like
...labels. It collects all the extra inputs into an array. It is never counted.
function save(title, priority = "normal", ...labels) { return title;}console.log(save.name, save.length);Line 1 makes a function named save with three parameters:
titlehas no default value, so it is counted.priority = "normal"has a default value, so counting stops here....labelsis a rest parameter, so it is never counted.
Line 4 prints the name and the length: save 1.
title is left, so save.length is 1.The next example has three functions that all have a length of 0. It also uses bind. add.bind(null, 1, 2, 3) makes a new function, called a bound function. The null sets this. The numbers after it are inputs filled in ahead of time.
function defaultsFirst(value = 3, later) {}function restOnly(...items) {}function add(first, second) { return first + second; }const boundTwice = add.bind(null, 1, 2, 3);console.log(defaultsFirst.length, restOnly.length, boundTwice.length);Line 5 prints 0 0 0. Here is why, line by line:
- Line 1: the first parameter,
value = 3, has a default value. Counting stops before anything is counted. - Line 2:
...itemsis a rest parameter, and rest parameters are never counted. - Line 4:
addhas a length of 2.bindfills in three inputs, so no inputs are left. The new function's length is 0. It never goes below 0.
Step through four functions below and watch JavaScript set each one's name and length.
Step through four functions and watch JavaScript set each one's name and length.
script
return a + b;}const inferred = function (first, second = 2, ...rest) { return first + second + rest.length;};const arrow = (value, index) => value + index;const bound = declared.bind(null, 1);console.log(declared.name, declared.length);console.log(inferred.name, inferred.length);console.log(arrow.name, arrow.length);console.log(bound.name, bound.length);The table below shows common kinds of functions. Read each row as: "this code gives this name and this length."
| Kind of function | Example | What is its name? | What is its length? |
|---|---|---|---|
| A function declaration | function add(a, b) {} | add | 2 |
An unnamed function saved in a const | const add = function (a, b) {} | add | 2 |
| A default value before other parameters | function f(a, b = 1, c) {} | f | 1 |
| A rest parameter | function f(a, ...rest) {} | f | 1 |
bind with one input filled in | add.bind(null, 1) | bound add | 1 |
| A getter | get total() {} | get total | 0 |
function f(a, b = 1, c) {} has a length of 1, not 3. Counting stops at b = 1, so c is not counted either. function g(a, ...rest) {} also has a length of 1, because rest parameters are never counted.
How an unnamed function gets a name
INTERACTIVEMany functions are written without a name, like function () {} or () => {}. These are called anonymous functions. When you save one under a name, JavaScript copies that name onto the function. This is called name inference.
For example, const greet = () => {} gives greet.name === "greet". The function had no name, so it took the variable's name.
Pick a kind of function in the inspector below. It shows the function's name and length, plus two checks: typeof gives the type of a value as text, and instanceof Function asks "is this a kind of Function?"
function add(a, b) { return a + b; }add2functiontruePick a kind of function. The values shown are real JavaScript results, checked by the lesson's tests. Build tools may still change names and code on a live site.
The code below shows five more places a name can come from. Lines 9 to 13 each print one name:
const assigned = function () {};const object = { run() {}, get total() { return 42; }, [Symbol("secret")]() {},};const descriptor = Object.getOwnPropertyDescriptor(object, "total");const fallback = (fn = function () {}) => fn.name;console.log(assigned.name);console.log(object.run.name);console.log(descriptor.get.name);console.log(Object.getOwnPropertySymbols(object).map((key) => object[key].name)[0]);console.log(fallback());- Line 1 saves an unnamed function in
assigned. So line 9 printsassigned. - Line 3 is a method called
run. So line 10 printsrun. - Line 4 is a getter: a function that runs when you read a property, here
object.total. Its name getsgetand a space in front. Line 7 usesObject.getOwnPropertyDescriptorto reach the getter function itself, and line 11 printsget total. A setter getssetin front in the same way. - Line 5 uses a symbol as the method's key. A symbol is a unique key value.
Symbol("secret")has the descriptionsecret. The method's name is that description in square brackets, so line 12 prints[secret]. - Line 8 gives the parameter
fnan unnamed function as its default value. The function takes the parameter's name, so line 13 printsfn.
Before a site goes live, tools called bundlers and minifiers join and shrink the code. They can shorten function names, so save might become a. Use fn.name for logs and messages. Don't use it to decide what your program does, and never for security checks.
Not every function gets a name. A function made with no name nearby has an empty name, "". And a bundler can change a name later. If your code needs a name that never changes, store your own ID next to the function, like { id: "save" }.
bound save- Rest parameters are not counted
fn.count = 3get totalfunction f(a, b = 1, c) {}is1fn.toString()
Sort each card: is it about fn.name, about fn.length, or about neither?
Your own properties on a function
STEP THROUGHA function is an object, so you can add your own properties to it, like any object. For example:
fn.count = 0to count how many times it was called.fn.meta = {}to keep extra facts about it.fn.cache = new Map()to remember results it already worked out.
These properties are public. Any code that can reach the function can read them or change them. If the data should be private, use a closure instead. A closure is a function that keeps using the variables from the place where it was made. You met closures in the Closure patterns lesson.
function greet(name) { return "Hi, " + name;}greet.calls = 0;greet.calls += 1;console.log(greet("Asha"));console.log(greet.calls);- Lines 1 to 3 make a function,
greet, that returns a greeting. - Line 4 adds a property called
callsto the function and sets it to 0. - Line 5 adds 1 to it, the same way as with any object property.
- Line 6 calls the function and prints
Hi, Asha. - Line 7 prints
1. Callinggreetdid not changecalls; only line 5 did.
Any code that can reach greet can also read or change calls. So this is public information about the function, not private data.
The next example counts calls for you. makeCounter puts a count property on the function double. Then it returns a new function that adds 1 to the count each time it runs. Step through it and watch the count change.
A function can carry your own properties. This wrapper counts calls in a property on the function.
script
function makeCounter(fn) { return function counted(...args) { fn.count += 1; return fn(...args); };}function double(n) { return n * 2; }const countedDouble = makeCounter(double);console.log(countedDouble(3));console.log(countedDouble(4));console.log(double.count);To memoize a function means to remember its results, so the same input is not worked out twice. The code below keeps the remembered results in a property.
function memoize(fn) { function memoized(n) { if (!memoized.cache.has(n)) { memoized.cache.set(n, fn(n)); } return memoized.cache.get(n); } memoized.cache = new Map(); return memoized;}- Line 1:
memoizetakes the original function asfn. - Line 2 makes a new function,
memoized, which takes one input,n. - Line 3 checks whether the cache already has an answer for
n. - If not, line 4 works it out with
fn(n)and saves it in the cache. - Line 6 returns the saved answer.
- Line 8 puts an empty
Mapcalledcacheonmemoized, as a property.
Because the cache is a property, other code can look inside it, or empty it with memoized.cache.clear(). If the cache should be private, keep the Map in a closure instead.
How to choose where to keep the data:
- Use a property when other code should be able to see or reset the data.
- Use a closure or a
WeakMapwhen other code must not change it. You'll see aWeakMapnear the end of this lesson. - If you do add a public property, give it a name that other code is unlikely to use too.
Named function expressions
A function expression is a function written where a value goes, such as on the right of =. It can have its own name, like this: const factorial = function inner(n) { … }. Now the function has two names:
factorialis the variable that holds the function. Code outside the function can use it.inneris the function's own name. Only code inside the function can use it.
const factorial = function inner(n) { if (n <= 1) return 1; return n * inner(n - 1);};console.log(factorial(4));try { console.log(inner);} catch (error) { console.log(error.name);}- Line 1 saves the function in
factorial. The function's own name isinner. - Line 2 stops when
nis 1 or less and returns 1. - Line 3 calls
innerfrom inside the function. A function that calls itself like this is using recursion. - Line 5 calls
factorial(4)and prints24, which is 4 × 3 × 2 × 1. - Line 7 tries to read
innerfrom outside the function. It doesn't exist there, so it throws an error. Line 9 prints the error's name,ReferenceError.
factorial holds a function whose own name is inner. Inside the function, inner works, so the function can call itself. Outside, inner does not exist, so line 7 throws a ReferenceError.The inner name has one more use. It keeps working even when the outside variable changes.
let factorial = function inner(n) { return n <= 1 ? 1 : n * inner(n - 1);};const saved = factorial;factorial = null;console.log(saved(4));Line 4 saves the function in a second variable, saved. Line 5 sets factorial to null, so that name no longer holds the function. Line 6 still prints 24. The function calls itself with inner, not with factorial, so the change to factorial doesn't matter.
new Function: a function built from text
JavaScript can build a function from strings of text: new Function("a", "b", "return a + b"). The last string is the function's body. The strings before it are the parameter names.
This site never runs new Function on the page. It is here because you will see it in documentation and in older code. Two words help with the example:
- A global variable can be seen by all the code.
globalThis.shared = 3makes one. - A local variable lives only inside its function, like
secretinsidedemo.
function demo() { const secret = 7; globalThis.shared = 3; const addShared = new Function("value", "return value + shared;"); console.log(addShared(4)); try { const readSecret = new Function("return secret;"); console.log(readSecret()); } catch (error) { console.log(error.name); }}demo();- Line 2 makes a local variable,
secret. Line 3 makes a global one,shared. - Line 4 builds
addSharedfrom text. Its body readsshared. - Line 5 calls it with 4. It can see the global
shared, so it prints7. - Line 7 builds a function whose body reads
secret. - Line 8 calls it. Functions built by
new Functioncan't see local variables, so the call throws. Line 10 printsReferenceError.
new Function turns text into code and runs it, just like eval. That causes three problems:
- If the text comes from a user, they can run any code they like on your page. That is a security hole.
- Many sites block it with a Content Security Policy (CSP): a set of rules that tells the browser which code may run. Then
new Functionfails. - The JavaScript engine can't make this code run as fast as normal code.
It also can't see local variables, as the example shows. The tests for this lesson run the example in a separate, safe place called a sandbox.
If you need behavior that settings can change, use one of these instead: a lookup table (an object that maps names to normal functions), a normal function, or a small reader for the simple text you accept. These keep code apart from data, and they still work under a strict CSP.
Constructors for generator and async functions
A constructor is a function that makes new objects. Function is the constructor for ordinary functions. Three other kinds of functions each have their own constructor:
- Generator functions, written
function*, useGeneratorFunction. - Async functions, written
async function, useAsyncFunction. - Async generator functions, written
async function*, useAsyncGeneratorFunction.
These constructors are not global names, so you can't just type AsyncFunction. You almost never need them. If you do, get one from a real function, as the code below shows. It never builds code from text.
const GeneratorFunction = Object.getPrototypeOf(function* () {}).constructor;const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;const AsyncGeneratorFunction = Object.getPrototypeOf(async function* () {}).constructor;console.log(GeneratorFunction.name);console.log(AsyncFunction.name);console.log(AsyncGeneratorFunction.name);- Line 1 makes an empty generator function.
Object.getPrototypeOfgets the object it inherits from, and.constructoron that object is the function that makes generator functions. - Lines 2 and 3 do the same for an async function and an async generator function.
- Lines 4 to 6 print
GeneratorFunction,AsyncFunction, andAsyncGeneratorFunction.
Generators and async functions get their own lessons later. Here you only need to know they are function objects too.
Each page, iframe, and worker has its own copy of these constructors. An iframe is a page shown inside another page. A worker is code that runs in the background. A function made in an iframe uses that iframe's constructor, not yours. So a check like fn.constructor === AsyncFunction can fail for a function that came from an iframe.
toString: a function's code as text
fn.toString() returns a function's source text: its code, as a string. It works like this:
- For a function you wrote, it usually returns your code.
- For a function built into JavaScript, like
Math.max, it returns a short text with[native code]in it.
On a real site, the browser doesn't get the files you wrote. A tool like Next.js turns your TypeScript and JSX into plain JavaScript first. So toString() shows that changed code, not your original file. This lesson shows its code as fixed text, and its tests check that the code works.
function sample(a) { return a * 2;}console.log(sample.toString().includes("return a * 2"));console.log(Math.max.toString());Line 4 checks that the text of sample includes return a * 2, and prints true. Line 5 prints the text of Math.max, which contains [native code].
toString() is handy when you are curious or debugging. But it is not a tool for taking code apart, and it can't lead you back to your original file. Bundlers, minifiers, and built-in functions can all give text that differs from what you wrote.
Don't save that text to turn it back into a function later. Comments, spaces, changes made by build tools, and built-in markers can all make the text wrong for that job.
Wrappers that pass everything on
STEP THROUGHA wrapper is a function that calls another function and adds a little work around the call. The extra work can be:
- writing a log line,
- timing the call,
- remembering results (caching),
- letting the function run only once.
People often call this kind of wrapper a decorator. In this lesson, decorator just means a wrapper function. It is not the @ syntax you may see in other code; that comes in a later lesson.
A good wrapper is transparent: the caller can't tell it is there. To be transparent, it must pass two things on to the original function, unchanged:
this, the object the method was called on.- Every argument the caller passed in.
So write the wrapper as a normal function, not an arrow function. A normal function gets this from the call, like cart.label(7). An arrow function takes this from the place where it was written. That only fits when you want this fixed on purpose.
You put a present in a gift box and add a card. The card adds something extra. But the person still gets the same present inside.
- In real life: The present inside
- In JavaScript: The original function
- In real life: The box and the card
- In JavaScript: The wrapper's extra work, like a log line
- In real life: Handing over the same present
- In JavaScript:
fn.apply(this, args)passes onthisand every argument
Step through a logging wrapper below. It wraps the label method of a cart object.
A decorator here is just a wrapper function. The safe version passes on this and every argument.
script
function withLogging(fn) { function wrapper(...args) { console.log("calling", fn.name, args.join(",")); return fn.apply(this, args); } return wrapper;}const cart = { prefix: "Item", label(id) { return this.prefix + " #" + id; },};cart.label = withLogging(cart.label);console.log(cart.label(7));console.log(cart.label.name);Here is the same wrapper as plain code:
function withLogging(fn) { function wrapper(...args) { console.log("calling", fn.name, args.join(",")); return fn.apply(this, args); } Object.defineProperty(wrapper, "name", { value: "logged" + fn.name[0].toUpperCase() + fn.name.slice(1) }); return wrapper;}const cart = { prefix: "Item", label(id) { return this.prefix + " #" + id; },};cart.label = withLogging(cart.label);console.log(cart.label(7));console.log(cart.label.name);- Line 1:
withLoggingtakes the original function asfn. - Line 2 makes
wrapper. Its...argscollects every argument into an array. - Line 3 logs
calling label 7before the real work. - Line 4 calls the original with
fn.apply(this, args).applycalls a function with athisyou choose and an array of arguments. Here it passes on the samethisthe wrapper got. - Line 6: you can't change
namewith=, soObject.definePropertysets it tologgedLabel. - Line 13 puts the wrapper in place of
cart.label. - Line 14 calls
cart.label(7). The wrapper getscartasthisand passes it on, so it printsItem #7. Line 15 printsloggedLabel.
cart.label(7) now runs the wrapper. It logs a line, then calls the original label with fn.apply(this, args). this is still cart, so the result is "Item #7".A wrapper must also hand back the original result. The next example has two arguments and returns a value.
function withTrace(fn) { return function (...args) { const result = fn.apply(this, args); console.log("result", result); return result; };}function join(first, second) { return first + " & " + second; }const tracedJoin = withTrace(join);console.log(tracedJoin("tea", "toast"));- Line 2 returns the wrapper. Its
...argscollects both arguments,"tea"and"toast". - Line 3 passes both to
joinwithapply, and keeps the result. - Line 4 logs
result tea & toast. - Line 5 returns the same result, so line 10 prints
tea & toast.
The wrapper added a log line, but the caller still gets the same value.
Where you'll use this
You will meet these facts about functions in real code:
- Logging tools read
fn.nameto label their messages. They still work when a name is missing. - Test helpers and form checkers sometimes read
fn.lengthto guess how many inputs a function wants. It is only a hint, not a promise. - Memoize helpers keep their cache on the wrapper, or in a closure.
- Frameworks, analytics tools, and permission checks wrap your callbacks. A good wrapper passes on
thisand every argument. - An ID that must never change belongs in your own data, like
{ id: "save" }. Don't usefn.namefor it, because names can change.
The table below compares three places to keep data about a function. Read each row as: "if I keep it here, what is it good for, and what does it cost?"
| Where you keep it | What is it good for? | What does it cost? |
|---|---|---|
| A property on the function | Data others should see, like fn.count, fn.cache, or fn.meta. | Anyone can read it or overwrite it. |
| A closure | Private data, like a counter, a run-once flag, or a hidden cache. | Code outside can't look at it or reset it. |
| A WeakMap | Data linked to a function, without changing the function. | It takes more code, but no property names can clash. |
A WeakMap is a map whose keys are objects, such as functions. Here is how it keeps data about a function outside the function:
const labels = new WeakMap();function saveDraft() {}labels.set(saveDraft, { status: "draft" });console.log(labels.get(saveDraft).status);console.log(saveDraft.status);- Line 1 makes an empty
WeakMapcalledlabels. - Line 2 makes a function,
saveDraft. - Line 3 links
saveDraftto the object{ status: "draft" }. - Line 4 looks it up and prints
draft. - Line 5 prints
undefined, because the function itself never got astatusproperty.
Library code uses this to keep its own private notes about functions. Its names can't clash with properties that other code adds.
Common misconceptions
Here are six common wrong ideas about functions. Each one comes with what is really true.
“Functions are not objects, because typeof fn is "function".”
typeof gives a special answer, "function", for objects you can call. A function is still an object. It has properties, and fn instanceof Function is true.
“length is how many arguments were passed.”
fn.length belongs to the function, and it is the same for every call. It counts the parameters before the first default value.
“fn.name is safe to use in program logic.”
Names are helpful labels, but they can change. bind adds bound and a space in front, and minifiers can shorten names. Use your own IDs for anything important.
“The inner name of a named function expression works outside it.”
The inner name works only inside the function. Outside, use the variable that holds the function.
“A wrapper can just call fn(...args).”
That call has no object in front of it, so a method loses its this. Use fn.apply(this, args) to pass this on.
“new Function is a harmless shortcut.”
It turns a string into code and runs it, like eval. Avoid it in your app, most of all with text that comes from users.
Practice: function objects
5 EXERCISESGuess the answer before you run each program. Use the hints if you get stuck.
What does the second log line print? Write it exactly.
function save(a, b = 1, ...rest) {}
const wrapped = save.bind(null, "draft");
console.log(save.name, save.length);
console.log(wrapped.name, wrapped.length);The first line prints save 1. bind makes a new function named bound save. It fills in a, so no counted parameters are left, and its length is 0. The second line prints bound save 0.
Read the code. What number does the last line print?
function track(fn) {
fn.count = 0;
return function (...args) {
fn.count += 1;
return fn(...args);
};
}
function shout(word) { return word.toUpperCase(); }
const tracked = track(shout);
tracked("hi");
tracked("bye");
console.log(shout.count);function track(fn) {
fn.count = 0;
return function (...args) {
fn.count += 1;
return fn(...args);
};
}
function shout(word) { return word.toUpperCase(); }
const tracked = track(shout);
tracked("hi");
tracked("bye");
console.log(shout.count);shout.count starts at 0. Each call to the wrapper adds 1 and then calls shout. The code calls it twice, so it prints 2.
thisThe wrapper should pass the call on, so the method still sees its object. What does the last line print?
function logCalls(fn) {
return function (...args) {
return fn.apply(this, args);
};
}
const user = {
prefix: "User",
label(id) { return this.prefix + ":" + id; },
};
user.label = logCalls(user.label);
console.log(user.label(5));function logCalls(fn) {
return function (...args) {
return fn.apply(this, args);
};
}
const user = {
prefix: "User",
label(id) { return this.prefix + ":" + id; },
};
user.label = logCalls(user.label);
console.log(user.label(5));The wrapper returns fn.apply(this, args). When user.label(5) calls the wrapper, this is user, and apply passes it on. So the original method can read this.prefix, and it prints User:5.
new Function see?Write the two printed lines. Why is the first one a number and the second one an error name?
function demo() {
const secret = 7;
globalThis.shared = 3;
const readShared = new Function("return shared;");
console.log(readShared());
try {
const readSecret = new Function("return secret;");
console.log(readSecret());
} catch (error) {
console.log(error.name);
}
}
demo();The first function reads the global shared and prints 3. The second one tries to read secret, which is local to demo, so it can't see it. The call throws, and the catch prints ReferenceError.
thisThe wrapper should pass the call on, so box.getValue() still works. What does the fixed code print?
function wrap(fn) {
return function (...args) {
return fn.apply(this, args);
};
}
const box = {
value: 9,
getValue() { return this.value; },
};
box.getValue = wrap(box.getValue);
console.log(box.getValue());function wrap(fn) {
return function (...args) {
return fn.apply(this, args);
};
}
const box = {
value: 9,
getValue() { return this.value; },
};
box.getValue = wrap(box.getValue);
console.log(box.getValue());The wrapper passes on both this and every argument. So box.getValue() still sees box as this, and it returns 9.
Quiz: check your understanding
7 QUESTIONSTry to guess each output before you click. For each code question, first ask what is printed: a name, a length, or the result of a wrapper. Every answer explains why it is right or wrong.
Question 1 of 7What name and length does this function get?
Read the code, then predictconst greet = function (name, punctuation = "!") {}; console.log(greet.name, greet.length);Choose an answer to see the explanation.
Question 2 of 7Which is true about your own properties on a function?
Choose an answer to see the explanation.
Question 3 of 7What name does the bound method get?
Read the code, then predictconst obj = { run() {} }; const again = obj.run.bind(obj); console.log(again.name);Choose an answer to see the explanation.
Question 4 of 7Why is
new Functionrisky in your app?Choose an answer to see the explanation.
Question 5 of 7Which line makes a wrapper safe for methods?
Choose an answer to see the explanation.
Question 6 of 7What name does the method with a symbol key get?
Read the code, then predictconst key = Symbol("job"); const tasks = { [key]() {} }; console.log(tasks[key].name);Choose an answer to see the explanation.
Question 7 of 7What does
fn.toString()usually give back?Choose an answer to see the explanation.
Key takeaways
- A function is an object you can call. You can run it, and you can read and add properties.
nameis the function's name.lengthcounts the parameters before the first default value. Rest parameters are never counted.- An unnamed function takes a name from where it is saved: a variable, an object key, a getter, a default value, or a symbol.
bindaddsboundin front. - Your own properties on a function are public. Use a closure for private data.
- The inner name of a named function expression works only inside the function.
new Functionturns text into code. It can't see local variables. Avoid it in your app.- A good wrapper passes the call on with
fn.apply(this, args).
Remember the one-liner.
A function is an object you can call: you can run it, read its properties, add your own, and wrap it.
Up next: Methods on primitives. It explains how simple values, like strings, seem to have methods.