Function

A function is a block of code that can be called repeatedly. Functions can also accept input parameters, and different parameters will return different values.

Overview

Function declaration

JavaScript has three ways of declaring functions.

(1) function command

The code block declared by the function command is a function. The function command is followed by the function name, and the function name is followed by a pair of parentheses, which are the parameters of the passed-in function. The function body is placed inside curly braces.

function print(s) {
  console.log(s);
}

The above code names a print function, and then use the form of print() to call the corresponding code. This is called a function declaration (Function Declaration).

(2) Function expression

In addition to using the function command to declare functions, you can also use variable assignment.

var print = function (s) {
  console.log(s);
};

This way of writing assigns an anonymous function to a variable. At this time, this anonymous function is also called a function expression (Function Expression), because the right side of the equal sign of the assignment statement can only put expressions.

When using a function expression to declare a function, there is no function name after the function command. If a function name is added, the function name is only valid inside the function body, and invalid outside the function body.

var print = function x() {
  console.log(typeof x);
};

x;
// ReferenceError: x is not defined

print();
// function

The above code adds the function name x to the function expression. This x is only available inside the function body, it refers to the function expression itself, and is not available anywhere else. There are two uses for this way of writing, one is to call itself inside the function body, and the other is to facilitate debugging (when the debugging tool displays the function call stack, it will display the function name instead of showing that it is an anonymous function). Therefore, the following form of declaration function is also very common.

var f = function f() {};

It should be noted that the expression of the function needs to add a semicolon at the end of the statement to indicate the end of the statement. The function declaration does not need to add a semicolon after the closing brace. In general, the two ways of declaring functions are slightly different and can be roughly regarded as equivalent.

(3) Function constructor

The third way to declare a function is the Function constructor.

var add = new Function("x", "y", "return x + y");

// Equivalent to
function add(x, y) {
  return x + y;
}

In the above code, the Function constructor accepts three parameters. Except for the last parameter which is the "function body" of the add function, the other parameters are the parameters of the add function.

You can pass any number of parameters to the Function constructor. Only the last parameter will be treated as the function body. If there is only one parameter, the parameter is the function body.

var foo = new Function('return "hello world";');

// Equivalent to
function foo() {
  return "hello world";
}

The Function constructor does not need to use the new command, and the result is exactly the same.

In general, this way of declaring functions is very unintuitive and hardly anyone uses it.

Duplicate declaration of function

If the same function is declared multiple times, the subsequent declaration will overwrite the previous one.

function f() {
  console.log(1);
}
f(); // 2

function f() {
  console.log(2);
}
f(); // 2

In the above code, the last function declaration overwrites the previous one. Moreover, due to the promotion of the function name (see below), the previous declaration is invalid at any time, so special attention should be paid to this point.

Parenthesis operator, return statement and recursion

When calling a function, use the parenthesis operator. In the parentheses, you can add function parameters.

function add(x, y) {
  return x + y;
}

add(1, 1); // 2

In the above code, the function name is followed by a pair of parentheses, and the function is called.

The return statement inside the function body means return. When the JavaScript engine encounters a return statement, it directly returns the value of the expression following the return, and even if there are statements after it, it will not be executed. In other words, the expression in the return statement is the return value of the function. The return statement is not necessary. If it does not, the function does not return any value, or returns undefined.

A function can call itself, which is recursion. Below is the code to calculate the Fibonacci sequence through recursion.

function fib(num) {
  if (num === 0) return 0;
  if (num === 1) return 1;
  return fib(num - 2) + fib(num - 1);
}

fib(6); // 8

In the above code, fib is called inside the fib function, and the sixth element of the Fibonacci sequence is calculated to be 8.

First-class citizen

The JavaScript language treats a function as a value, which has the same status as other values ​​(numeric, string, Boolean, etc.). Wherever values ​​can be used, functions can be used. For example, functions can be assigned to variables and object properties, and can also be passed into other functions as parameters, or returned as the result of the function. A function is just a value that can be executed, and there is nothing special about it.

Since functions are equal to other data types, they are also called first-class citizens in the JavaScript language.

function add(x, y) {
  return x + y;
}

// Assign the function to a variable
var operator = add;

// Take function as parameter and return value
function a(op) {
  return op;
}
a(add)(1, 1);
// 2

Function name promotion

The JavaScript engine treats function names as variable names, so when you use the function command to declare a function, the entire function will be promoted to the head of the code just like a variable declaration. Therefore, the following code will not report an error.

f();

function f() {}

On the surface, the above code seems to call the function f before the declaration. But in fact, due to "variable promotion", the function f is promoted to the head of the code, that is, it has been declared before the call. However, if you use an assignment statement to define a function, JavaScript will report an error.

f();
var f = function () {};
// TypeError: undefined is not a function

The above code is equivalent to the following form.

var f;
f();
f = function () {};

In the second line of the above code, when f is called, f is only declared and has not been assigned yet, which is equal to undefined, so an error will be reported.

Note that if you use the function command and the var assignment statement to declare the same function as in the following example, due to the function promotion, the definition of the var assignment statement will be used in the end.

var f = function () {
  console.log("1");
};

function f() {
  console.log("2");
}

f(); // 1

In the above example, the function f declared later on the surface should override the previous var assignment statement, but due to the function promotion, it is actually the other way round.

Function attributes and methods

name attribute

The name property of a function returns the name of the function.

function f1() {}
f1.name; // "f1"

If it is a function defined by variable assignment, the name attribute returns the variable name.

var f2 = function () {};
f2.name; // "f2"

However, the above situation is only true when the value of the variable is an anonymous function. If the value of the variable is a named function, then the name attribute returns the name of the function after the function keyword.

var f3 = function myName() {};
f3.name; //'myName'

In the above code, f3.name returns the name of the function expression. Note that the real function name is still f3, and the name myName is only available inside the function body.

One use of the name attribute is to get the name of the parameter function.

var myFunc = function () {};

function test(f) {
  console.log(f.name);
}

test(myFunc); // myFunc

In the above code, the function test uses the name attribute inside to know what function the passed parameter is.

length property

The length property of the function returns the number of parameters expected by the function, that is, the number of parameters in the function definition.

function f(a, b) {}
f.length; // 2

The above code defines an empty function f, and its length property is the number of parameters at the time of definition. No matter how many parameters are entered in the call, the length property is always equal to 2.

The length attribute provides a mechanism to determine the difference between the parameters at the time of definition and at the time of invocation, so as to realize the "method overload" of object-oriented programming.

toString()

The toString() method of the function returns a string with the source code of the function.

function f() {
  a();
  b();
  c();
}

f.toString();
// function f() {
// a();
// b();
// c();
//}

In the above example, the toString() method of the function f returns the source code of f, including the newline character.

For those native functions, the toString() method returns function (){[native code]}.

Math.sqrt.toString();
// "function sqrt() {[native code] }"

In the above code, Math.sqrt() is the native function provided by the JavaScript engine, and the toString() method returns the hint of the native code.

The comments inside the function can also be returned.

function f() {
  /*
  This is an
  Multi-line comments
*/
}

f.toString();
// "function f(){/*
// This is an
// Multi-line comment
// */}"

Using this, multi-line character strings can be realized in disguise.

var multiline = function (fn) {
  var arr = fn.toString().split("\n");
  return arr.slice(1, arr.length - 1).join("\n");
};

function f() {
  /*
  This is an
  Multi-line comments
*/
}

multiline(f);
// " This is an
// Multi-line comment"

In the above example, there is a multi-line comment inside the function f. After the toString() method gets the source code of f, remove the first and last two lines to get a multi-line string.

Function scope

Definition

The scope (scope) refers to the scope of the variable. In the ES5 specification, JavaScript has only two scopes: one is global scope, where variables always exist in the entire program and can be read everywhere; the other is function scope, where variables only exist inside the function. ES6 has added a new block-level scope, which is not covered in this tutorial.

For top-level functions, the variables declared outside the function are global variables, which can be read inside the function.

var v = 1;

function f() {
  console.log(v);
}

f();
// 1

The above code shows that the global variable v can be read inside the function f.

Variables defined inside a function cannot be read from outside, and are called "local variables".

function f() {
  var v = 1;
}

v; // ReferenceError: v is not defined

In the above code, the variable v is defined inside the function, so it is a local variable and cannot be read outside the function.

Variables defined inside the function will overwrite global variables with the same name in the scope.

var v = 1;

function f() {
  var v = 2;
  console.log(v);
}

f(); // 2
v; // 1

In the above code, the variable v is defined both outside and inside the function. As a result, defined inside the function, the local variable v overrides the global variable v.

Note that for the var command, local variables can only be declared inside the function, and declared in other blocks are all global variables.

if (true) {
  var x = 5;
}
console.log(x); // 5

In the above code, the variable x is declared in the conditional judgment block, and the result is a global variable that can be read outside the block.

Variable promotion inside functions

Like the global scope, the phenomenon of "variable promotion" occurs inside the function scope. The variable declared by the var command, no matter where it is, the variable declaration will be promoted to the head of the function body.

function foo(x) {
  if (x > 100) {
    var tmp = x - 100;
  }
}

// Equivalent to
function foo(x) {
  var tmp;
  if (x > 100) {
    tmp = x - 100;
  }
}

The scope of the function itself

The function itself is also a value and also has its own scope. Its scope is the same as that of a variable, it is the scope in which it is declared, and has nothing to do with the scope in which it is running.

var a = 1;
var x = function () {
  console.log(a);
};

function f() {
  var a = 2;
  x();
}

f(); // 1

In the above code, the function x is declared outside the function f, so its scope is bound to the outer layer, and the internal variable a will not get a value in the function f body, so the output is 1 Instead of 2.

In short, the scope of the function execution is the scope of the definition, not the scope of the call.

It is easy to make a mistake if function A calls function B without considering that function B will not reference the internal variables of function A.

var x = function () {
  console.log(a);
};

function y(f) {
  var a = 2;
  f();
}

y(x);
// ReferenceError: a is not defined

The above code takes the function x as a parameter and passes it to the function y. However, the function x is declared outside of the function y, and the scope is bound to the outer layer, so the internal variable a of the function y cannot be found, resulting in an error.

Similarly, for functions declared inside the function body, the scope is bound inside the function body.

function foo() {
  var x = 1;
  function bar() {
    console.log(x);
  }
  return bar;
}

var x = 2;
var f = foo();
f(); // 1

In the above code, the function foo declares a function bar inside, and the scope of bar is bound to foo. When we take out bar and execute it outside foo, the variable x points to x inside foo instead of x outside foo. It is this mechanism that constitutes the "closure" phenomenon to be explained below.

Parameters

Overview

When a function is running, it is sometimes necessary to provide external data. Different external data will get different results. This kind of external data is called a parameter.

function square(x) {
  return x * x;
}

square(2); // 4
square(3); // 9

The x in the above formula is the parameter of the square function. You need to provide this value each time you run it, otherwise you won't get the result.

Omission of parameters

Function parameters are not required, JavaScript allows parameters to be omitted.

function f(a, b) {
  return a;
}

f(1, 2, 3); // 1
f(1); // 1
f(); // undefined

f.length; // 2

The function f of the above code defines two parameters, but no matter how many parameters are provided (or no parameters are provided) at runtime, JavaScript will not report an error. The value of the omitted parameter becomes undefined. It should be noted that the length property of the function has nothing to do with the actual number of parameters passed in, and only reflects the number of parameters expected to be passed in by the function.

However, there is no way to omit only the first parameters and keep the latter parameters. If you must omit the first parameter, only explicitly pass in undefined.

function f(a, b) {
  return a;
}

f(, 1) // SyntaxError: Unexpected token ,(…)
f(undefined, 1) // undefined

In the above code, if the first parameter is omitted, an error will be reported.

Delivery method

If the function parameters are primitive values ​​(numeric, string, boolean), the transfer method is passes by value. This means that modifying parameter values ​​in the function body will not affect the outside of the function.

var p = 2;

function f(p) {
  p = 3;
}
f(p);

p; // 2

In the above code, the variable p is a primitive value, and the way to pass it to the function f is to pass by value. Therefore, inside the function, the value of p is a copy of the original value, no matter how it is modified, it will not affect the original value.

However, if the function parameter is a value of a composite type (array, object, other function), the transfer method is pass by reference. In other words, the address of the original value of the incoming function, so modifying the parameters inside the function will affect the original value.

var obj = { p: 1 };

function f(o) {
  op = 2;
}
f(obj);

obj.p; // 2

In the above code, what is passed into the function f is the address of the parameter object obj. Therefore, modifying the attribute p of obj inside the function will affect the original value.

Note that if the internal modification of the function is not an attribute of the parameter object, but the entire parameter is replaced, the original value will not be affected at this time.

var obj = [1, 2, 3];

function f(o) {
  o = [2, 3, 4];
}
f(obj);

obj; // [1, 2, 3]

In the above code, inside the function f(), the parameter object obj is completely replaced with another value. At this time, the original value will not be affected. This is because the value of the formal parameter (o) is actually the address of the parameter obj. Re-assigning o will cause o to point to another address. Of course, the value stored at the original address will not be affected.

Parameters with the same name

If there is a parameter with the same name, the value that appears last is used.

function f(a, a) {
  console.log(a);
}

f(1, 2); // 2

In the above code, the function f() has two parameters, and the parameter names are both a. When selecting a value, the following a shall prevail, even if the following a has no value or is omitted, it shall prevail.

function f(a, a) {
  console.log(a);
}

f(1); // undefined

When calling the function f(), without providing the second parameter, the value of a becomes undefined. At this time, if you want to get the value of the first a, you can use the arguments object.

function f(a, a) {
  console.log(arguments[0]);
}

f(1); // 1

arguments object

(1) Definition

Since JavaScript allows a function to have an indefinite number of parameters, it needs a mechanism to read all the parameters inside the function body. This is the origin of the arguments object.

The arguments object contains all the parameters of the function at runtime, arguments[0] is the first parameter, arguments[1] is the second parameter, and so on. This object can only be used inside the function body.

var f = function (one) {
  console.log(arguments[0]);
  console.log(arguments[1]);
  console.log(arguments[2]);
};

f(1, 2, 3);
// 1
// 2
// 3

In normal mode, the arguments object can be modified at runtime.

var f = function (a, b) {
  arguments[0] = 3;
  arguments[1] = 2;
  return a + b;
};

f(1, 1); // 5

In the above code, the parameters passed in when the function f() is called are modified to 3 and 2 inside the function.

In strict mode, the arguments object does not have a linkage relationship with function parameters. In other words, modifying the arguments object will not affect the actual function parameters.

var f = function (a, b) {
  "use strict"; // enable strict mode
  arguments[0] = 3;
  arguments[1] = 2;
  return a + b;
};

f(1, 1); // 2

In the above code, the function body is in strict mode. Modifying the arguments object at this time will not affect the real parameters a and b.

Through the length property of the arguments object, you can determine how many parameters the function call takes.

function f() {
  return arguments.length;
}

f(1, 2, 3); // 3
f(1); // 1
f(); // 0

(2) Relationship with array

It should be noted that although arguments is very similar to an array, it is an object. Array-specific methods (such as slice and forEach) cannot be used directly on the arguments object.

If you want the arguments object to use the array method, the real solution is to turn the arguments into a real array. The following are two commonly used conversion methods: the slice method and filling in a new array one by one.

var args = Array.prototype.slice.call(arguments);

// or
var args = [];
for (var i = 0; i < arguments.length; i++) {
  args.push(arguments[i]);
}

(3) callee attribute

The arguments object has a callee attribute, which returns the original function it corresponds to.

var f = function () {
  console.log(arguments.callee === f);
};

f(); // true

The purpose of calling the function itself can be achieved through arguments.callee. This attribute is disabled in strict mode, so it is not recommended.

Other knowledge points of function

Closure

Closure is a difficult point and characteristic of the JavaScript language. Many advanced applications rely on closures.

To understand closures, we must first understand the scope of variables. As mentioned earlier, JavaScript has two scopes: global scope and function scope. Global variables can be read directly inside the function.

var n = 999;

function f1() {
  console.log(n);
}
f1(); // 999

In the above code, the function f1 can read the global variable n.

However, under normal circumstances, the variables declared inside the function cannot be read outside the function.

function f1() {
  var n = 999;
}

console.log(n);
// Uncaught ReferenceError: n is not defined(

In the above code, the variable n declared inside the function f1 cannot be read outside the function.

If for various reasons, you need to get the local variables in the function. Under normal circumstances, this is impossible and can only be achieved through workarounds. That is to define another function inside the function.

function f1() {
  var n = 999;
  function f2() {
    console.log(n); // 999
  }
}

In the above code, the function f2 is inside the function f1. At this time, all the local variables inside f1 are visible to f2. But the reverse is not possible. The local variables inside f2 are invisible to f1. This is the unique "chain scope" structure of the JavaScript language. The child object will look up all the variables of the parent object level by level. Therefore, all variables of the parent object are visible to the child object, and vice versa.

Since f2 can read the local variables of f1, as long as f2 is used as the return value, can we not read its internal variables outside f1!

function f1() {
  var n = 999;
  function f2() {
    console.log(n);
  }
  return f2;
}

var result = f1();
result(); // 999

In the above code, the return value of the function f1 is the function f2. Since f2 can read the internal variables of f1, the internal variables of f1 can be obtained externally.

The closure is the function f2, which is a function that can read the internal variables of other functions. Since in the JavaScript language, only the sub-functions inside the function can read the internal variables, the closure can be simply understood as "a function defined inside a function". The biggest feature of a closure is that it can "remember" the environment in which it was born. For example, f2 remembers the environment in which it was born, f1, so the internal variables of f1 can be obtained from f2. In essence, a closure is a bridge that connects the inside of the function with the outside of the function.

There are two biggest uses of closures. One is to read the variables inside the outer function, and the other is to keep these variables in memory at all times, that is, the closure can make the environment in which it was born always exist. Please look at the following example. The closure makes the internal variables remember the result of the operation at the last call.

function createIncrementor(start) {
  return function () {
    return start++;
  };
}

var inc = createIncrementor(5);

inc(); // 5
inc(); // 6
inc(); // 7

In the above code, start is an internal variable of the function createIncrementor. Through the closure, the state of start is retained, and each call is calculated on the basis of the previous call. It can be seen that the closure inc makes the internal environment of the function createIncrementor always exist. Therefore, the closure can be seen as an interface to the internal scope of the function.

Why can the closure return the internal variables of the outer function? The reason is that the closure (inc in the above example) uses the outer variable (start), which causes the outer function (createIncrementor) to not be released from memory. As long as the closure is not cleared by the garbage collection mechanism, the operating environment provided by the outer function will not be cleared, and its internal variables will always save the current value for the closure to read.

Another use of closures is to encapsulate private properties and private methods of objects.

function Person(name) {
  var _age;
  function setAge(n) {
    _age = n;
  }
  function getAge() {
    return _age;
  }

  return {
    name: name,
    getAge: getAge,
    setAge: setAge,
  };
}

var p1 = Person("Zhang San");
p1.setAge(25);
p1.getAge(); // 25

In the above code, the internal variable _age of the function Person, through the closures getAge and setAge, becomes a private variable that returns the object p1.

Note that each time the outer function runs, a new closure will be generated, and this closure will retain the internal variables of the outer function, so the memory consumption is very large. Therefore, closures cannot be abused, otherwise it will cause performance problems on the web page.

Immediately called function expression (IIFE)

According to JavaScript syntax, parentheses () follow the function name to indicate that the function is called. For example, print() means to call the print function.

Sometimes, we need to call the function immediately after defining the function. At this time, you cannot add parentheses after the function definition, which will cause a syntax error.

function(){ /* code */ }();
// SyntaxError: Unexpected token (

The reason for this error is that the keyword function can be used as both a statement and an expression.

// statement
function f() {}

// expression
var f = function f() {};

When used as an expression, the function can be called directly with parentheses after the definition.

var f = (function f() {
  return 1;
})();
f; // 1

In the above code, the function is called directly with parentheses after the function definition, and no error is reported. The reason is that function is used as an expression, and the engine treats the function definition as a value. In this case, no error will be reported.

In order to avoid ambiguity in parsing, JavaScript stipulates that if the function keyword appears at the beginning of a line, it shall be interpreted as a sentence. Therefore, after the engine sees the function keyword at the beginning of the line, it thinks that this paragraph is the definition of a function and should not end with parentheses, so it reports an error.

The solution to call immediately after the function is defined is to not let the function appear at the beginning of the line, and let the engine interpret it as an expression. The easiest way to deal with it is to put it inside a parenthesis.

(function () {
  /* code */
})();
// or
(function () {
  /* code */
})();

The above two writing methods start with parentheses, and the engine will think that what follows is an expression, not a function definition statement, so errors are avoided. This is called "Immediately-Invoked Function Expression", or IIFE for short.

Note that the semicolon at the end of the above two writing methods is required. If you omit the semicolon and encounter two consecutive IIFEs, you may get an error.

// report an error
(function () {
  /* code */
})()(
  (function () {
    /* code */
  })()
);

There is no semicolon between the two lines of the above code, JavaScript will interpret them together, and interpret the second line as the first line of parameters.

By extension, any method that allows the interpreter to process function definitions as expressions can produce the same effect, such as the following three ways of writing.

var i = (function () {
  return 10;
})();
true &&
  (function () {
    /* code */
  })();
0,
  (function () {
    /* code */
  })();

It is even possible to write like the following.

!(function () {
  /* code */
})();
~(function () {
  /* code */
})();
-(function () {
  /* code */
})();
+(function () {
  /* code */
})();

Under normal circumstances, only use this "immediately executed function expression" for anonymous functions. It has two purposes: one is that it is not necessary to name the function to avoid polluting global variables; the other is that a separate scope is formed inside the IIFE, which can encapsulate some private variables that cannot be read from the outside.

// Writing method one
var tmp = newData;
processData(tmp);
storeData(tmp);

// Writing method two
(function () {
  var tmp = newData;
  processData(tmp);
  storeData(tmp);
})();

In the above code, writing method two is better than writing method one, because it completely avoids polluting global variables.

eval command

Basic usage

The eval command accepts a string as a parameter and executes this string as a statement.

eval("var a = 1;");
a; // 1

The above code runs the string as a statement and generates the variable a.

If the parameter string cannot be run as a statement, an error will be reported.

eval("3x"); // Uncaught SyntaxError: Invalid or unexpected token

The string placed in eval should have its own meaning and cannot be used in conjunction with commands other than eval. For example, the following code will report an error.

eval("return;"); // Uncaught SyntaxError: Illegal return statement

The above code will report an error, because return cannot be used alone, it must be used in a function.

If the parameter of eval is not a string, it will return as it is.

eval(123); // 123

eval does not have its own scope and is executed in the current scope, so it may modify the value of the variable in the current scope, causing security problems.

var a = 1;
eval("a = 2");

a; // 2

In the above code, the eval command modifies the value of the external variable a. For this reason, eval is a security risk.

To prevent this risk, JavaScript stipulates that if strict mode is used, variables declared inside eval will not affect the outer scope.

(function f() {
  "use strict";
  eval("var foo = 123");
  console.log(foo); // ReferenceError: foo is not defined
})();

In the above code, the function f is in strict mode inside. At this time, the foo variable declared inside eval will not affect the outside.

However, even in strict mode, eval can still read and write variables in the current scope.

(function f() {
  "use strict";
  var foo = 1;
  eval("foo = 2");
  console.log(foo); // 2
})();

In the above code, in strict mode, external variables are rewritten inside eval, which shows that security risks still exist.

In short, the essence of eval is to inject code into the current scope. Due to security risks and not conducive to the optimization of the execution speed of the JavaScript engine, it is generally not recommended. Under normal circumstances, the most common occasion for eval is to parse the string of JSON data, but the correct way is to use the native JSON.parse method.

Alias ​​call of eval

As mentioned earlier, eval is not conducive to engine optimization execution speed. What's more troublesome is that there is also the following situation, the engine can not distinguish the execution of eval at the stage of static code analysis.

var m = eval;
m("var x = 1");
x; // 1

In the above code, the variable m is an alias of eval. In the static code analysis phase, the engine cannot tell that m('var x = 1') executes the eval command.

In order to ensure that the alias of eval does not affect code optimization, the JavaScript standard stipulates that if an alias is used to execute eval, the inside of eval will be in the global scope.

var a = 1;

function f() {
  var a = 2;
  var e = eval;
  e("console.log(a)");
}

f(); // 1

In the above code, eval is an alias call, so even if it is in a function, its scope is still the global scope, so the output a is a global variable. In this way, the engine can confirm that e() will not affect the current function scope, and this line can be eliminated during optimization.

There are various forms of eval alias calls. As long as they are not called directly, they are all alias calls, because the engine can only distinguish that the form of eval() is a direct call.

eval.call(null, "...");
window.eval("...")(1, eval)("...")(eval, eval)("...");

The above forms are all alias calls of eval, and the scopes are all global scopes.