In 2022, don't you know the difference between arrow function and ordinary function?

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

Original starting address Click here Welcome to the official account "front end South nine".

I'm Nan Jiu. I'll see you next time!!!

Keywords: Javascript Front-end Interview

Added by 244863 on Tue, 01 Mar 2022 04:44:53 +0200