JavaScript advanced programming - Chapter 10: Functions

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

  1. Function declaration

    function sum(num1,num2){
        return num1+num2;
    }
    
  2. Function expression

    let sum = function(num1,num2){
        return num1+num2;
    }
    
  3. 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.

  4. 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

  1. length: saves the number of named parameters defined by the function
  2. 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:

  1. apply(): receive two parameters: one is the direction of this inside the function; The other is the parameter array.

  2. 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.

  3. 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.

Keywords: Javascript Front-end

Added by radstorm on Wed, 23 Feb 2022 17:59:55 +0200