Chapter 10: function
In fact, functions are also objects. Each Function is an instance of the object Function, which also has properties and methods;
Because the function is also an object, the function name is a pointer to the function object;
10.1 declaration of functions
-
Function declaration
function sum(num1,num2){ return num1+num2; }
-
Function expression
let sum = function(num1,num2){ return num1+num2; }
-
Constructor:
let sum = new Function('num1','num2','return num1+num2');
This method is not recommended because it will be explained twice, which will affect the performance.
-
Arrow function:
let sum = (num1,num2) => return num1+num2;
The arrow function has the following characteristics:
- If there is only one parameter, you can omit the parentheses
- If there is only one line of code, you can omit braces
- arguments``super and new cannot be used target; You cannot use a constructor without a prototype attribute
- this points to the context scope when the arrow function is defined
10.2 function name
Because the function name is a pointer to the function, a function can have multiple names;
function sum(num1,num2){ return num1+num2; } let anotherSum = sum; console.log(sum(10,20)); // 30 console.log(anotherSum(10,20)); // 30
In ES6, all function objects will expose a read-only name attribute;
function foo() {} let bar = function() {}; let baz = () => {}; let nav = new Function(); console.log(foo.name); // foo console.log(bar.name); // bar console.log(baz.name); // baz console.log((() => {}).name); //(empty string) console.log(nav.name); // anonymous
10.3 function parameters
Unlike other languages, ECMAScript function does not care about the number of parameters passed in; No matter less or more, the interpreter will not report an error.
This is mainly because: the parameters are internally represented as an array; Except for the arrow function, you can access the arguments object inside the function to obtain the number of parameters passed.
function sayHi(name,message){ console.log(`Hi,${name}!${message}`); } function anotherSayHi(){ console.log(`Hi,${arguments[0]}!${arguments[1]}`); } sayHi('zyzc','welcome'); // Hi,zyzc!welcome anotherSayHi('zyzc','welcome'); // Hi,zyzc!welcome
Different from other languages, the parameters of ECMASCript function are named for easy calling, not necessary; Because there is no mechanism to validate named parameters
function getLength(){ console.log(arguments.length); } getLength(); // 0 getLength('zyzc',18); // 2
One thing to note here is that the value of the arguments object is always synchronized with the corresponding named parameter.
function sum(num1,num2){ arguments[0] = 10; console.log(num1 + num2); } sum(20,20); // 30
But this does not happen in strict mode. Furthermore: attempting to modify the arguments object in a function will result in a syntax error.
"use strict" function sum(num1,num2){ arguments = {}; // Unexpected eval or arguments in strict mode console.log(num1 + num2); }
10.3.1 arrow function parameters
The parameters passed to the arrow function cannot be accessed using the arguments keyword;
function foo(){ console.log(arguments[0]); } let bat = ()=>{ console.log(arguments[0]); } foo(5); // 5 bat(5); // {}
However, this can be done:
function foo(){ let bar = () => { console.log(arguments[0]); } bar(); } foo(5); // 5
10.4 no overload mechanism
Because the ECMAScript function has no signature, it is not overloaded;
If there is a function with the same name, the later defined will overwrite the earlier defined.
function sum(num1){ return 100 + num1; } function sum(num1){ return 200 + num1; } console.log(sum(100)); // 300
10.5 default parameter values
In ecmascript5 Before 1, you need to manually give the default value;
function showKing(name){ name = (typeof name !== 'undefined') ? name : 'defaultName'; return `King is ${name}!`; } console.log(showKing()); // King is defaultName! console.log(showKing('zyzc')); // King is zyzc!
After ES6, you don't have to be so troublesome
function showKing(name='defaultName'){ return `King is ${name}!`; } console.log(showKing()); // King is defaultName! console.log(showKing('zyzc')); // King is zyzc!
Of course, in addition to the original value, the default value can also be a reference value or function;
In ES6, default values are given to parameters, which can be understood as follows:
function showKing(){// showKing(name='defaultName',message='This is my kingdom!') let name = 'defaultName'; let message = 'This is my kingdom!'; return `King is ${name}:${message}`; } console.log(showKing()); // King is defaultName:This is my kingdom!
Obviously, there will be a temporary dead zone; That is, the parameters defined earlier cannot refer to the parameters defined later.
function showKing(name='default',message=name){ // feasible return `King is ${name}:${message}`; } console.log(showKing()); // King is default:default
It cannot be used like this
function showKing(name=message,message='default'){ // infeasible return `King is ${name}:${message}`; }// Cannot access 'message' before initialization
10.6 parameter expansion and collection
10.6.1 extended parameters
Extended operator Is the syntax of ES6, which is used to retrieve all traversable attributes of parameter objects;
let values = [1,2,3,4,5]; function getSum(){ let sum = 0; for(let i=0 ; i<arguments.length ; i++){ sum += arguments[i]; } return sum; } console.log(getSum(...values)); // 15 // The extension operator can also be used with other parameters console.log(getSum(-1,...values)); // 14 console.log(getSum(...values,-1)); // 14 console.log(getSum(...values,...[6,7,8,9,10])); // 55
After exploring the expansion, what is it like?
let values = [1,2,3,4,5]; function getLength(){ console.log(arguments.length); } getLength(...values); // 5 getLength(-1,...values); // 6 getLength(...values,-1); // 6 getLength(...values,...[6,7,8,9,10]); // 10
According to the above results:
- The extended parameter is getLength(1,2,3,4,5);
- The arguments object does not know the existence of the extension operator, but only consumes the extension operator.
10.6.2 collect remaining parameters
Obviously, we can extract one part of the incoming array (not limited to the array) and continue to put the other part in the array through the extension operator.
function ignoreFirst(firstValue,...values){ // feasible console.log(values); } ignoreFirst(); // [] ignoreFirst(1); // [] ignoreFirst(1,2,3); // [2,3]
Note: it cannot be written like this:
function getLast(...values,lastValue){ // Will report an error }
10.6.3 expansion operator + arrow function
Since the arrow function has no arguments object, it is often impossible to operate on multiple parameters;
However, you can use the extension operator to solve this problem
let args = (...values) => { console.log(values); // This is equivalent to arguments console.log(values.length); } args(1,2,3,4,5);
10.7 function declarations and function expressions
Function declaration formula:
function sum(){ return 0; }
Function expression:
let sum = function(){ return 0; }
10.7.1 notes
What's the difference between the two?
The function declaration has the function variable promotion effect, but the function expression does not!
console.log(sum1()); // 0 function sum1(){ return 0; } console.log(sum2()); // Cannot access 'sum2' before initialization let sum2 = function(){ return 0; }
Is it possible that it is caused by the temporary dead zone of let?
console.log(sum3()); // TypeError: sum3 is not a function var sum3 = function(){ return 0; }
No!
You cannot do this:
if(condition){ function sayHi(){ console.log('hi'); } }else{ function sayHi(){ console.log('Yo') } }
Many browsers will directly ignore the first condition and return the second declaration; Firefox browser will return the first declaration when the first condition is true;
But you can use function expressions to deal with it!
let sayHi; if(condition){ sayHi = function (){ console.log('hi'); } }else{ sayHi = function (){ console.log('Yo') } }
10.8 function as value
Because the function name is a variable in ECMAScript, it is used where variables can be used;
function callSomeFunction(someFunction,someArguments){ return someFunction(someArguments); } function sum(num){ return num+10; } let result = callSomeFunction(sum,10); console.log(result); // 20
10.9 function internals
In ES5, there are two special objects inside the function: arguments and this;
In ES6, new is added Target attribute.
10.9.1 arguments
-
It is a class array object, which contains all the parameters passed in when calling the function;
-
Except for the arrow function, all other functions have arguments
-
The callee attribute is a pointer to the function where the arguments object is located.
// Factorial function function factorial(num){ if(num <= 1){ return 1; }else{ return num * factorial(num-1); // This will increase the coupling between function logic and function name } } // Override with callee property function factorial(num){ if(num <= 1){ return 1; }else{ return num * arguments.callee(num-1); // This decouples the function logic from the function name } } console.log(factorial(5)); // 120
In strict mode: cannot access arguments callee
10.9.2 this
-
In standard functions, this points to the context object that treats the function as a method call.
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 points to the context that defines the arrow function.
color = 'red'; let o = { color:'blue' } sayColor = ()=>{ console.log(this.color); } sayColor(); // red o.sayColor = sayColor; o.sayColor(); // red
10.9.3 caller and recursion
This attribute refers to the function that calls the current function. If it is called in global scope, it is null.
function outer(){ inner(); } function inner(){ console.log(arguments.callee.caller); } outer(); // outer() inner(); // null
Because in strict mode:
- Cannot assign a value to the caller attribute of a function;
- Cannot access arguments callee
Therefore: you can use named function expressions to achieve the purpose;
// Factorial function const factorial = (function f(num){ if(num <= 1){ return 1; }else{ return num * f(num -1); // This will increase the coupling between function logic and function name } }); console.log(factorial(5)); // 120
10.9.4 new.target
This attribute is used to check whether the function is constructed with the new keyword;
function Plugin(){ if(new.target){ console.log("The plugin had been installed"); }else{ throw "Plugin must be install using new"; } }
10.10 function properties and methods
10.10.1 properties of function
- length: saves the number of named parameters defined by the function
- Prototype: save the prototype of the reference type, which contains the shared methods of all instances of the reference type
10.10.2 method of function
In addition to toString(), valueOf(), there are two more important methods:
-
apply(): receive two parameters: one is the direction of this inside the function; The other is the parameter array.
-
call(): receive N parameters: one is the direction of this inside the function; The rest is parameter by parameter.
function sum(num1,num2){ console.log(num1 + num2); } function callSum(num1,num2){ sum.call(this,num1,num2) } function applySum(num1,num2){ sum.apply(this,arguments) } callSum(10,20); // 30 applySum(10,20); // 30
In strict mode, if this is not explicitly pointed to, it will not automatically point to window, but will point to undefined.
-
ES5 also provides a function pointed to by the binding function this:
-
bind(): bind this point to the function and create a new function instance.
color = 'red'; let o = { color:'blue' } function sayColor(){ console.log(this.color); } let anotherSayColor = sayColor.bind(o); sayColor(); // red anotherSayColor(); // blue;
-
10.11 tail tuning, optimization and recursion
A memory management optimization mechanism is added in ES6; Let the JS engine reuse stack frames when the conditions are met
This optimization uses tail calls very much, that is, the return value of the external function is the return value of the internal function.
10.11.1 for example
function outerFunction(){ return innerFunction(); // Tail call }
Before ES6 optimization, the following operations will occur in memory when this example is executed.
(1) When executed to the outerFunction function body, the first stack frame is pushed onto the stack.
(2) Execute the outerFunction function body to the return statement. innerFunction must be calculated before calculating the return value.
(3) Execute to the innerFunction function body, and the second stack frame is pushed to the stack.
(4) Execute the innerFunction function body and calculate its return value.
(5) Return value is returned to outerFunction, and then outerFunction returns value.
(6) Pop the stack frame out of the stack.
After ES6 optimization, the following operations will occur in memory after executing this example.
(1) When executed to the outerFunction function body, the first stack frame is pushed onto the stack.
(2) Execute the outerFunction function body to reach the return statement. In order to evaluate the return statement, innerFunction must be evaluated first.
(3) The engine finds that it is OK to pop the first stack frame out of the stack, because the return value of innerFunction is also the return value of outerFunction.
(4) Pop up the stack frame of outerFunction.
(5) Execute to the innerFunction function body, and the stack frame is pushed to the stack.
(6) Execute the innerFunction function body and calculate its return value.
(7) Pop the stack frame of innerFunction out of the stack.
Obviously, one more stack frame will be called before optimization
10.11.2 conditions for tail tuning optimization
- The code executes in strict mode
- After the tail call function returns, there is no need to execute other logic
- The tail calling function is not a closure that references a free variable in the scope of an external function
for instance:
"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(); } // 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.11.3 tail tuning optimization of Fibonacci sequence recursion
Before optimization:
function fb(n){ if( n<2 ){ return n; } return fb(n-1) + fb(n-2); // Two additional stack frames are called each time, and the memory complexity reaches the nth power of 2 }
After optimization:
"use strict" function fb(n){ return fbPlus(0,1,n); } function fbPlus(a,b,n){ if( n==0 ){ return a; } return fbPlus(b , a+b , n-1); }
10.12 closure
10.12.1 what is closure?
Closure: a function that references a variable in the scope of another function;
for instance:
function create(name){ return function(object){ let objName = object[name]; // An internal anonymous function references a variable of an external function return objName; } }
We talked about scope chain in Chapter 4; Here again emphasize the scope chain of the function:
When a function is executed, there will be an object containing variables in each execution context.
In the global context, it is called variable object, which will always exist during code execution.
During the execution of the function, it is called the local function.
When defining a function, the corresponding execution context will be created, and then its Scope chain will be created by copying [[Scope]] of the function. Finally, create the active object of the function and push it to the front of the Scope chain.
After the function is executed, the local active object is destroyed.
In closure: that is, the function (anonymous function) defined inside the function (create()) will add the active object containing the function (create()) to its scope chain (anonymous function).
When the create() function is executed, its execution context will be destroyed; But because anonymous functions still reference their active objects. Therefore, the active object of the create () function remains in memory; The anonymous function is not destroyed until it is destroyed.
Therefore, you need to think carefully when choosing to use closures.
10.12.2 this object
In closures, using this makes the code more complex. If the inner function is not defined with an arrow function, the this object will be bound to the context in which the function is executed.
Example 1:
color = 'red'; let object = { color:'blue', sayColor(){ return function(){ console.log(this); console.log(this.color); } } } object.sayColor()(); // red
The function is executed by object, which is in the global context, so this is bound to the global context at this time.
How to make the internal function (anonymous function) access this of the external function (sayColor)?
color = 'red'; let object = { color:'blue', sayColor(){ let that = this; return function(){ console.log(that); // { color: 'blue', sayColor: [Function: sayColor] } console.log(that.color); } } } object.sayColor()(); // blue
In addition to this, arguments cannot be accessed by internal functions.
10.12.3 memory leakage
Because of the closure, the reference count of the active object of the external function is never 0, resulting in memory leakage.
10.13 IIFE
The anonymity of immediate call is also called Iife (immediately invoked function expression)
In ES5, there is no block level scope; You can use IIFE to simulate block level scopes
(function(){ for(var i=0 ; i<5 ; i++){ console.log(i); // 0 1 2 3 4 } })(); console.log(i); // i is not defined
This can prevent memory problems caused by closures, because there is no reference to this anonymous function;
In ES6, it can be defined like this
{ let i; for(i=0 ; i<5 ; i++){ console.log(i); // 0 1 2 3 4 } } for(let i=0 ; i<5 ; i++){ console.log(i); // 0 1 2 3 4 } console.log(i); // i is not defined
Block level scopes are useful for adding click events to a div and displaying its index for each click
for(let i=0 ; i<divs.length ; i++){ divs[i].addEventListener('click',function(){ console.log(i); }) }
10.14 private variables
Strictly speaking, there is no private variable in JavaScript, but the concept of private variable; Variables can be privatized in various ways.
10.14.1 implementation of private variables by privileged methods
If a closure is created in a function, the closure can access variables and methods in the function through its scope chain;
Privileged methods are public methods that can access private variables and methods of functions:
function Person(name){ let _name = name; // private variable // privileged method this.getName = function(){ // public Method return _name; } this.setName = function(value){ // public Method _name = value } }
Obviously, this is the constructor pattern. Its disadvantage is that each instance will recreate a new method; Using static private variables to implement privileged methods can avoid this problem
10.14.2 static private variables
- Form private variables through closure method;
- Global variables are formed by declaring objects without keywords;
- By defining the method for the prototype of the above global variables, all instances of the privileged method can be shared;
(function(){ // private variable let _name = 'defaultName'; // Constructor Person = function(name){ _name = name; }; // Public and privileged methods Person.prototype.getName = function(){ return _name; } })(); let person = new Person('zyzc'); console.log(person.getName()); // zyzc console.log(_name); // _name is not defined
**Note: * * using closures and private variables will make the scope chain side long, resulting in the time required for variable search.
10.14.3 module mode
Module mode: implements the same isolation and encapsulation on a singleton object.
let BaseComponent = function(){}; // Component 1 let application = function(){ let components = new Array(); // For storage components components.push(new BaseComponent()); // initialization // Common interface return{ getComponent(){ return components.length; }, registerComponent(component){ if(typeof component == 'object'){ components.push(component); } } } }(); console.log(application.getComponent()); // 1 application.registerComponent(new BaseComponent()); console.log(application.getComponent()); // 2
10.14.4 enhanced module mode
Combined with the module mode, upgrade and arm the object before returning it
let Person = function(){ let name; function sayHi(){ console.log('hi'); } return { name, sayHi } }; let Solider = function(){ // Private property let weapons = ['knife','tank','guns']; // Private method let shot = function(){ console.log('I am shooting'); } let person = new Person(); // Create an ordinary person person.shot = shot; // Enhancement method person.weapons = weapons; // Enhanced properties return person; } let soliderA = new Solider(); soliderA.sayHi(); // Existing personality before enhancement soliderA.shot(); // It also has enhanced features console.log(soliderA.weapons);
Summary
- The function has the following expressions
- Function expression
- Function declaration
- Anonymous function
- Arrow function
- The parameters of the function are very flexible
- Many objects and attributes are exposed inside the function: this, arguments, new target,callee,caller
- You can get a lot of information through the objects and attributes exposed inside the function
- The JavaScript engine can optimize the functions that meet the tail tone conditions, which can save stack space.
- Use closures with caution
- Although there is no concept of private variables in JavaScript, private variables can be implemented through some patterns.
- Private variables and methods can be accessed through privileged methods;
- Privileged methods can be implemented through custom types using constructors or prototype patterns; It can also be implemented on singleton objects through module mode or enhanced module mode.