252 lines
10 KiB
Markdown
252 lines
10 KiB
Markdown
# Chapter 8. Functions
|
|
|
|
**Function** = a block of JS code that's defined once but may be invoked multiple times.
|
|
* *argument*: values provided by during invocation as function's parameters.
|
|
* *return value*: computed by function using argument values
|
|
* *invocation context*: value of `this` keyword for each invocation
|
|
|
|
Utilization of functions:
|
|
* **method of object**: if function is assigned to a property of an obj.
|
|
* When a function is invoked on an object (e.g. `obj.method()`), this object become invocation context (aka `this`) for the function
|
|
* **Constructor**: function designed to initialize a new obj.
|
|
|
|
Function as **first class citizen** (object):
|
|
* In JS, functions are obj. Hence, can be manipulated by programs
|
|
* We can set properties on functions and invoke methods on them (i.e. pass function as parameter to method)
|
|
|
|
JS function definitions can be nested within other functions
|
|
|
|
## 8.1 Defining Functions
|
|
|
|
### 8.1.1 Function Declarations
|
|
|
|
**Function declaration (函数声明)** = `function` keyword + Identifier as function name + `(param1, param2, ...)` + `{JS statements as function body}`
|
|
* `return` causes function to stop executing and return computed value to caller
|
|
* if body has no `return`, value of function is `undefined`
|
|
|
|
```js
|
|
// Print the name and value of each property of o. Return undefined.
|
|
function printprops(o) {
|
|
for(let p in o) {
|
|
console.log(`${p}: ${o[p]}\n`);
|
|
}
|
|
}
|
|
|
|
// Compute the distance between Cartesian points (x1,y1) and (x2,y2).
|
|
function distance(x1, y1, x2, y2) {
|
|
let dx = x2 - x1;
|
|
let dy = y2 - y1;
|
|
return Math.sqrt(dx*dx + dy*dy);
|
|
}
|
|
|
|
// A recursive function (one that calls itself) that computes factorials
|
|
// Recall that x! is the product of x and all positive integers less than it.
|
|
function factorial(x) {
|
|
if (x <= 1) return 1;
|
|
return x * factorial(x-1);
|
|
}
|
|
```
|
|
|
|
* Name of function (in function declaration) becomes a variable, whose value is function itself.
|
|
* function declaration statements are "hoisted" (level up) to top of enclosing block.
|
|
* All function in a JS block will be defined before JS interpreter start execution
|
|
|
|
### 8.1.2 Function Expressions
|
|
|
|
Multiple Function Expression e.g.
|
|
```js
|
|
// This function expression defines a function that squares its argument.
|
|
// Note that we assign it to a variable
|
|
const square = function(x) { return x*x; };
|
|
|
|
// Function expressions can include names, which is useful for recursion.
|
|
const f = function fact(x) { if (x <= 1) return 1; else return x*fact(x-1); };
|
|
|
|
// Function expressions can also be used as arguments to other functions:
|
|
[3,2,1].sort(function(a,b) { return a-b; });
|
|
|
|
// Function expressions are sometimes defined and immediately invoked:
|
|
let tensquared = (function(x) {return x*x;}(10));
|
|
```
|
|
|
|
**Function Expression (FE 函数表达式)**:
|
|
* FE appear within context of a larger expression, or within statement
|
|
* name of function in FE is *optional*. (e.g. 1st FE e.g. has no function name)
|
|
* FE/FD declare variable:
|
|
* How FD use variable: (follow e.g. in 8.1.1 `function factorial(x)`) declares a variable and assigns a function obj to it.
|
|
* How FE use variable: developer can decide whether assign the newly defined function obj to a const or var, so we can refer to it mult-times later. (e.g. 3rd & 5th FE e.g. does not assign function obj to obj, and directly use it)
|
|
* Good practice: assign FE to `const` to protect function obj.
|
|
|
|
**FD vs. FE**:
|
|
* Function defined by FD: the func obj are created before the script get executed (i.e. hoisted), so we can call these functions from code that appears above FD.
|
|
* Functions defined by FE **DO NOT EXIST** until FE are evaluated.
|
|
* To invoke a function (either defined using FE/FD), JS must can refer to it, function defined by FE cannot be referred until it's assigned to a variable
|
|
|
|
### 8.1.3 Arrow Functions
|
|
|
|
In ES6, **Arrow Function** provide more compact function syntax. **Use arrow => to separate function parameters from function body**
|
|
|
|
Syntax of arrow function:
|
|
|
|
* *general form*: comma-separated list of params in parentheses, followed by `=>` arrow, followed by function body in curly braces
|
|
```js
|
|
const func_var = (param1, param2) => { return param1 + param2; };
|
|
```
|
|
* *compact form (single return)*: if function body is a single `return` statement, omit `return`, semicolon, and curly braces
|
|
```js
|
|
const func_var = (param1, param2) => param1 + param2;
|
|
```
|
|
* *compact form (1 param)*: if arrow func has only 1 param, omit `()`
|
|
```js
|
|
const func_var = param => param*param + 2*param;
|
|
```
|
|
* *compact form (no param)*: if arrow func has no param, `()` must be there
|
|
```js
|
|
const constantFunc = () => 42;
|
|
```
|
|
|
|
Additional Syntax rules:
|
|
* **No newline btw `(param)` and `arrow`**: it will create valid statement with other meaning (e.g. `const polynomial = x`)
|
|
* **{} must needed in body for single return statement**: it will avoid syntactic ambiguity:
|
|
```js
|
|
const f = x => { return { value: x }; }; // Good: f() returns an object
|
|
const g = x => ({ value: x }); // Good: g() returns an object
|
|
const h = x => { value: x }; // Bad: h() returns nothing
|
|
const i = x => { v: x, w: x }; // Bad: Syntax Error
|
|
```
|
|
|
|
#### Where to use arrow function?
|
|
|
|
It's like lambda function in Python, it's ideal to pass arrow function to another function
|
|
```js
|
|
// Make a copy of an array with null elements removed.
|
|
let filtered = [1,null,2,3].filter(x => x !== null); // filtered == [1,2,3]
|
|
```
|
|
|
|
#### Arrow Func vs other Funcs
|
|
|
|
* Arrow func inherit value of `this` from the environment (where it's defined)
|
|
* Arrow func do not have a `prototype` property, hence it cannot be used as constructor for new classes.
|
|
|
|
### 8.1.4 Nested Functions
|
|
|
|
```js
|
|
function hypotenuse(a, b) {
|
|
function square(x) { return x*x; }
|
|
return Math.sqrt(square(a) + square(b));
|
|
}
|
|
```
|
|
|
|
Scoping rule of nested function: enclosure function can access param and var of the functions they are nested within (i.e. inner function know outer function's param)
|
|
|
|
## 8.2 Invoking Functions
|
|
|
|
JS will not execute function body, when function is defined. Rather executed when func is invoked
|
|
|
|
5 ways to invoke JS functions:
|
|
* As functions
|
|
* As methods
|
|
* As constructors
|
|
* Indirectly through `call()`, `apply()`
|
|
* Implicitly
|
|
|
|
### 8.2.1 Function Invocation
|
|
|
|
**Invocation Expression**:
|
|
```js
|
|
func_name(param1, param2);
|
|
```
|
|
* params can be any argument expression. JS will evaluate these expression and then use result as args.
|
|
* For no `return` function, value of return is `undefined`
|
|
|
|
Use **conditional invocation** on invocation expression: invoke the function only if it's not `null` or `undefined`
|
|
```js
|
|
f?.(x)
|
|
```
|
|
equivalent to
|
|
```js
|
|
(f !== null && f !== undefined) ? (fx) : undefined
|
|
```
|
|
|
|
### 8.2.2 Method Invocation
|
|
|
|
A **method** = JS function stored in a property of an object.
|
|
|
|
* **Defining a function method**, given object `o`, method name `m`, and a function `f`:
|
|
```js
|
|
o.m = f;
|
|
```
|
|
|
|
* **Invoking object method**:
|
|
```js
|
|
o.m(param1, param2);
|
|
```
|
|
|
|
Method invokation can also use `[]` instead of dot notation:
|
|
```js
|
|
o["m"](x,y); // Another way to write o.m(x,y).
|
|
a[0](z) // Also a method invocation (assuming a[0] is a function).
|
|
```
|
|
|
|
**Invoctaion Context** of invoking by method:
|
|
* (OOP) In a method-invocation expression, the object become invocation contaxt, the function body can refer to the object by keyword `this` (e.g. shown below)
|
|
```js
|
|
let calculator = { // An object literal
|
|
operand1: 1,
|
|
operand2: 1,
|
|
add() { // We're using method shorthand syntax for this function
|
|
// Note the use of the this keyword to refer to the containing object.
|
|
this.result = this.operand1 + this.operand2;
|
|
}
|
|
};
|
|
calculator.add(); // A method invocation to compute 1+1.
|
|
calculator.result // => 2
|
|
```
|
|
* `this` is a keyword, not a variable or property name.
|
|
|
|
#### Nested function & `this` keyword
|
|
|
|
* nested functions do not inherit the this value of the containing function.
|
|
* If a nested function is invoked as a method, its this value is the object it was invoked on.
|
|
* If a nested function (that is not an arrow function) is invoked as a function, then its this value will be either the global object (non-strict mode) or undefined (strict mode).
|
|
* It is a common mistake to assume that a nested function defined within a method and invoked as a function can use this to obtain the invocation context of the method.
|
|
|
|
Solution (workaround) 1:
|
|
```js
|
|
let o = { // An object o.
|
|
m: function() { // Method m of the object.
|
|
let self = this; // Save the "this" value in a variable.
|
|
this === o // => true: "this" is the object o.
|
|
f(); // Now call the helper function f().
|
|
|
|
function f() { // A nested function f
|
|
this === o // => false: "this" is global or undefined
|
|
self === o // => true: self is the outer "this" value.
|
|
}
|
|
}
|
|
};
|
|
o.m(); // Invoke the method m on the object o.
|
|
```
|
|
* Within the method m, we assign the this value to a variable self, and within the nested function f, we can use self instead of this to refer to the containing object.
|
|
|
|
Solution (workaround) 2 since ES6 **arrow function**:
|
|
```js
|
|
const f = () => {
|
|
this === o // true, since arrow functions inherit this
|
|
};
|
|
```
|
|
* Functions defined as expressions instead of statements are not hoisted, so in order to make this code work, the function definition for f will need to be moved within the method m so that it appears before it is invoked.
|
|
|
|
Solution (workaround) 3 using `.bind(this)`:
|
|
```js
|
|
const f = (function() {
|
|
this === o // true, since we bound this function to the outer this
|
|
}).bind(this);
|
|
```
|
|
|
|
### 8.2.3 Constructor Invocation
|
|
|
|
**Constructor invocation** = function/method invocation proceded by keyword `new`.
|
|
* Constructor invocation differ from regular function and method invocations in arg handling, invocation context, and return value.
|
|
* A constructor invocation creates a new, empty object that inherits from the object specified by the prototype property of the constructor.
|
|
* Constructor functions are intended to initialize objects, and this newly created object is used as the invocation context, so the constructor function can refer to it with the this keyword. |