Introduction
In many traditional languages (C/C++/Java/C#, etc.), functions exist as second-class citizens. You can only declare a function with the keywords of the language and then call it. If you need to pass the function as a parameter to another function, or assign a value to a local variable, or as a return value, you need to make a breakthrough through special methods such as function pointer and proxy (delegate).
In the JavaScript world, functions are first-class citizens. They not only have all the ways of using traditional functions (declarations and calls), but also can assign values, pass parameters, and return like simple values. Such functions are also called first-class functions. Not only that, functions in JavaScript also act as class constructors, and are also instances of Function class. Such multiple identities make JavaScript functions very important.
1. JavaScript function entry-level
Like ordinary languages, JavaScript functions also follow the principle of declaration first and then use. Function names can only contain letters, numbers, underscores or $, and cannot start with numbers. There are two common ways to declare functions:
The code copy is as follows:
// Declare the function myfunc directly
function myfunc(/* arguments */) {
}
// Assign anonymous functions to the local variable myfunc
var myfunc = function(/* arguments */) {
}
Note that there are subtle differences in the above two functions declaration methods: the first method is a named function when declared, whether it is a declared function before, after the call, or even the location where it will not be executed (for example, after the return statement or in a branch that will never be true), it is accessible in the entire scope; the second method is to assign anonymous functions to variables. Strictly speaking, this is not a function declaration but a function expression. Before assignment, this function cannot be accessed by any code, which means that the assignment must be completed before the call, otherwise an error will appear when calling: "TypeError: undefined is not a function". For example:
The code copy is as follows:
myfunc1(); // Can be called normally, because myfunc1 uses a direct declaration method
function myfunc1() {
}
myfunc2(); // Error TypeError: undefined is not a function
var myfunc2 = function() {
};
The basic calling method of functions is called in the same way as in traditional languages: myfunc(). JavaScript functions also support direct or indirect recursive calls. For example, the classic Fibonacci function can be implemented in JavaScript like this:
The code copy is as follows:
function fib(n) {
if (n == 1 || n == 2) {
return 1;
} else {
return fib(n - 2) + fib(n - 1);
}
}
Functions in JavaScript can handle variable-length parameters. They all have a local variable called arguments inside the function. It is an array-like object that contains all the parameters passed in when calling, and have a length attribute to represent the number of parameters. For example:
The code copy is as follows:
function test() {
alert(arguments.length);
}
test(1); // 1
test(1, 'a'); // 2
test(true, [], {}); // 3 Use arguments to implement functions similar to printf in C language, and can also be used to implement polymorphism of methods.
2. Advanced JavaScript functions
2.1 Anonymous and nested functions
In JavaScript, you can declare a function without a name, called anonymous function (Anonymous Function). At the same time, JavaScript also allows the declaration of functions inside functions, called nested functions, and the scope of nested functions is the entire parent function.
In the previous part of the function declaration, I saw an usage of anonymous functions and nested functions. Since anonymous functions have no name, they will not introduce new variables to pollute the context and will bring new variable scopes. Therefore, anonymous functions are often used to prevent global environmental pollution.
There is a special global object in the JavaScript runtime. This object stores global functions and variables. In actual development, several third-party libraries or multiple js files are often used. If you accidentally introduce duplicate variables or function declarations into the global object, it will cause confusion in the code execution. For example, two js files are introduced successively, and their own function log is defined as internal use. The second introduced function will overwrite the definition of the first one and will not throw any errors. Calling the log function in subsequent execution may cause an error. At this time, using an anonymous function to wrap the logic in the entire js can avoid this error. This method has been used by most open source js libraries.
The code copy is as follows:
(function() { // Anonymous function
function log(msg) {
console.log(msg);
}
// Other codes
}()); // Execute immediately
The above code is a simple example. The scope of the log function is limited to this anonymous function. The anonymous function is included in a pair of brackets () outside to form a function expression. The value of the expression is a function, followed by a pair of brackets to indicate that the function is executed immediately, so that the original code can be executed normally. However, functions declared in this way, variables declared through var, etc. are internal and cannot be accessed by any code other than anonymous functions. If you need to expose some functions as interfaces, there are several methods:
The code copy is as follows:
var mylib = (function(global) {
function log(msg) {
console.log(msg);
}
log1 = log; // Method 1: Use the default behavior of variable declaration without var to become a global variable in log1 (not recommended)
global.log2 = log; // Method 2: directly add log2 attribute to the global object and assign it to a log function (recommended)
return { // Method 3: Return a series of interface function collection objects through anonymous functions and assign them to the global variable mylib (recommended)
log: log
};
}(window));
2.2 High-order Function
If a function is used as a parameter or return value, it is called a higher-order function. Functions in JavaScript can be used as higher-order functions, which is also a feature of the first type of functions. Let’s analyze these two usage methods below.
The code copy is as follows:
function negative(n) {
return -n; // Take the opposite value of n
}
function square(n) {
return n*n; // square of n
}
function process(nums, callback) {
var result = [];
for(var i = 0, length = nums.length; i < length; i++) {
result[i] = callback(nums[i]); // Pass all elements in the array nums to callback for processing, and save the return value as the result
}
return result;
}
var nums = [-3, -2, -1, 0, 1, 2, 3, 4];
var n_neg = process(nums, negative);
// n_neg = [3, 2, 1, 0, -1, -2, -3, -4];
var n_square = process(nums, square);
// n_square = [9, 4, 1, 0, 1, 4, 9, 16];
The above code shows an example of passing a function as a parameter into another function process call. In the implementation of the process function, callback is regarded as a black box, responsible for passing the parameter to it, and then obtaining the return value. The specific implementation of callback is not clear before the call. Only when 20 and 22 lines are executed, callback is represented by negative or square, respectively, and each element is taken with the opposite value or square value.
The code copy is as follows:
function generator() {
var i = 0;
return function() {
return i++;
};
}
var gen1 = generator(); // Get a natural number generator
var gen2 = generator(); // Get another natural number generator
var r1 = gen1(); // r1 = 0
var r2 = gen1(); // r2 = 1
var r3 = gen2(); // r3 = 0
var r4 = gen2(); // r4 = 1
The above code shows an example of using a function as a return value. Generator is a natural number generator function, and the return value is a natural number generator function. Each time generator is called, an anonymous function will be returned as the result. This anonymous function returns each natural number in turn when it is actually called. The variable i in the generator will increase by 1 every time this anonymous function is called, which is actually a closure. Let's introduce closures below.
2.3 Closure
Closures are not a new concept, and many functional languages use closures. In JavaScript, when you use variables within the scope of external functions in an embedded function, you use closures. Use a commonly used analogy to explain the relationship between a closure and a class: a class is data with functions, and a closure is a function with data.
Variables used in closures have a characteristic that they are not released when the parent function returns, but end with the end of the closure life cycle. For example, as in the generator example in the previous section, gen1 and gen2 use independent variables i respectively (when gen1's i is increased by 1, gen2's i will not be affected, and vice versa). As long as the two variables gen1 or gen2 are not garbage collected by the JavaScript engine, their respective variables i will not be released. In JavaScript programming, closures are used unconsciously. This feature of closures is easy to use, but also easily leads to memory leakage problems. For example:
The code copy is as follows:
var elem = document.getElementById('test');
elem.addEventListener('click', function() {
alert('You clicked ' + elem.tagName);
});
The purpose of this code is to display its label name when clicking on a node. It registers an anonymous function as a click event handling function of a DOM node. A DOM object elem is referenced in the function, which forms a closure. This will generate a circular reference, that is: DOM->Clossary->DOM->Clossary... The DOM object will not be released before the closure is released; and the closure exists as the event handling function of the DOM object, so the closure will not be released before the DOM object is released. Even if the DOM object is deleted in the DOM tree, due to the existence of this circular reference, neither the DOM object nor the closure will be released. This memory leak can be avoided using the following methods:
The code copy is as follows:
var elem = document.getElementById('test');
elem.addEventListener('click', function() {
alert('You clicked ' + this.tagName); // No more directly references the elem variable
});
In the above code, this is used instead of elem (this pointer points to the DOM element itself in the DOM event handling function), so that the JS runtime no longer believes that the function uses the parent class variables, so no longer form a closure.
Closures will also bring many similar memory leak problems. You can only pay attention to closures when writing code and try to avoid such problems.
2.4 Class constructor
JavaScript functions are also used as class constructors, so you can use the new keyword to create an instance of the class as long as you declare a function.
The code copy is as follows:
function Person(name) {
this.name = name;
this.toString = function() {
return 'Hello, ' + this.name + '!';
};
}
var p = new Person('Ghosttheaven');
alert(p); // Hello, Ghosttheaven! In the above example, Person function is used as the class constructor. At this time, this points to the newly created instance object, and can add properties and methods to the instance. For detailed object-oriented JavaScript programming, please refer to this article. What I want to say here is the return value problem when using JavaScript functions as class constructors.
The code copy is as follows:
function MyClass(name) {
this.name = name;
return name; // The return value of the constructor?
}
var obj1 = new MyClass('foo');
var obj2 = MyClass('foo');
var obj3 = new MyClass({});
var obj4 = MyClass({});
The above constructor is quite special, with a return statement, so what objects do obj1~obj4 point to? The actual result is this:
The code copy is as follows:
obj1 = MyClass object
obj2 = 'foo'
obj3 = {}
obj4 = {}
The specific reasons are explained in this article, and I will not repeat it in this article. Since constructors with return values will produce strange results, do not call return statements with return values in the constructor (empty return can be done).
3. JavaScript functions monster level
Welcome to the monster-level function teaching area, where you will be given how to face the old monster calmly and freely. . .
3.1 Function Class
There is a built-in class called Function in the JavaScript runtime. Declaring a function with the function keyword is actually an abbreviation for creating Function class objects. All functions have all the methods of the Function class, such as call, apply, bind, etc. You can verify this statement through the instanceof keyword.
Since Function is a class, its constructor is Function (it is itself an object of the Function class), and a function object should be generated through the new keyword. The first monster is here, which is how to construct a function using the Function class. The syntax of Function is as follows:
The code copy is as follows:
new Function ([arg1[, arg2[, ... argN]],] functionBody)
where arg1, arg2, ... argN is a string, representing the parameter name, and functionBody is also a string, representing the function body. The previous parameter name is more or less. The Function constructor will treat the last parameter as the function body, and the previous ones as parameters.
The code copy is as follows:
var func1 = new Function('name', 'return "Hello, " + name + "!";');
func1('Ghosttheaven'); // Hello, Ghosttheaven!
The above method constructs a function through Function, which is exactly the same as other functions declared with the function keyword.
Seeing this, many people may ask why such a monster is needed? "What exists is reasonable", the Function class has its unique purpose. You can use it to dynamically generate various function logic, or replace the functions of the eval function, and keep the current environment from being polluted*.
3.2 Self-update Function
In many languages, once a function has been declared, it cannot declare the function of the same name again, otherwise syntax errors will occur. Functions in JavaScript can not only be declared repeatedly, but also update themselves. The monster that I eat is here!
The code copy is as follows:
function selfUpdate() {
window.selfUpdate = function() {
alert('second run!');
};
alert('first run!');
}
selfUpdate(); // first run!
selfUpdate(); // second run! This function can be used for logic that is run only once, and after the first run, it is replaced with a new piece of logic.
summary
JavaScript's functions are very powerful. While solving many problems beautifully, they also bring many negative problems. Monster-level functions are usually used with little-known usage. Unless it is particularly necessary, it will cause code reading difficulties and affect team development efficiency.
* A strict mode was introduced in the new ECMAScript. In the strict mode, the eval function is greatly restricted and can ensure that the environment is not polluted.
Do you understand, it’s very practical. If there are any missing places, please give me some advice and make progress together.