[Red Treasure Book Condensed notes] Chapter 10 functions

Posted by Imad on Wed, 15 Dec 2021 23:43:45 +0100

catalogue

10.1 arrow function

10.2 function name

10.3 understanding parameters

10.4 no heavy load

10.5 default parameter values

10.6 parameter extension and collection

10.7 function declaration and function expression

10.8 function as value

10.9 function internals

10.10 function properties and methods

10.11 function expression

10.12 recursion

10.13 tail call optimization

10.14 closure

10.15 function expression called immediately

10.16 private variables

10.17 summary

10.1 arrow function

ECMAScript 6 adds the ability to define function expressions using fat arrow (= >) syntax. To a large extent, the function objects instantiated by arrow functions behave the same as those created by formal function expressions.

// Both of the following expressions are valid
let double = (x) => { return 2 * x; };
let triple = x => { return 3 * x; };
// No argument requires parentheses
let getRandom = () => { return Math.random(); };
// More than one parameter requires parentheses
let sum = (a, b) => { return a + b; };
// Invalid writing:
let multiply = a, b => { return a * b; }; 
// Both of the following expressions are valid and return the corresponding value
let double = (x) => { return 2 * x; };
let triple = (x) => 3 * x;
// Can be assigned
let value = {};
let setName = (x) => x.name = "Matt";
setName(value);
console.log(value.name); // "Matt"
// Invalid writing:
let multiply = (a, b) => return a * b; 

Although the syntax of arrow function is concise, it is not applicable in many occasions. Arrow functions cannot use arguments, super, and new Target cannot be used as a constructor. In addition, the arrow function has no prototype attribute.

10.2 function name

All Function objects in ECMAScript 6 will expose a read-only name attribute, which contains information about the Function. In most cases, what is stored in this attribute is a Function identifier, or a string variable name. Even if the Function has no name, it will be displayed as an empty string. If it was created using the Function constructor, it is identified as "anonymous":

function foo() {}
let bar = function() {};
let baz = () => {};
console.log(foo.name); // foo
console.log(bar.name); // bar
console.log(baz.name); // baz
console.log((() => {}).name); //(empty string)
console.log((new Function()).name); // anonymous

10.3 understanding parameters

The arguments to the ECMAScript function are different from most other languages. The ECMAScript function does not care about the number of parameters passed in, nor does it care about the data types of these parameters. Receiving two parameters when defining a function does not mean passing two parameters when calling. You can pass one, three, or even none, and the interpreter will not report an error.

This is mainly because the parameters of the ECMAScript function are internally represented as an array. When a function is called, it always receives an array, but the function doesn't care what the array contains. If there is nothing in the array, that's no problem; If the elements of the array exceed the requirements, there is no problem. In fact, when using the function keyword to define a function (not an arrow), you can access the arguments object inside the function to get the value of each parameter passed in

Parameters in arrow function

If the function is defined using arrow syntax, the parameters passed to the function cannot be accessed using the arguments keyword, but only through the defined named parameters.

10.4 no heavy load

ECMAScript functions cannot be overloaded like traditional programming. In other languages, such as Java, a function can have two definitions as long as the signature (type and number of received parameters) is different. As mentioned earlier, ECMAScript function has no signature because the parameters are represented by an array containing zero or more values. Without function signature, there is naturally no overload.

If two functions with the same name are defined in ECMAScript, the latter will override the former

function addSomeNumber(num) {
 return num + 100;
}
function addSomeNumber(num) {
 return num + 200;
}
let result = addSomeNumber(100); // 300

10.5 default parameter values

In ecmascript5 1 and before, a common way to implement the default parameter is to check whether a parameter is equal to undefined. If yes, it means that the parameter is not passed, so assign a value to it.

ECMAScript 6 will not be so troublesome since it supports explicit definition of default parameters. The following is the ES6 writing method equivalent to the previous code. As long as = is used after the parameter in the function definition, a default value can be assigned to the parameter:

function makeKing(name = 'Henry') {
 return `King ${name} VIII`;
}
console.log(makeKing('Louis')); // 'King Louis VIII'
console.log(makeKing()); // 'King Henry VIII'

function makeKing(name = 'Henry', numerals = 'VIII') {
 return `King ${name} ${numerals}`;
}
console.log(makeKing()); // 'King Henry VIII'
console.log(makeKing('Louis')); // 'King Louis VIII'
console.log(makeKing(undefined, 'VI')); // 'King Henry VI' 

Default parameter scope and transient deadband

Defining default values for multiple parameters is actually the same as declaring variables sequentially using the let keyword. The default parameters are initialized in the order in which they are defined

function makeKing(name = 'Henry', numerals = name) {
 return `King ${name} ${numerals}`;
}
console.log(makeKing()); // King Henry Henry 

The parameter initialization sequence follows the "temporary dead zone" rule, that is, the previously defined parameters cannot refer to the later defined parameters. This will throw an error

10.6 parameter extension and collection

Extension operators can be used to pass parameters when calling functions or to define function parameters.

10.6. 1 extension parameters

When passing parameters to a function, sometimes it may not be necessary to pass an array, but to pass in the elements of the array respectively.

Assuming the following function definition, it will add up all the passed in parameters:

let values = [1, 2, 3, 4];
function getSum() {
 let sum = 0;
 for (let i = 0; i < arguments.length; ++i) {
 sum += arguments[i];
 }
 return sum;
} 

In ECMAScript 6, this operation can be implemented very concisely by extending the operator. Apply an extension operator to the iteratable object and pass it in as a parameter. You can split the iteratable object and pass in each value returned by the iteration separately.

For example, using the extension operator, you can directly pass the array in the previous example to the function like this:

console.log(getSum(...values)); // 10

Because the length of the array is known, when using the extended operator to pass parameters, it does not prevent other values from being passed before or after it, including passing other parameters using the extended operator:

console.log(getSum(-1, ...values)); // 9
console.log(getSum(...values, 5)); // 15
console.log(getSum(-1, ...values, 5)); // 14
console.log(getSum(...values, ...[5,6,7])); // 28 

10.6. 2 collection parameters

If there are named parameters before the collection parameters, only the remaining parameters will be collected; If not, you get an empty array. Because the result of collecting parameters is variable, it can only be used as the last parameter:

// may not
function getProduct(...values, lastValue) {}
// sure
function ignoreFirst(firstValue, ...values) {
 console.log(values);
}
ignoreFirst(); // []
ignoreFirst(1); // []
ignoreFirst(1,2); // [2]
ignoreFirst(1,2,3); // [2, 3] 

10.7 function declaration and function expression

In fact, the JavaScript engine treats them differently when loading data. Before any code is executed, the JavaScript engine reads the function declaration and generates the function definition in the execution context. The function expression must wait until the code executes to its line before generating the function definition in the execution context.

When executing the code, the JavaScript engine will first perform a scan to raise the found function declaration to the top of the source code tree. Therefore, even if function definitions appear after the code that calls them, the engine raises the function declaration to the top.

// no problem
console.log(sum(10, 10));
function sum(num1, num2) {
 return num1 + num2;
} 

// Will go wrong
console.log(sum(10, 10));
let sum = function(num1, num2) {
 return num1 + num2;
}; 

The reason why the above code makes an error is that the function definition is contained in a variable initialization statement rather than a function declaration. This means that if the code does not execute to the line "let sum = function(num1, num2)", there is no function definition in the execution context.. This is not caused by the use of let, and the same problem will be encountered when using var keyword.

10.8 function as value

Because the function name is a variable in ECMAScript, the function can be used wherever variables can be used. This means that you can not only pass a function as an argument to another function, but also return another function in one function.

function callSomeFunction(someFunction, someArgument) {
 return someFunction(someArgument);
} 

example:

function add10(num) {
 return num + 10;
}
let result1 = callSomeFunction(add10, 10);
console.log(result1); // 20
function getGreeting(name) {
 return "Hello, " + name;
}
let result2 = callSomeFunction(getGreeting, "Nicholas");
console.log(result2); // "Hello, Nicholas" 

It should be noted that if it is an access function rather than a call function, it must be without parentheses. Therefore, add10 and getGreeting must be passed to callSomeFunction(), not their execution results.

10.9 function internals

In ECMAScript 5, there are two special objects inside the function: arguments and this. ECMAScript 6 adds new Target attribute.

10.9.1 arguments

The arguments object actually has a callee attribute, which is a pointer to the function where the arguments object is located. Let's look at the following classical factorial function:

function factorial(num) {
 if (num <= 1) {
 return 1;
 } else {
 return num * factorial(num - 1);
 }
} 

 // Tight coupling. Use arguments Callee can decouple the function logic from the function name:
function factorial(num) {
 if (num <= 1) {
 return 1;
 } else {
 return num * arguments.callee(num - 1);
 }
} 

// The rewritten factorial() function has been used with arguments Callee replaces the previously hard coded factorial.
// This means that the correct function can be referenced regardless of its name
let trueFactorial = factorial;
factorial = function() {
 return 0;
};
console.log(trueFactorial(5)); // 120
console.log(factorial(5)); // 0

By decoupling the function from the name, trueFactorial() can correctly calculate the factorial, while factorial() can only return 0.  

10.9.2 this

Another special object is this, which has different behavior in standard functions and arrow functions. In standard functions, this refers to the context object that takes functions as method calls. This is usually referred to as this values (when calling functions in the global context of a web page, this points to windows). In arrow functions, this refers to the upper and lower definitions of arrow functions.

In the standard function, this refers to the function as the context object of method call. Which object this refers to can be determined only when the function is called. Therefore, this value may change during code execution:

window.color = 'red';
let o = {
 color: 'blue'
};
function sayColor() {
 console.log(this.color);
}
sayColor(); // 'red'
o.sayColor = sayColor;
o.sayColor(); // 'blue'

In the arrow function, this refers to the context defining the arrow function. In both calls to sayColor(), this refers to the window object, because the arrow function is defined in the window context:

window.color = 'red';
let o = {
 color: 'blue'
};
let sayColor = () => console.log(this.color);
sayColor(); // 'red'
o.sayColor = sayColor;
o.sayColor(); // 'red' 

10.9.3 caller

slightly

10.9.4 new.target

ECMAScript 6 adds new to detect whether the function is called with the new keyword Target attribute. If the function is called normally, new The value of target is undefined; If it is called with the new keyword, new Target will reference the called constructor.

function King() {
 if (!new.target) {
 throw 'King must be instantiated using "new"'
 }
 console.log('King instantiated using "new"');
}
new King(); // King instantiated using "new"
King(); // Error: King must be instantiated using "new" 

10.10 function properties and methods

The functions in ECMAScript are objects, so they have properties and methods. Each function has two properties: length and prototype. The length attribute holds the number of named parameters defined by the function.

The function also has two methods: apply() and call(). Both methods will call the function with the specified this value, that is, the value of this object in the function body will be set when calling the function. The apply() method takes two parameters: the value of this in the function and an Array of parameters. The second parameter can be an instance of Array, but it can also be an arguments object

The call() method has the same function as the apply() method, except that the parameters are passed in different forms. Like apply(), the first parameter is also the value of this, while the remaining parameters to be passed to the called function are passed one by one. In other words, when you pass parameters to a function through call(), you must list the parameters one by one.

function callSum1(num1, num2) {
 return sum.apply(this, arguments); // Pass in arguments object
} 

function callSum2(num1, num2) {
 return sum.apply(this, [num1, num2]); // Pass in array
} 

function callSum(num1, num2) {
 return sum.call(this, num1, num2);
}

The real power of apply() and call() is not to pass parameters to the function, but to control the function call context, that is, the value of this in the function body.

window.color = 'red';
let o = {
 color: 'blue'
};
function sayColor() {
 console.log(this.color);
}
sayColor(); // red
sayColor.call(this); // red
sayColor.call(window); // red
sayColor.call(o); // blue

The advantage of using call() or apply() is that any object can be set to the scope of any function, so that the object can not care about the method. In the original version of the previous example, in order to switch the context, you need to directly assign sayColor() to the property of o, and then call. In this modified version, this step is not required.

10.11 function expression

There are two ways to define functions: function declarations and function expressions.

The key feature of function declaration is function declaration promotion, that is, function declaration will be defined before code execution. This means that a function declaration can appear after the code that calls it. The second way to create a function is a function expression. Like other expressions in JavaScript, function expressions need to be assigned before use.

The key to understanding the difference between function declaration and function expression is to understand promotion.

// Function declaration
sayHi(); // Hi!
function sayHi() {
 console.log("Hi!");
} 

// Function expression
sayHi(); // Error! function doesn't exist yet
let sayHi = function() {
 console.log("Hi!");
};

10.12 recursion

Recursive functions usually take the form of a function calling itself by name. Although this is OK, if you assign this function to other variables, there will be a problem. Solution: use arguments callee. Replace the function name with arguments Callee, you can ensure that no matter what variable you call this function, there will be no problem. Therefore, when writing recursive functions, arguments Callee is the preferred reference to the current function.

10.13 tail call optimization

10.13. 1. Conditions for tail call optimization

// The following shows several functions that violate the above conditions, so they do not require symbol tail call Optimization:
"use strict";
// No optimization: tail call does not return
function outerFunction() {
 innerFunction();
}
// No optimization: the tail call does not return directly
function outerFunction() {
 let innerFunctionResult = innerFunction();
 return innerFunctionResult;
}
// No optimization: after the tail call returns, it must be transformed into a string
function outerFunction() {
 return innerFunction().toString();
}
// No optimization: the tail call is a closure
function outerFunction() {
 let foo = 'bar';
 function innerFunction() { return foo; }
 return innerFunction();
}
// The following are some examples that meet the tail call optimization conditions:
"use strict";
// With optimization: perform parameter calculation before stack frame destruction
function outerFunction(a, b) {
 return innerFunction(a + b);
}
// With optimization: the initial return value does not involve stack frames
function outerFunction(a, b) {
 if (a < b) {
 return a;
 }
 return innerFunction(a + b);
}
// There is optimization: both internal functions are at the tail
function outerFunction(condition) {
 return condition ? innerFunctionA() : innerFunctionB();
} 

10.13. 2 tail call optimized code

// Function for calculating Fibonacci sequence:
function fib(n) {
 if (n < 2) {
 return n;
 }
 return fib(n - 1) + fib(n - 2);
}
console.log(fib(0)); // 0
console.log(fib(1)); // 1
console.log(fib(2)); // 1
console.log(fib(3)); // 2
console.log(fib(4)); // 3
console.log(fib(5)); // 5
console.log(fib(6)); // 8

Obviously, this function does not meet the conditions of tail call optimization, because there is an addition operation in the return statement. As a result, the memory complexity of the number of stack frames of fib(n) is O(2^n). Therefore, even such a simple call can bring trouble to the browser: fib(1000); Of course, there are different strategies to solve this problem, such as rewriting recursion into iterative loop form. However, you can also keep the recursive implementation, but refactor it into a form that meets the optimization conditions.

"use strict";
// Basic framework
function fib(n) {
 return fibImpl(0, 1, n);
}
// Execute recursion
function fibImpl(a, b, n) {
 if (n === 0) {
 return a;
 }
 return fibImpl(b, a + b, n - 1);
} 

10.14 closure

Closures refer to functions that reference variables in the scope of another function, usually implemented in nested functions.

slightly

Note that closures consume more memory than other functions because they preserve the scope of the functions they contain. Overuse of closures can lead to excessive memory consumption, so it is recommended to use them only when necessary. V8 and other optimized JavaScript engines will try to reclaim the memory trapped by closures, but we still recommend caution when using closures.

10.14.1 this object

Using this in closures complicates the code. If the inner function is not defined with an arrow function, the this object is bound to the context in which the function is executed at run time.

window.identity = 'The Window';
let object = {
 identity: 'My Object',
 getIdentityFunc() {
 return function() {
 return this.identity;
 };
 }
};
console.log(object.getIdentityFunc()()); // 'The Window' 

Each function automatically creates two special variables when called: this and arguments. Internal functions can never directly access these two variables of external functions. However, it makes sense to save this to another variable that the closure can access

window.identity = 'The Window';
let object = {
 identity: 'My Object',
 getIdentityFunc() {
 let that = this;
 return function() {
 return that.identity;
 };
 }
};
console.log(object.getIdentityFunc()()); // 'My Object' 

Note that this and arguments cannot be accessed directly in internal functions. If you want to access the arguments object in the scope, you also need to save its reference to another variable that the closure can access.

10.14. 2 memory leak

Since ie used different garbage collection mechanisms for JScript objects and COM objects before IE9 (discussed in Chapter 4), closures may cause problems in these old versions of ie. in these versions of IE, saving HTML elements in the scope of a closure is equivalent to declaring that the element cannot be destroyed.

10.15 function expression called immediately

An anonymous function called immediately is also called IIFE (Immediately Invoked Function Expression). It is similar to a function declaration, but because it is contained in parentheses, it will be interpreted as a function expression. The second set of parentheses immediately after the first set of parentheses will immediately call the previous function expression.

Using IIFE, you can simulate block level scope, that is, declare variables inside a function expression, and then call the function immediately. In this way, the variables in the scope of the function body are like those in the block level scope. ECMAScript 5 does not yet support block level scopes, and it is quite common to use IIFE to simulate block level scopes.

After ECMAScript 6, IIFE is not necessary because variables in block level scope can be isolated without IIFE

10.16 private variables

Strictly speaking, JavaScript has no concept of private members, and all object properties are public. However, there is the concept of private variables. Any variable defined in a function or block can be considered private because it cannot be accessed outside the function or block. Private variables include function parameters, local variables, and other functions defined inside the function.

10.16. 1 static private variable

slightly

10.16. 2 module mode

Template for module mode:

let singleton = function() {
 // Private variables and private functions
 let privateVariable = 10;
 function privateFunction() {
 return false;
 }
 // Privileged / public methods and properties
 return {
 publicProperty: true,
 publicMethod() {
 privateVariable++;
 return privateFunction();
 }
 };
}(); 

10.16. 3 module enhancement mode

10.17 summary

Function is the most useful and general tool in JavaScript programming. ECMAScript 6 adds more powerful syntax features, so that developers can use functions more effectively.

 function expressions are different from function declarations. Function declarations require the function name to be written out, but function expressions do not. A function expression without a name is also called an anonymous function.

 ES6 adds arrow function syntax similar to function expression, but there are also some important differences between the two.

 the parameters of function definition and call in JavaScript are extremely flexible. The arguments object, as well as the new extension operator in ES6, can fully dynamically define and call functions.

 many objects and references are also exposed inside the function, including who calls the function, what calls are used, and what parameters are passed in during the call.

 the JavaScript engine can optimize the functions that meet the tail call conditions to save stack space.

 the scope chain of the closure contains its own variable object, followed by the variable object containing the function until the variable object of the global context.  usually, the function scope and all variables in it will be destroyed after the function is executed.

 after the closure is returned by the function, its scope will be kept in memory until the closure is destroyed.  the function can be called immediately after creation, and the code in it is executed without leaving a reference to the function.

 if the function expression called immediately does not assign the return value to a variable in the inclusion scope, all the variables contained in it will be destroyed.

 although JavaScript does not have the concept of private object attributes, it can use closures to implement public methods to access variables defined in the containing scope.

 public methods that can access private variables are called privileged methods.

 privileged methods can be implemented in custom types using constructors or prototype patterns, or on singleton objects using module patterns or module enhancement patterns.

Topics: Javascript