preface
As a new syntax added in ES6, arrow function is deeply loved by developers and interviewers because it simplifies our code and allows developers to get rid of the "erratic" this direction. Arrow function often appears in the interview questions of major companies because it is different from ordinary functions, This paper will analyze the arrow function and ordinary function.
If this article helps you, ❤️ Follow + like ❤️ Encourage the author, the official account and the first time to get the latest articles.
Introduce arrow function
ES6 allows the use of "arrows" (= >) to define functions. The arrow function is equivalent to an anonymous function and simplifies the function definition.
Let's see how to declare a function using (= >):
// Arrow function let foo = (name) => `I am ${name}` foo('Nanjiu') // I'm Nan Jiu // Equivalent to the following ordinary function let foo2 = function(name) { return `I am ${name}` }
There are two formats for arrow functions. One is like the one above, which contains only one expression, even {...} And return are omitted. Another method can contain multiple statements. At this time, {...} cannot be omitted And return:
let foo = (name) => { if(name){ return `I am ${name}` } return 'Front end Nanjiu' } foo('Nanjiu') // I'm Nan Jiu
⚠️ It should be noted here that if the arrow function returns a literal object, you need to wrap the literal object with parentheses
let foo = (name) => ({ name, job: 'front end' }) // Equivalent to let foo2 = function (name) { return { name, job: 'front end' } }
OK, the basic introduction of arrow function. Let's see here first. Let's further understand the arrow function by comparing the difference between arrow function and ordinary function
The difference between arrow function and ordinary function
We can print arrow function and ordinary function to see the difference between them:
let fn = name => { console.log(name) } let fn2 = function(name) { console.log(name) } console.dir(fn) // console.dir(fn2) //
From the printing results, compared with ordinary functions, the arrow function lacks caller, arguments and prototype
Different declaration methods, anonymous functions
- To declare an ordinary function, you need to use the keyword function, and function can be declared as either a named function or an anonymous function
- To declare an arrow function, you only need to use the arrow without using the keyword function, which is more concise than ordinary function declaration.
- Arrow functions can only be declared as anonymous functions, but they can be named through expressions
this points to different
For ordinary functions, the internal this points to the object where the function runs, but this is not true for arrow functions. It does not have its own this object. The internal this is the this in the upper scope at the time of definition. That is to say, the this direction inside the arrow function is fixed. In contrast, the this direction of ordinary functions is variable.
var name = 'Nanjiu' var person = { name: 'nanjiu', say: function() { console.log('say:',this.name) }, say2: () => { console.log('say2:',this.name) } } person.say() // say: nanjiu person.say2() // say2: Nanjiu
The first say here defines an ordinary function, and it is called as the method of the object person, so its this points to person, so it should output say: nanjiu
The second say2 defines an arrow function. We know that the arrow function itself does not have this. Its this always points to the upper scope where it is defined, so this of say2 should point to the global window, so it will output say2: Nanjiu
We can also use the ES5 code generated by Babel to arrow function to prove that the arrow function does not have its own this, but refers to this in the upper scope.
// ES6 function foo() { setTimeout(() => { console.log('id:', this.id); }, 100); } // ES5 function foo() { var _this = this; setTimeout(function () { console.log('id:', _this.id); }, 100); }
The converted ES5 version clearly shows that the arrow function does not have its own this at all, but refers to this in the upper scope.
this of the arrow function will never change, nor will call, apply and bind
We can use call, apply and bind to change the direction of this of ordinary functions, but since the direction of this of the arrow function has been determined when it is defined and always points to this in the upper scope when it is defined, these methods can never change the direction of this of the arrow function.
var name = 'Nanjiu' var person = { name: 'nanjiu', say: function() { console.log('say:',this.name) }, say2: () => { console.log('say2:',this.name) } } person.say.call({name:'Xiao Ming'}) // say: Xiao Ming person.say2.call({name:'Xiao Hong'}) // say2: Nanjiu
It's the same as the above example, but when we call, we use call to try to change the direction of this. The first say is an ordinary function, which is called by call and prints say: Xiaoming, which shows that this of the ordinary function has changed. The second say2 is an arrow function, which is also called by call, but it still prints say2: Nanjiu, This proves that this of the arrow function will never change, even if call, apply and bind are used
The arrow function has no prototype
let fn = name => { console.log(name) } let fn2 = function(name) { console.log(name) } console.log(fn.prototype) // undefined console.dir(fn2.prototype) // {constructor: ƒ}
The arrow function cannot be treated as a constructor
Why can't an arrow function be treated as a constructor? Let's call new first to see what happens:
let fn = name => { console.log(name) } const f = new fn('nanjiu')
The result is in line with our expectation, so the call will report an error
We know that the internal implementation of new is actually divided into the following four steps:
- Create a new empty object
- Link to prototype
- Bind this and execute the constructor
- Return new object
function myNew() { // 1. Create a new empty object let obj = {} // 2. Get constructor let con = arguments.__proto__.constructor // 3. Link prototype obj.__proto__ = con.prototype // 4. Bind this and execute the constructor let res = con.apply(obj, arguments) // 5. Return new object return typeof res === 'object' ? res : obj }
Because the arrow function does not have its own this, its this actually inherits this in the outer execution environment, and the direction of this will never change, and the arrow function has no prototype, so it can't make its instance__ proto__ Property points to, so the arrow function cannot be used as a constructor, otherwise an error will be reported when calling with new!
No new target
New is the command that generates the instance object from the constructor. ES6 introduces a new for the new command Target attribute, which is generally used in the constructor to return the constructor called by new. If the constructor is not through the new command or reflect Called by construct(), new Target will return undefined, so this attribute can be used to determine how the constructor is called.
function fn(name) { console.log('fn:',new.target) } fn('nanjiu') // undefined new fn('nanjiu') /* fn: ƒ fn(name) { console.log('fn:',new.target) } */ let fn2 = (name) => { console.log('fn2',new.target) } fn2('nan') // Error reporting uncaught syntax error: new target expression is not allowed here
⚠️ be careful:
- new. The target attribute is generally used in the constructor, which returns the constructor called by new
- this of the arrow function points to the global object. Use new in the arrow function Target will report an error
- This of the arrow function points to the ordinary function, its new Target is a reference to this ordinary function
The arrow function does not have its own arguments
If the arrow function is in the global scope, there are no arguments
let fn = name => { console.log(arguments) } let fn2 = function(name) { console.log(arguments) } fn2() // Arguments [callee: ƒ, Symbol(Symbol.iterator): ƒ] fn() // Uncaught ReferenceError: arguments is not defined
If we compare the two functions, the ordinary function can print out arguments, and the arrow function will report an error if it uses arguments, because the arrow function itself does not have arguments, and then it will look for arguments in the upper scope. Because there is no defined arguments in the global scope, it will report an error.
The arrow function is in the function scope of the ordinary function, and the arguments is the arguments of the upper ordinary function
let fn2 = function(name) { console.log('fn2:',arguments) let fn = name => { console.log('fn:',arguments) } fn() } fn2('nanjiu')
The arguments printed by the two functions here are the same as those of the fn2 function
You can use the rest parameter instead
ES6 introduces the rest parameter to obtain an indefinite number of parameter arrays of the function. This API is used to replace arguments in the form of Variable name, the variable matched with rest parameter is an array, which puts redundant parameters into the array.
let fn3 = (a,...arr) => { console.log(a,arr) //1, [2,3,4,5,6] } fn3(1,2,3,4,5,6)
The above is the basic usage of the rest parameter. You need to ⚠️ Note that:
- The rest parameter can only be used as the last parameter of the function
// report errors function f(a, ...b, c) { // ... }
- The length attribute of the function, excluding the rest parameter
Comparison between rest parameters and arguments:
- Both arrow functions and ordinary functions can use the rest parameter, while arguments can only be used by ordinary functions
- Accepting the parameter rest is more flexible than arguments
- rest parameter is a real array, while arguments is an array like object, so array method cannot be used directly
Arrow functions cannot have duplicate function parameter names
function fn(name,name) { console.log('fn2:',name) } let fn2 = (name,name) => { console.log('fn',name) } fn('nan','jiu') // 'jiu' fn2('nan','jiu') // report errors
You cannot use the yield command, so the arrow function cannot be used as a Generator function.
This may be due to historical reasons. TC39 discussed it twice in 2013 and 2016 respectively. It chose = > * from * (), * = >, = * >, = > *, and reluctantly entered stage 1. Moreover, because of the asynchronous generator, we have to consider the asynchronous arrow generator at the same time. Before, 99.999% of the purpose of the generator was to use it to realize asynchronous programming, which does not really need the original purpose of the generator. Since the introduction of async/await, the generator generator has become more and more useless. It is speculated that this may be the reason why adding a syntax that is not used frequently may bring greater complexity to the specification, which may not be worth it.
The arrow function is not applicable to the scene
Object method, and this is used in the method
var name = 'Nanjiu' var person = { name: 'nanjiu', say: function() { console.log('say:',this.name) }, say2: () => { console.log('say2:',this.name) } } person.say() // say: nanjiu person.say2() //say2: Nanjiu
In the above code, person The say2 () method is an arrow function that calls person When say2(), make this point to the global object, so you won't get the expected result. This is because the object does not constitute a separate scope, so the scope of the say2 () arrow function is the global scope. say() defines an ordinary function, and its internal this points to the object that calls it, so using an ordinary function is expected.
When the function needs dynamic this
var button = document.querySelector('.btn'); button.addEventListener('click', () => { this.classList.toggle('on'); });
It is obvious that an error will be reported here, because the callback of the button click is an arrow function, and the internal this of the arrow function always points to this in its upper scope, which is window here, so an error will be reported. Here you only need to change the arrow function into an ordinary function to call it normally!
After reading, let's do a question ~
var name = 'Nanjiu' function Person (name) { this.name = name this.foo1 = function () { console.log(this.name) }, this.foo2 = () => console.log(this.name), this.foo3 = function () { return function () { console.log(this.name) } }, this.foo4 = function () { return () => { console.log(this.name) } } } var person1 = new Person('nan') var person2 = new Person('jiu') person1.foo1() // 'nan' person1.foo1.call(person2) // 'jiu' person1.foo2() // 'nan' person1.foo2.call(person2) // 'nan' person1.foo3()() // 'Nanjiu' person1.foo3.call(person2)() // 'Nanjiu' person1.foo3().call(person2) // 'jiu' person1.foo4()() // 'nan' person1.foo4.call(person2)() // 'jiu' person1.foo4().call(person2) // 'nan'
Resolution:
After global code execution, person1 = new Person('nan'),person2 = new Person('jiu'), this in person1 Name is nan, this in person2 After the name is Jiu and OK is clear, continue to look down:
- Execute person1 Foo1(), foo1 is an ordinary function, so this should point to person1 and print nan
- Execute person1 foo1. Call (person2), foo1 is an ordinary function, and the direction of this is changed with call, so this in it should point to person2 and print jiu
- Execute person1 Foo2 (), foo2 is an arrow function, and its this points to the upper scope, that is, person1, so print nan
- Execute person1 foo2. Call (person2). this point of the arrow function cannot be changed by call, so it still points to person1 and prints nan
- Execute person1 Foo3 () (), execute person1 Foo3 (), which returns an ordinary function, and then executes this function. At this time, it is equivalent to executing an ordinary function in the global scope, so its this points to window and prints out Nanjiu
- Execute person1 foo3. Call (person2) () is similar to the above. It returns an ordinary function and then executes. In fact, the previous execution does not need to be concerned. It is also equivalent to executing an ordinary function in the global scope, so its this points to the window and prints out Nanjiu
- Execute person1 foo3(). Call (person2) here is to bind this of the ordinary function returned by foo3 to person2, so it prints jiu
- Execute person1 Foo4 (), execute person1 Foo4 () returns an arrow function, and then executes the arrow function. Since this of the arrow function always points to its upper scope, nan is printed out
- Execute person1 foo4. Call (person2) (), which is similar to the above, but uses call to change this in the upper scope to person2, so it prints jiu
- Execute person1 foo4(). Call (person2). Here, person1 is executed first Foo4(), returns the arrow function, and then tries to change the this direction of the arrow function through call. As we mentioned above, the this of the arrow function always points to its upper scope, so print nan
Recommended reading
- Common security problems and preventive measures at the front end
- Why are gifs used as burial points in front-end monitoring of large factories?
- Introduce reflow & repaint and how to optimize it?
- What is the difference between Promise, Generator and Async?
- Causes and solutions of unreliable execution of JS timer
- From how to use to how to implement a Promise
- Super detailed explanation of page loading process
Original starting address Click here Welcome to the official account "front end South nine".
I'm Nan Jiu. I'll see you next time!!!