A function is a block of code that can be called repeatedly. The function can also accept input parameters, and different parameters have unique corresponding return values.
overview
function declaration
JavaScript has three ways of declaring functions.
(1) function command
function
The code block declared by the command is a function. function
The command is followed by the function name, and the function name is followed by a pair of parentheses, which are the parameters passed into the function. The body of the function is enclosed in curly braces.
function print(s) { console.log(s); }
The above code names a print
function, and using this form later print()
, you can call the corresponding code. This is called a function declaration (Function Declaration).
(2) Function expression
In addition to function
declaring functions with commands, 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 function expression (Function Expression), because only expressions can be placed on the right side of the equal sign of the assignment statement.
When a function is declared using a function expression, function
the command is not followed by the function name. If you add a function name, 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
In the above code, the function name is added to the function expression x
. This is x
only available inside the function body, referring to the function expression itself, and is not available anywhere else. This way of writing has two uses, 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 this is an anonymous function). Therefore, it is also very common to declare functions in the following form.
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. Function declarations do not need a semicolon after the closing curly brace. In general, the two ways of declaring functions are slightly different and can be considered approximately equivalent.
(3) Function constructor
The third way to declare a function is Function
as a constructor.
var add = new Function( 'x', 'y', 'return x + y' ); // Equivalent to function add(x, y) { return x + y; }
In the above code, Function
the constructor accepts three parameters, except that the last parameter is the add
"function body" of the function, and the other parameters are add
the parameters of the function.
You can pass any number of parameters to Function
the constructor, only the last parameter will be treated as the function body, if there is only one parameter, it will be the function body.
var foo = new Function( 'return "hello world";' ); // Equivalent to function foo() { return 'hello world'; }
Function
The constructor does not need to use new
the command, and the return result is exactly the same.
Overall, this way of declaring functions is so unintuitive that almost no one uses it.
Duplicate declaration of function
If the same function is declared multiple times, later declarations override earlier ones.
function f() { console.log(1); } f() // 2 function f() { console.log(2); } f() // 2
In the above code, the latter function declaration overrides the previous one. Also, because of function name promotion (see below), it is important to note that the previous declaration is invalid at any time.
The parenthesis operator, the return statement, and recursion
When calling a function, use the parenthesis operator. In the parentheses, you can add the parameters of the function.
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 will be called.
The statement inside the function body return
means return. When the JavaScript engine encounters return
a statement, it directly returns return
the value of the expression that follows, and even if there are statements behind, it will not be executed. In other words, return
the expression in the statement is the return value of the function. return
statement is not required, if not, the function returns nothing, or returns undefined
.
A function can call itself, which is called recursion. The following 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
the function is called again fib
, and the sixth element of the Fibonacci sequence is calculated to be 8.
first class citizen
The JavaScript language treats functions as values, on the same level as other values (numbers, strings, booleans, etc.). Wherever you can use a value, you can use a function. For example, functions can be assigned to variables and properties of objects, passed as parameters to other functions, or returned as the result of functions. A function is just a value that can be executed, and there is nothing special about it.
Because functions are equal to other data types, functions 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; // function as parameter and return value function a(op){ return op; } a(add)(1, 1) // 2
Hoisting of function names
The JavaScript engine treats function names like variable names, so function
when a function is declared with a directive, the entire function is hoisted to the top of the code just like a variable declaration. Therefore, the following code will not report an error.
f(); function f() {}
On the surface, it looks like the above code calls the function before it is declared f
. But in fact, due to "variable hoisting", the function f
is hoisted to the head of the code, that is, it is declared before the call. However, if the function is defined with an assignment statement, JavaScript will report an error.
f(); var f = function (){}; // TypeError: undefined is not a function
The code above is equivalent to the form below.
var f; f(); f = function () {};
The second line of the above code, f
when called, f
is only declared, and has not been assigned a value, which is equal to undefined
, so an error will be reported.
Note that if the same function is declared with function
a command and an assignment statement as in the following example, the definition of the assignment statement will be used at the end due to function promotion.var
var
var f = function () { console.log('1'); } function f() { console.log('2'); } f() // 1
In the above example, on the surface, the function declared later f
should overwrite the previous var
assignment statement, but because of function promotion, it is actually the opposite.
Function properties and methods
name attribute
The function name
property returns the name of the function.
function f1() {} f1.name // "f1"
If the function is defined by variable assignment, name
the property returns the variable name.
var f2 = function () {}; f2.name // "f2"
However, the above case is only true when the value of the variable is an anonymous function. If the value of the variable is a named function, name
the property returns function
the name of the function after the keyword.
var f3 = function myName() {}; f3.name // 'myName'
In the above code, f3.name
the name of the function expression is returned. Note that the real function name is still f3
, and myName
this name is only available inside the function body.
name
One use of attributes 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, test
through name
the attribute inside the function, you can know what function the parameter passed in is.
length property
The attribute of the function length
returns the number of parameters that the function is expected to pass in, 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
attribute is the number of parameters when it is defined. No matter how many parameters are entered when calling, length
the attribute is always equal to 2.
length
Attributes provide a mechanism to judge the difference between parameters at the time of definition and at the time of calling, so as to realize "method overloading" (overload) of object-oriented programming.
toString()
The method of the function toString()
returns a string whose content is the source code of the function.
function f() { a(); b(); c(); } f.toString() // function f() { // a(); // b(); // c(); // }
In the above example, the method f
of the function toString()
returns f
the source code, including newline characters.
For those native functions, toString()
the method returns function (){[native code]}
.
Math.sqrt.toString() // "function sqrt() { [native code] }"
The above code Math.sqrt()
is a native function provided by the JavaScript engine, and toString()
the method returns a hint of the native code.
Comments inside functions can also be returned.
function f() {/* This is a multiline comment */} f.toString() // "function f(){/* // This is a // multi-line comment // */}"
Taking advantage of this, multi-line 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 a multiline comment */} multiline(f); // " This is a // multi-line comment"
In the above example, f
there is a multi-line comment inside the function. After toString()
the method gets f
the source code, remove the first and last two lines to get a multi-line string.
function scope
definition
Scope refers to the scope in which a variable exists. In the ES5 specification, JavaScript has only two scopes: one is global scope, variables exist throughout the program and can be read everywhere; the other is function scope, variables only exist inside functions. ES6 has added block-level scope, which is not covered in this tutorial.
For top-level functions, variables declared outside the function are global variables (global variable), which can be read inside the function.
var v = 1; function f() { console.log(v); } f() // 1
The above code shows that f
global variables can be read inside the function v
.
Variables defined inside a function that cannot be read outside 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 that cannot be read outside the function.
Variables defined inside the function will overwrite global variables with the same name in this scope.
var v = 1; function f(){ var v = 2; console.log(v); } f() // 2 v // 1
In the code above, variables v
are defined both outside and inside the function. As a result, defined inside a function, local variables v
override global variables v
.
Note that for var
commands, local variables can only be declared inside the function, and all variables declared in other blocks are 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 hoisting inside functions
Like the global scope, the phenomenon of "variable hoisting" will also occur inside the function scope. var
The variable declared by the 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
A function itself is also a value and has its own scope. Its scope, like a variable, is the scope in which it is declared, not the scope in which it is run.
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 f
declared outside the function, so its scope is bound to the outer layer, and the internal variable a
will not get f
a value in the function body, so the output 1
is not 2
.
In short, the scope in which the function is executed is the scope in which it was defined, not the scope in which it was called.
It's easy to make a mistake if a function A
calls a function B
without taking into account that the function B
doesn't refer to the function A
's internal variables.
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 into the function y
. However, the function x
is y
declared outside the function body, and the scope is bound to the outer layer, so y
the internal variables of the function cannot be found a
, resulting in an error.
Similarly, the function declared inside the function body, the scope is bound to the inside of 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, foo
a function is declared inside the function bar
, bar
the scope binding foo
. When we foo
fetch execution externally bar
, the variable x
points to foo
the internal x
, not foo
the external x
. It is this mechanism that constitutes the "closure" phenomenon to be explained below.
parameter
overview
When a function is running, it sometimes needs to provide external data. Different external data will get different results. This external data is called a parameter.
function square(x) { return x * x; } square(2) // 4 square(3) // 9
The above formula x
is square
the parameter of the function. Each time you run, you need to provide this value, otherwise you won't get the result.
omission of parameters
Function parameters are not required, and 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 of the code above f
defines two parameters, but no matter how many parameters (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 length
the properties of a function have nothing to do with the number of parameters actually passed in, but only reflect 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 ones. If you must omit the first parameter, you can only pass it in explicitly 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 parameter is a value of a primitive type (number, string, Boolean value), the transfer method is passed by value (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 value of a primitive type, and f
the way to pass it into the function is to pass by value. Therefore, inside the function, p
the value of is a copy of the original value, no matter how you modify it, 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. That is to say, the address of the original value passed into the function, so modifying the parameter inside the function will affect the original value.
var obj = { p: 1 }; function f(o) { o.p = 2; } f(obj); obj.p // 2
In the above code, the address of f
the parameter object is passed into the function obj
. obj
Therefore, properties modified inside the function p
will affect the original value.
Note that if the internal modification of the function is not a certain attribute of the parameter object, but replaces the entire parameter, 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, f()
inside the function, the parameter object obj
is completely replaced with another value. The original value is not affected at this time. This is because o
the value of the formal parameter ( ) is actually obj
the address of the parameter, and reassignment o
leads to o
pointing to another address, and the value stored in the original address is of course not affected.
parameter with the same name
If there is a parameter with the same name, the value that occurs last is taken.
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 taking a value, the latter a
shall prevail, even if the latter a
has no value or is omitted, it shall prevail.
function f(a, a) { console.log(a); } f(1) // undefined
When the function is called f()
, if the second parameter is not provided, a
the value of is changed undefined
. At this time, if you want to get a
the value of the first one, you can use arguments
the object.
function f(a, a) { console.log(arguments[0]); } f(1) // 1
arguments object
(1) Definition
Since JavaScript allows functions to have an indefinite number of parameters, a mechanism is needed to read all parameters inside the function body. This is where arguments
objects come in.
arguments
The object contains all the parameters of the function at runtime, arguments[0]
which is the first parameter, arguments[1]
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, arguments
objects 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, f()
the parameters passed in when the function is called are modified to sum inside the 3
function 2
.
In strict mode, arguments
objects and function parameters do not have a linkage relationship. That is, modifying arguments
the object does 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, and modifying arguments
the object at this time will not affect the real parameters a
and sum b
.
Through the properties arguments
of the object length
, you can determine how many parameters are used when the function is called.
function f() { return arguments.length; } f(1, 2, 3) // 3 f(1) // 1 f() // 0
(2) Relationship with arrays
Note that, while arguments
much like an array, it is an object. Array-specific methods (such as slice
and forEach
) cannot arguments
be used directly on objects.
arguments
The real solution, if you want your object to use array methods, is to arguments
convert it to a real array. Here are two commonly used conversion methods: slice
methods and filling new arrays 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
arguments
The object has a callee
property that returns the original function it corresponds to.
var f = function () { console.log(arguments.call === f); } f() // true
It can be used arguments.callee
to achieve the purpose of calling the function itself. This attribute is disabled in strict mode, so its use is not recommended.
Other knowledge points of functions
Closure
Closure is a difficult point of the JavaScript language, and it is also its feature. Many advanced applications rely on closures.
To understand closures, you must first understand variable scope. As mentioned earlier, JavaScript has two kinds of 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 global variables n
.
However, under normal circumstances, variables declared inside the function cannot be read from outside the function.
function f1() { var n = 999; } console.log(n) // Uncaught ReferenceError: n is not defined(
In the above code, f1
the variables declared inside the function n
cannot be read outside the function.
If for various reasons, you need to get the local variables in the function. Normally, this is not possible and is only possible through workarounds. That is, inside the function, define another function.
function f1() { var n = 999; function f2() { console.log(n); // 999 } }
In the above code, the function f2
is f1
inside the function, and f1
all local variables inside f2
are visible to the right. But the reverse is not possible, f2
the internal local variables f1
are not visible. 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 the local variable f2
can be read f1
, as long as it is used f2
as the return value, can't we f1
read its internal variable externally!
function f1() { var n = 999; function f2() { console.log(n); } return f2; } var result = f1(); result(); // 999
In the above code, f1
the return value of the function is the function f2
. Since the internal variable f2
can be read , the internal variable f1
can be obtained externally .f1
Closures are functions f2
, that is, functions that can read variables inside other functions. Since in the JavaScript language, only the sub-functions inside the function can read the internal variables, so 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, such as f2
remembering the environment in which it was born f1
, so f2
it can get f1
internal variables from it. 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, closures can keep the environment in which they were born. Please see the following example, the closure makes the internal variable remember the operation result of 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
it is createIncrementor
the internal variable of the function. With closures, start
the state is preserved, and each call is calculated on the basis of the previous call. It can be seen from this that the closure inc
makes createIncrementor
the internal environment of the function always exist. So, a closure can be seen as an interface to the inner scope of a function.
Why can closures return the internal variables of the outer function? The reason is that the closure (in the above example inc
) 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 running 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 methods of objects.
function Person(name) { was _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, Person
the internal variables of the function become the private variables of the returned object _age
through the closure getAge
and sum.setAge
p1
Note that every 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 Invoked Function Expressions (IIFEs)
According to the syntax of JavaScript, parentheses ()
follow the function name and indicate that the function is called. For example, print()
it means calling print
a function.
Sometimes, we need to call a function immediately after defining it. At this time, you cannot put parentheses after the function definition, which will cause a syntax error.
function(){ /* code */ }(); // SyntaxError: Unexpected token (
The reason for this error is that function
this keyword 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 after the definition with parentheses.
var f = function f(){ return 1}(); f // 1
In the above code, the parentheses are added directly after the function definition to call, and no error is reported. The reason is function
that as an expression, the engine treats the function definition as a value. In this case, no error will be reported.
In order to avoid parsing ambiguity, JavaScript stipulates that if function
a keyword appears at the beginning of a line, it will be interpreted as a statement. Therefore, after the engine sees that the beginning of the line is function
a keyword, 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 calling the function immediately after definition is not to let function
it appear at the beginning of the line, so that the engine understands it as an expression. The easiest way to deal with it is to put it in parentheses.
(function(){ /* code */ }()); // or (function(){ /* code */ })();
The above two writing methods start with parentheses, and the engine will think that what follows is an expression rather than 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 ways of writing is required. If the semicolon is omitted, an error may be reported when two IIFEs are connected.
// 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 parameter of the first line.
By extension, any method that allows the interpreter to process function definitions with expressions can produce the same effect, such as the following three writing methods.
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 */ }();
Normally, this kind of "immediately executed function expression" is only used for anonymous functions. It has two purposes: one is that there is no need 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 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 2 is better than writing method 1, because it completely avoids polluting global variables.
eval command
basic usage
eval
Commands accept a string as an argument and execute the string as a statement.
eval('var a = 1;'); a // 1
The above code runs the string as a statement, generating variables a
.
If the argument string cannot be run as a statement, an error will be reported.
eval('3x') // Uncaught SyntaxError: Invalid or unexpected token
The strings placed eval
in should have their own meaning and cannot be eval
used in conjunction with other commands. For example, the following code will throw an error.
eval('return;'); // Uncaught SyntaxError: Illegal return statement
The above code will report an error because return
it cannot be used alone and must be used in a function.
If eval
the argument is not a string, it will be returned unchanged.
eval(123) // 123
eval
It does not have its own scope and is executed in the current scope, so it may modify the value of variables in the current scope, causing security problems.
var a = 1; eval('a = 2'); a // 2
In the above code, eval
the command modifies a
the value of the external variable. For this reason, eval
there are security risks.
In order to prevent this risk, JavaScript stipulates that if strict mode is used, eval
variables declared inside 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, f
the inside of the function is in strict mode. At this time, the variables eval
declared inside foo
will not affect the outside.
However, even in strict mode, eval
variables in the current scope can still be read and written.
(function f() { 'use strict'; var foo = 1; eval('foo = 2'); console.log(foo); // 2 })()
In the above code, in strict mode, eval
external variables are still rewritten internally, which shows that security risks still exist.
In short, eval
the essence is to inject code in the current scope. It is generally not recommended due to security risks and detrimental to JavaScript engine optimization execution speed. Under normal circumstances, eval
the most common occasion is to parse the string of JSON data, but the correct way should be to use the native JSON.parse
method.
Alias call for eval
As mentioned earlier, eval
it is not conducive to engine optimization execution speed. What's more troublesome is that there is the following situation where the engine cannot tell what is being executed at the stage of static code analysis eval
.
var m = eval; m('var x = 1'); x // 1
In the above code, the variable m
is eval
an alias of . During the static code analysis phase, the engine cannot tell that m('var x = 1')
what is being executed is eval
a command.
In order to ensure eval
that aliases do not affect code optimization, the JavaScript standard stipulates that all executions using aliases eval
are eval
in the global scope.
var a = 1; function f() { var a = 2; where e = eval; e('console.log(a)'); } f() // 1
In the above code, eval
it is an alias call, so even if it is in a function, its scope is still the global scope, so the output is a a
global variable. In this way, the engine can confirm e()
that it will not affect the current function scope, and this line can be excluded during optimization.
eval
There are various forms of alias calls, as long as it is not a direct call, it is an alias call, because the engine can only distinguish eval()
this form as a direct call.
eval.call(null, '...') window.eval('...') (1, eval)('...') (eval, eval)('...')
The above forms are all eval
alias calls, and the scope is the global scope.
reference link
-
Mark Daggett, Functions Explained
-
Juriy Zaytsev, Named function expressions demystified
-
Marco Rogers polotek, What is the arguments object?
-
Juriy Zaytsev, Global eval. What are the options?
-
Axel Rauschmayer, Evaluating JavaScript code via eval() and new Function()