The more you know, the more you don’t know

The introduction

Arrow functions are one of the most interesting and welcome additions to ES6.

This paper will be divided into three parts:

The first part mainly introduces the basic syntax and usage of arrow functions, in which the pointing problem of this will be mainly introduced.

The second part explores the strange phenomenon of arrow functions in self-executing functions.

Part 3 will provide some interview questions to help you understand.

What is an arrow function

As the name suggests, the arrow function is a new syntax for defining functions using (=>), which is a bit different from traditional ES5 functions.

This is a function written in ES5 syntax:

function addTen(num){
  return num + 10;
}
addTen(5); / / 15
Copy the code

With the arrow function in ES6, we can use the arrow function as follows:

var addTen = num= > num + 10

addTen(5); / / 15
Copy the code

The arrow function is much shorter! Because of the implicit return, we can omit the curly braces and return statements.

It is important to understand how arrow functions behave compared to regular ES5 functions.

Characteristics of the arrow function

Shorter syntax

Basic syntax is as follows:

(parameter)=> {statements}Copy the code

Next, let’s break down the various ways arrow functions are written:

When there are no arguments, use a parenthesis to represent the argument part

let f = (a)= > 5;

f(); / / 5
Copy the code

You can omit the parentheses when there is only one argument.

let f = num= > num + 5;

f(10); / / 15
Copy the code

When there are multiple arguments, define multiple arguments in parentheses separated by commas.

let f = (a,b) = > a + b;

f(1.2); / / 3
Copy the code

When there is more than one statement in the block of the arrow function, braces are used and a return statement is used.

// Without braces, the expression result is returned by default
let f1 = (a,b) = > a + b
f1(1.2) / / 3

// Curly braces, no return statement, no return value
let f2 = (a,b) = > {a + b}
f2(1.2) // undefined

// There are braces, there is a return statement, return result
let f3 = (a,b) = > {return a + b}
f3(1.2) / / 3
Copy the code

Because braces are interpreted as blocks of code, if an arrow function returns an object directly, it must enclose braces around the object or an error will be reported.

/ / an error
let f1 = num= > {num:num}

/ / is not an error
let f2 = num= > ({num:num})
Copy the code

Cannot be called by the new keyword

Arrow functions do not have a [[Construct]] method and therefore cannot be used as constructors.

let F = (a)= >{};

TypeError: F is not a constructor
let f = new F();
Copy the code

There is no prototype

Since it cannot be called with the new keyword, there is no need to build a prototype, so the arrow function does not have the prototype attribute.

let F = (a)= >{};
console.log(F.prototype) // undefined
Copy the code

Cannot be used as a Generator function

In arrow functions, yield cannot be used, so arrow functions cannot be used as Generator functions.

No arguments, super, new.target

Arrow functions have no bindings like Arguments, super, and new.target. These values are determined by the nearest layer of non-arrow functions.

Taking arguments as an example, look at the following code:

let f = (a)= >console.log(arguments);

/ / an error
f(); // arguments is not defined
Copy the code

This error is reported because the arrow function f is defined in a global context. Arguments for peripheral non-arrow functions cannot be retrieved for f.

Here’s another example:

function fn(){
    let f = (a)= > console.log(arguments)
    f();
}
fn(1.2.3) / / [1, 2, 3]
Copy the code

The above code, arguments inside the arrow function f, is actually the arguments variable for function fn.

To get a list of arguments of variable length in an arrow function, use the REST argument in ES6:

let f = (. args) = >console.log(args)

f(1.2.3.4.5) / / [1, 2, 3, 4, 5]
Copy the code

No this binding

Before we understand that the arrow function this points to the problem, let’s go back and forth to an example from ES5:

var obj = {
  value:0.fn:function(){
    this.value ++
  }
}
obj.fn();
console.log(obj.value); / / 1
Copy the code

This code is very simple; the expectation is to increment obj.value by 1 every time obj.fn() is called.

Now let’s change the code:

var obj = {
  value:0.fn:function(){
    var f = function(){
      this.value ++
    }
    f();
  }
}
obj.fn();
console.log(obj.value); / / 0
Copy the code

We changed the code to add a function f to the obj.fn method and put the action of adding obj.value to 1 in the function F. However, due to a design error in javascript language, this in function f is not this in method obj.fn, so we cannot get obj.value.

To solve this problem, in ES5 we usually assign this from the outer function to a temporary variable (usually named that, _this, self), which is used to fetch this from the inner function if we want to use this from the outer function. Modify the code as follows:

var obj = {
  value:0.fn:function(){
    // I prefer _this, but many prefer that or self
    var _this = this;
    var f = function(){
      _this.value ++
    }
    f();
  }
}
obj.fn();
console.log(obj.value); / / 1
Copy the code

From this example, we saw how the inner function gets the outer function this in ES5.

Then let’s see if the arrow function has and different this Pointers compared to ES5 functions.

Let’s start with a definition from the INTRODUCTION to the ES6 standard

The this object inside the arrow function is the object at which it is defined, not used.

So, how to understand this sentence?

Let’s try using Babel to convert the following code into ES5 format code and see what it does.

function fn(){
  let f = (a)= >{
    console.log(this)}}Copy the code

Let’s take a look at the transformed result, directly above:

What do we find? We define a temporary variable _this ~ just like we did in ES5 to get the outer function this from the inner function

So what happened to the arrow function’s own this?

The answer is that arrow functions don’t have their own this at all!

So, to sum up, we can translate obscure definitions into vernacular:

  • If the arrow function has a normal function, the arrow function’s this is the normal function’s this
  • If there is no ordinary function outside the arrow function, the arrow function’s this is the global variable

Let’s use a few examples to verify the rules we’ve concluded:

let obj = {
    fn:function(){
        console.log(I'm a normal function..this === obj)
        return (a)= >{
            console.log('I'm the arrow function'.this === obj)
        }
    }
}

console.log(obj.fn()())

// I am a normal function true
// I am the arrow function true
Copy the code

From the above example, we can see that this of the arrow function is equal to this of the outer function.

Look at an example of a multi-layer arrow function nested:

let obj = {
    fn:function(){
        console.log(I'm a normal function..this === obj)
        return (a)= >{
            console.log('First arrow function'.this === obj)
            return (a)= >{
                console.log('Second arrow function'.this === obj)
                return (a)= >{
                    console.log('Third arrow function'.this === obj)
                }
            }
        }
    }
}

console.log(obj.fn()()()())
// I am a normal function true
// The first arrow function is true
// The second arrow function is true
// The third arrow function is true
Copy the code

In this example, we can see that for the arrow function, the arrow function’s this is the same as the outer normal function’s this, regardless of how many layers the arrow function is nested.

Let’s look at another example without the outer function:

let obj = {
    fn:(a)= >{
        console.log(this= = =window); }}console.log(obj.fn())

// true
Copy the code

This example proves that the arrow function’s this is equal to the global object if there are no ordinary functions outside the arrow function.

Note that in the browser environment, the global object is Window, while in the Node environment, the global object is global.

The arrow function meets call, apply, and bind

As you already know, arrow functions don’t have their own “this”, so what happens when arrow functions encounter call, apply, and bind?

We know that call and apply change the direction of this, pass arguments, and execute the function, while bind generates a new function that binds this with preset function arguments.

However, since the arrow function does not have its own this at all, so:

  • When the call or apply method is used on an arrow function, only arguments are passed and the function is called, and the reference to this in the arrow function is not changed.
  • When the bind method is used on arrow functions, only the new function with a default argument is returned, and the new function’s this pointer is not bound.

Let’s verify:

window.name = 'window_name';

let f1 = function(){return this.name}
let f2 = (a)= > this.name

let obj = {name:'obj_name'}

f1.call(obj) // obj_name
f2.call(obj) // window_name

f1.apply(obj) // obj_name
f2.apply(obj) // window_name

f1.bind(obj)() // obj_name
f2.bind(obj)() // window_name
Copy the code

In the code above, we declare the ordinary function f1, the arrow function f2.

The this reference of ordinary functions is dynamically variable, so when you use call, apply, and bind on F1, the this reference inside F1 will change.

The arrow function’s this reference is determined at its definition and never changes, so the context parameters passed in are ignored when using call, apply, bind on F2.

Self-executing function

Before the arrow function in ES6, self-executing functions were written like this:

(function(){
    console.log(1)
})()
Copy the code

Or it could be written like this:

(function(){
    console.log(1)
}())
Copy the code

The arrow function can also be used as a self-executing function, like this:

((a)= > {
    console.log(1)
})()
Copy the code

However, to the surprise of most people, the following statement will return an error:

((a)= > {
    console.log(1)
}())
Copy the code

So, why is the error reported?

This question has puzzled me for a long time until I read the ECMAScript® 2015 specification and learned that arrow functions are a type of AssignmentExpression while function calls are CallExpression. The specification requires that when CallExpression, the expression on the left must be MemberExpression or another CallExpression, while the arrow function does not belong to either of these expressions, so it will report an error at compile time.

This is how it works, as described in the ECMAScript® 2015 specification

On the arrow function

The arrow function is called “this”. The arrow function is called “this”. The arrow function is called “this”.

Subject to a

function foo(n) {
  var f = (a)= > arguments[0] + n;
  return f();
}

let res = foo(2);

console.log(res); // Ask for the output
Copy the code
Answer and analysis

Answer: 4

The arrow function has no arguments of its own, so arguments refer to the arguments object of the function foo. So arguments[0] = 2, n = 2, resulting in 4.

Topic 2

function A() {
  this.foo = 1
}

A.prototype.bar = (a)= > console.log(this.foo)

let a = new A()
a.bar() // Ask for the output
Copy the code
The answer

Answer: undefined

Arrow functions do not have their own this, so this of the arrow function is equivalent to this of the outer non-arrow function scope. Since there are no ordinary functions around the arrow function, this in the arrow function is equivalent to the global object, so the output is undefined.

The title three

let res = (function pt() {
  return (() = > this.x).bind({ x: 'inner'}) (); }).call({x: 'outer' });

console.log(res)  // Ask for the output
Copy the code
The answer

The answer: ‘outer’

This is a little bit more complicated. Find the output of res.

Analysis is as follows:

  1. Find the return value of the pt function called by call.
  2. This in the pt function is converted to {x:’outer’} by call.
  3. In the pt function, the arrow function generates a new function through bind and executes it. The result is the return value of the pt function.
  4. The arrow function’s this cannot be bound by the bind method; the arrow function’s this is the outer scope’s this.
  5. When the arrow function executes, the this of the outer scope is {x:’outer’} specified by the call method.
  6. The final result is ‘outer’.

The title four

window.name = 'window_name';

let obj1 = {
    name:'obj1_name'.print:(a)= >console.log(this.name)
}

let obj2 = {name:'obj2_name'}

obj1.print()  // Ask for the output
obj1.print.call(obj2)  // Ask for the output
Copy the code
The answer

Answer: ‘window_name’ ‘window_name’

Arrow functions do not have their own this, and there is no way to change this in arrow functions through call, apply, or bind. The arrow function’s this depends on whether there are ordinary functions in the outer layer, which refer to this in the ordinary function, and there are no ordinary functions in the outer layer, which is the global object.

In this case, there is no normal function outside the arrow function, so this points to the global object, so the result is ‘window_name’, ‘window_name’.

Topic five

let obj1 = {
    name:'obj1_name'.print:function(){
        return (a)= >console.log(this.name)
    }
}

let obj2 = {name:'obj2_name'}


obj1.print()() // Ask for the output
obj1.print().call(obj2) // Ask for the output
obj1.print.call(obj2)() // Ask for the output
Copy the code
The answer

Answer: ‘obj1_name’ ‘obj1_name’ ‘obj2_name’

The arrow function’s this is the same as the normal function’s this, regardless of call, apply, or bind.

In this case, obj1.print returns an arrow function in which this is the same as this when obj1.print is called.

  1. Obj1.print ()() : this in obj1.print is obj1, so the output is obj1_name
  2. Obj1.print ().call(obj2) : this in obj1.print is obj1, so output is obj1_name
  3. Obj1.print.call (obj2)() : this in obj1.print is obj2, so output is obj2_name

reference

  • Understand ES6 in depth
  • Introduction to ES6 standards
  • Arrow function and this keyword
  • Rerecognize the arrow function this
  • ECMAScript ® 2015 specification

Write in the last

  • If there are any mistakes in this article, please correct them in the comments section. If this article helped you, pleasegive a likeandFocus on
  • This article was published simultaneously withgithub, can be ingithubFind more articles, welcomeWatch & Star u
  • See the following article: Plan

Welcome to pay attention to the wechat public number [front-end small black house], every week 1-3 high-quality articles push, help you to progress