Placeholder Image

字幕列表 影片播放

  • 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 thinghaving 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