Preface
As we all know, in the field of front-end development, functions are first-class citizens. This shows the importance of functions. This article aims to introduce some of the characteristics and methods of functions and have a better understanding of functions.
text
1. Arrow function
ECMAScript 6 adds the ability to define function expressions using fat arrow (=>) syntax. To a large extent, the behavior of the function object instantiated by the arrow function is the same as the function object created by the formal function expression. Anywhere you can use function expressions, you can use arrow functions:
let arrowSum = (a, b) => {
return a + b;
};
let functionExpressionSum = function(a, b) {
return a + b;
};
console.log(arrowSum(5, 8)); // 13
console.log(functionExpressionSum(5, 8)); // 13
using arrow functions:
- If the function body of the arrow function is not enclosed in braces, it will implicitly return the value of this line of code
- Arrow functions cannot use
arguments
,super
andnew.target
, nor can they be used as constructors - Arrow function does not have
prototype
attribute
2. Function declaration and function expression
Before any code is executed, the JavaScript engine reads the function declaration and generates the function definition in the execution context. The function expression must wait until the code execution reaches its line before generating the function definition in the execution context.
// 没问题
console.log(sum(10, 10));
function sum(num1, num2) {
return num1 + num2;
}
The above code can run normally, because the function declaration will be read and added to the execution context before any code is executed. This process is called function declaration hoisting. When executing the code, the JavaScript engine will perform a scan first, and promote the found function declaration to the top of the source code tree. So even if function definitions appear after the code that calls them, the engine will lift the function declarations to the top. If you change the function declaration in the previous code to an equivalent function expression, an error will occur during execution:
// 会出错
console.log(sum(10, 10));
let sum = function(num1, num2) {
return num1 + num2;
};
Some students may think that the error of the above code is temporary dead zone caused by let. In fact, the reason does not lie here, it is because the function definition is contained in a variable initialization statement, not in the function declaration. This means that if the code is not executed to
let
, then there is no function definition in the execution context. You can try it yourself, even if it is var
, it will still make an error.
3. Inside the function
In ECMAScript 5, there are two special objects inside the function: arguments
and this
. ECMAScript 6 adds the new.target
attribute.
arguments
It is an array object of type , which contains all the parameters passed in when calling the function. This object is only available when a function is defined with the function keyword (as opposed to creating a function using arrow syntax). But the arguments object actually has a callee property, which is a pointer to the function where the arguments object is located.
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * factorial(num - 1);
}
}
// 上述代码可以运用arguments来进行解耦
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * arguments.callee(num - 1);
}
}
The factorial() function after this rewrite has replaced the hard-coded factorial arguments.callee
This means that no matter what the name of the function is, the correct function can be referenced.
Decoupling example of arguments.callee
let trueFactorial = factorial;
factorial = function() {
return 0;
};
console.log(trueFactorial(5)); // 120
console.log(factorial(5)); // 0
Here the factorial function is rewritten after assigning trueFactorial. So if we don’t use arguments.callee
in the recursion, then obviously the result of trueFactorial(5) is also 0, but after we decouple, the new variables can still proceed normally.
this
Another special object inside the function is this
, which has different behaviors in standard functions and arrow functions.
In the standard function, this refers to the context object that the function is used as a method call. At this time, it is usually called the this value (when the function is called in the global context of the web page, this points to windows).
In the arrow function, this refers to the context in which the arrow function is defined.
caller
This attribute refers to the function that called the current function, or null if it was called in the global scope.
function outer() {
inner();
}
function inner() {
console.log(inner.caller);
}
outer();
The above code will show the source code of the outer() function. This is because ourter() calls inner(), and inner.caller points to outer(). If you want to reduce the degree of coupling, you can use arguments.callee.caller to refer to the same value:
function outer() {
inner();
}
function inner() {
console.log(arguments.callee.caller);
}
outer();
new.target
A function in ECMAScript can always be used as a constructor to instantiate a new object, or it can be called as a normal function. ECMAScript 6 adds the new.target property to detect whether a function is called with the new keyword. If the function is called normally, the value of new.target is undefined; if it is called with the new keyword, new.target will refer to the called constructor.
function King() {
if (!new.target) {
throw 'King must be instantiated using "new"'
}
console.log('King instantiated using "new"');
}
new King(); // King instantiated using "new"
King(); // Error: King must be instantiated using "new"
Some extensions can be made here. Is there any other way to determine whether the function is called by new?
You can use instanceof
to judge. We know what happened new
Expressed with the following code:
var p = new Foo()
// 实际上执行的是
// 伪代码
var o = new Object(); // 或 var o = {}
o.__proto__ = Foo.prototype
Foo.call(o)
return o
The above pseudo code says this in MDN:
- A new object inherited from Foo.prototype is created.
- Call the constructor Foo with the specified parameters and bind this to the newly created object. new Foo is equivalent to new Foo(), that is, if no parameter list is specified, Foo is called without any parameters.
- The object returned by the constructor is the result of the new expression. If the constructor does not explicitly return an object, the object created in step 1 is used. (Under normal circumstances, the constructor does not return a value, but the user can choose to actively return the object to cover the normal object creation steps)
new
is finished. Now let’s take a look at instanceof
. MDN says this: instanceof operator is used to detect whether the prototype property of the constructor appears in the prototype chain of an instance object.
That is to say, A are all equal to B.prototype, then A instanceof B returns true, now that the knowledge points have been introduced, you can start to code
function Person() {
if (this instanceof Person) {
console.log("通过new 创建");
return this;
} else {
console.log("函数调用");
}
}
const p = new Person(); // 通过new创建
Person(); // 函数调用
Analysis: We know that the this of the new constructor points to an instance, so the above code is not difficult to draw the following conclusion this.__proto__ === Person.prototype
. So in this way, you can judge whether the function is called by new or function
Here we can this instanceof Person
rewrite this instanceof arguments.callee
4. Closure
I finally talked about closures. Closures are really a must in interviews, so it’s still necessary to master them.
Closures refer to functions that refer to variables in the scope of another function, usually implemented in nested functions.
function foo() {
var a = 20;
var b = 30;
function bar() {
return a + b;
}
return bar;
}
In the above code, because the bar function inside the foo function uses the variables inside the foo function, and the bar function return returns the variables, the closure is generated, which allows us to get these variables outside.
const b = foo();
b() // 50
The foo function creates an execution context when it is called, and the variables a and b can be used in this context. Theoretically, at the end of the foo call, the variables inside the function will be recycled by the garbage collection mechanism of the v8 engine through a specific mark. But here, due to the generation of the closure, the variables a and b will not be recycled, which leads to we can access the variables inside the function in the global context (or other execution contexts).
I saw a statement before:
Whenever you declare a new function and assign it to a variable, you must store the function definition and closure. The closure contains all the variables in the scope when the function is created. Similar to a backpack, the function definition comes with a small backpack, its package All variables in the scope of the function definition are stored in
Derive a classic interview question from this
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}
How can the output of the above code become 1, 2, 3, 4, 5?
Using es6, we can make a very simple answer: replace var with let.
So how do we use the closure knowledge we just learned to answer? code show as below:
for (var i = 1; i <= 5; i++) {
(function (i) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
})(i)
}
According to the above statement, the closure is regarded as a backpack. The backpack contains the variables at the time of definition. Each time the loop is looped, the value of i is stored in a closure. When the operation defined in setTimeout is executed, the corresponding closure is accessed. The saved value of i can be solved.
5. Immediately call function expression (IIFE)
The following is to call the function expression immediately
(function() {
// 块级作用域
})();
Use IIFE to simulate block-level scope, that is, declare variables inside a function expression, and then call the function immediately. In this way, variables in the scope of the function body are as if they were in the block-level scope.
// IIFE
(function () {
for (var i = 0; i < count; i++) {
console.log(i);
}
})();
console.log(i); // 抛出错误
Block-level scope of ES6:
// 内嵌块级作用域
{
let i;
for (i = 0; i < count; i++) {
console.log(i);
}
}
console.log(i); // 抛出错误
// 循环的块级作用域
for (let i = 0; i < count; i++) {
console.log(i);
}
console.log(i); // 抛出错误
Another function of IIFE is to solve the output problem of settimeout above.
Appendix knowledge points
about instanceof
Function instanceof Object;//true
Object instanceof Function;//true
You can try to run the above code in a browser. It's amazing. So what's the reason?
Borrow a picture of the boss
Then you can get
//构造器Function的构造器是它自身
Function.constructor=== Function;//true
//构造器Object的构造器是Function(由此可知所有构造器的constructor都指向Function)
Object.constructor === Function;//true
//构造器Function的__proto__是一个特殊的匿名函数function() {}
console.log(Function.__proto__);//function() {}
//这个特殊的匿名函数的__proto__指向Object的prototype原型。
Function.__proto__.__proto__ === Object.prototype//true
//Object的__proto__指向Function的prototype,也就是上面中所述的特殊匿名函数
Object.__proto__ === Function.prototype;//true
Function.prototype === Function.__proto__;//true
in conclusion:
- The constructors of all constructors point to Function
- Function's prototype points to a special anonymous function, and the __proto__ of this special anonymous function points to Object.prototype
end
This article mainly refers to "JavaScript Advanced Programming Fourth Edition". Due to the limited level of the author, please contact me if you have any errors. Thank you for reading!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。