Subtitles section Play video
Tonight is Act III: Function the Ultimate. We're going to be talking about functions
tonight. Functions are the very best part of JavaScript. It's where most of the power
is, it's where the beauty is. Like everything else in JavaScript, they're not quite right,
but you can work around that, and there's a lot of good stuff here.
Tonight, unlike the previous two nights, I'm going to be showing you quite a lot of code.
Because we're talking about functions, you need to see how they work. I personally tend
to fall asleep in presentations that put a lot of code on the screen; it's just kind
of not a good time, so I have a lot of examples and I tried to make them all fit on one screen
in big type. They're all going to be simple, but they should be interesting and useful.
Let's begin.
Function is the key idea in JavaScript. It's what makes it so good and so powerful. In
other languages you've got lots of things: you've got methods, classes, constructors,
modules, and more. In JavaScript there's just function, and function does all of those things
and more. That's not a deficiency, that's actually a wonderful thing — having one
thing that can do a lot, and can do it brilliantly, at scale, that's what functions do in this
language.
Here's what a function is. A function is the word 'function'. It optionally has a name,
which can be used to allow it to call itself. It can have a set of parameters, which are
wrapped in parens, containing zero or more names which are separated by commas. It can
have a body which is wrapped in curly braces, containing zero or more statements. A function
expression like that produces an instance of a function object. Function objects in
this language are first class, which means that they can be passed as an argument to
another function, they may be returned as a return value from a function, they can be
assigned to a variable, and they can be stored in an object or an array.
Anything you can do with any other kind of value in this language, you can do with a
function. A function expression is like an object literal in that it produces a value,
except in this case it produces something that inherits from Function.prototype. It
may seem kind of strange that a function can inherit methods from something else, but it
can. So in this language, functions have methods. That may sound odd, but we've got that. I'll
show you some examples of that.
We have a var statement which allows us to declare and initialize variables within a
function. Because JavaScript is not a strongly typed language you don't specify types in
the var statement, you just give a name for the variable. Any variable can contain any
value that's expressible in the language. A variable that's declared anywhere within
a function is visible everywhere within the function; we don't respect block scope.
The way var statements work is the var statement gets split into two pieces. The declaration
part gets hoisted to the top of the function and is initialized with undefined. Back at
the place where the original var statement was, it gets turned into an assignment statement
so that the var gets removed. Here we have an example. I've got myVar = 0 and myOtherVar.
What that does is, at the top of the function it defines myVar and myOtherVar and sets them
both to undefined. Then at the point in the function where the original var statement
was, we have an assignment statement. The separation and the hoisting operation changes
the way you might think of the scoping of variable names.
We also have a function statement. Unfortunately, the function statement looks exactly like
a function expression. The only difference is that the name, instead of being optional,
is now mandatory. But in all other respects it looks exactly the same, and it is confusing
to have both. Why do we have both? Well, the function statement was the older thing, and the function expression,
which is really the more useful form, was added to the language later. What the function
statement does is it expands into a var statement which creates a variable and assigns a function
value to it. That expansion, because it's actually a var statement, splits into two
things. Except unlike the ordinary var statement that we saw earlier, both pieces of it are
hoisted to the top of the function, so things are not necessarily declared in the order
that you think they are.
It's confusing having both function expressions and function statements, so how do you know
which is which when you're looking at it? The rule is, if the first token of a statement
is function, then it's a function statement. Otherwise, it's a function expression. Generally,
function expressions are easier to reason about. For example, you can't put a function
statement inside of an if statement because of the hoisting stuff. You might want to have
a different function being defined if you take the else branch or the then branch, but
hoisting doesn't look at branching, and it happens before we know the result of the if,
so the language definition says that you can't do that. It turns out every browser lets you
do that anyway, but because the language definition doesn't tell you what it's supposed to do,
they all do something different. That's one of those edge cases that you want to stay
away from.
In this language we have function scope. In most other languages that have C syntax we
have block scope, but because of the way vars get hoisted, block scope doesn't work in this
language. In JavaScript, blocks do not have scope. Scope means that, in another language
such as Java, if you declare a variable inside of curly braces, it's visible only inside
of the curly braces and not outside. But that doesn't happen in JavaScript because of hoisting.
The variable declaration gets pulled out of the if statement and moved to the top of the
function, so the variables will be visible everywhere within the function. Only functions,
in this language, have scope. If you declare a variable in a function, that variable is
not visible outside of the function, but it's still visible everywhere within the function.
If you're coming from other languages, this can be confusing. For example, a function
like this will work in most other languages and will fail in JavaScript without an error.
What you'll find is that it will run forever, and that's because the programmer thinks he's
created two i variables, but in fact there's only one i variable. So the inner loop is
constantly resetting the i value so that the outer loop will never finish. That's something
to be aware of: in JavaScript, you can't be depending on block scope.
Because of hoisting, because of the way that variable statements and function statements
work, I recommend that you declare all variables at the top of the function and declare all
functions before you call them. In other languages the prevailing style is to declare variables
near the site of their first use, and in languages which have block scope that's good advice,
but I don't recommend it in this language.
We have a return statement. A return statement allows a function to return early, and also
indicates what value the function should be returning. There are two forms of it: there's
one that takes an expression, and one that does not. If there's no expression, then the
value that gets returned is undefined. It turns out, every function in JavaScript returns
a value, and if you don't explicitly say what the value is, it will return the undefined
value. Unless it was called as a constructor, in which case it will return the new object
that you're constructing. One other note: you cannot put a line break between the word
return and the expression. Semi-colon insertion will go in and turn it into a statement that
returns undefined, which is tragically awful.
There are two pseudo parameters that every function can receive. One is called arguments,
and the other has the unfortunate name of this. Let's look at arguments first. When a function
is invoked, in addition to the parameters that it declares, it also gets a special parameter
called arguments. It contains all of the arguments that were actually specified in the invocation.
It is an array-like object, but it is not an array, which is unfortunate. I'll show
you some examples of why that's unfortunate. It's array-like in that it has a length property,
so you can ask arguments how many arguments were actually passed to this function, which
might be different than the number of parameters that you specify.
It also has very weird interaction with parameters. If you change one of the elements of the arguments
array, you may change one of the parameters that it's associated with. If you do something
really scary like splicing on the arguments array, you may scramble and reassign all of
your parameters. Generally, you don't want to mess with the arguments array. While the
language doesn't require you to treat it as a read-only structure, I highly recommend
that you treat it as a read-only structure.
OK, let's look at an example. I want to have a function in which I can pass it some number
of numbers and it will then add them all and return the result. The way I do that is I
first look at arguments.length to find out how many numbers I'm going to be adding. Then
I will have a loop which will go through each of those members of the arguments' pseudo
array and figure out the total, and then when it's done it returns the total. This is how
you would write that in ES3, or in the third edition of the ECMAScript Standard. This gets
a little bit nicer in the fifth edition. In the fifth edition, arguments is more array-like
than before. It's more array-like in that it actually inherits, now, from array.prototype,
and array.prototype now contains some interesting functions like reduce. I can call arguments.reduce
and pass it a function that does adding, and the result of that will be to add up all the
members of that array and return it. I think it's a more elegant way of expressing the
same program.
Then we have the this parameter. I'm discovering that I don't like the name 'this' because
it makes it really difficult to talk about it. My first sentence: 'the this parameterĂ¢â‚¬Â¦'
Already you're in trouble. I mean, it's just hard to talk about it in doing code reviews:
'oh, I see your problem, this is wrong.'
[laughter]
Well, you might be right.
So what is this? The this parameter contains a reference to the object of invocation. This
allows a method to know what object it is concerned with. It allows a single instance
of a function object to serve as many functions. You can take a single function object and
store it in lots of different objects, or put it in lots of prototypes, and allow it
to be inherited by even more objects. There's just one instance of the function in the system,
but all of those objects think that they have that method, and they will do the right thing
with it because they use this to figure out what object they should actually be manipulating.
So this is the key to prototypal inheritance. Prototypal inheritance works in this language
because of this.
We have the parens suffix operator, which is used for invoking, or calling, or executing
the function. It surrounds zero or more comma separated expressions which will become the
arguments of the function, and those arguments will be bound to the parameters of the function.
If a function is called with too many arguments, the extra arguments are ignored. You don't
get an error for that, they're just ignored. But they'll still go into the arguments array,
so if you want to find out about them they're still accessible to you. If a function is
called with too few arguments, that's not an error either. It will fill in undefined
for any things that you did not include. There's no implicit type checking at all, so if the
types of the parameters are important to you then you need to check them yourself within
your function.
There are four ways to call a function. There's the function form, the method form, the constructor
form, and the apply form. They differ in what they do with this. In the method form, we
have an object, and then we say dot function name or subscript, some method name, and then
pass them arguments that will call the function and it will associate this with whatever that
object was. That will allow the function, then, to manipulate this.
Then there's the function form, in which we simply take a function value and call it immediately.
In this case there's no object to associate this to, so in ES3 this was set to the global
object, which was just awful. In ES5/Strict we improve that a little bit: we now bind
this to undefined, which is less awful. But one problem with this form is that sometimes
if you have an inner function inside of an outer method, and that method wants the inner
function to have access to this, but it doesn't have access to it because it has its own this
which is different than the outer this. So in order to make this visible to the inner
function, the outer function can declare a variable, perhaps called that, assign this
to it, and then the inner function will have access to that.