It has 2,670 words and takes five minutes to read. Compiled from Dmitri Pavlutin’s article with simplified content and code style optimization. Arrow functions introduced in ES6 allow us to write simpler code, but there are some scenarios where using arrow functions can cause serious problems. What are some scenarios? What problems do they cause? Let me tell you what I’m gonna do.

It’s exciting to watch the evolution of a programming language you use every day. Learning from mistakes, exploring better implementations, and creating new features is what drives iterations of a programming language version. The changes in JS in recent years are the best example, represented by ES6’s introduction of Arrow functions, class and other features, pushing the ease of use of JS to a new level.

There are plenty of articles on the web explaining arrow functions and syntax in ES6, so if you’re new to ES6, this is a good place to start. Every coin has two sides, and new features of the language are often misunderstood and abused, such as the use of arrow functions. Next, I describe scenarios where the use of arrow functions is avoided and how function expressions, function declarations, or method abbreviations can be used to ensure code correctness and readability in these scenarios.

1. Define object methods

Object methods in JS are defined by defining a property on the object that points to the function. When a method is called, this in the method refers to the object that the method belongs to.

1.1 Define literal methods

Because the arrow function syntax is concise, many students may be tempted to use it to define literal methods, such as the following example JS Bin:

const calculator = { array: [1, 2, 3], sum: () => { console.log(this === window); // => true return this.array.reduce((result, item) => result + item); }}; console.log(this === window); // => true // Throws "TypeError: Cannot read property 'reduce' of undefined" calculator.sum();Copy the code

Calculator. sum is defined using the arrow function, but TypeError is raised when called, because this.array is undefined at runtime. When calculator.sum is called, this in the execution context still points to window. The reason is that the arrow function binds the function context to the window. This. array is equivalent to window.array, which is clearly undefined.

The solution is to use function expressions or method abbreviations (already supported in ES6) to define methods. This ensures that this is determined at runtime by the context in which it is contained.

const calculator = { array: [1, 2, 3], sum() { console.log(this === calculator); // => true return this.array.reduce((result, item) => result + item); }}; calculator.sum(); / / = > 6Copy the code

Calculator. Sum becomes a normal function, and when it is executed, this points to the Calculator object, which naturally gives the calculator the correct result.

1.2 Define the prototype approach

The same rule applies to prototype method definitions. Using arrow functions results in runtime execution context errors, as in the following example JS Bin:

function Cat(name) {
    this.name = name;
}

Cat.prototype.sayCatName = () => {
    console.log(this === window); // => true
    return this.name;
};

const cat = new Cat('Mew');
cat.sayCatName(); // => undefinedCopy the code

Use traditional function expressions to solve the problem. JS Bin:

function Cat(name) {
    this.name = name;
}

Cat.prototype.sayCatName = function () {
    console.log(this === cat); // => true
    return this.name;
};

const cat = new Cat('Mew');
cat.sayCatName(); // => 'Mew'Copy the code

When sayCatName becomes a normal function, the execution context of the call points to the newly created cat instance.

2. Define the event callback function

This is a very powerful feature in JS, can change the function in a number of ways the execution context, JS internal also has several different default context, but the universal rule is on who calls this function to who, this code to understand naturally, like to read in said, something is going on in an object.

However, the arrow function is bound to the execution context when it is declared, so it is impossible to change the context dynamically, and its disadvantages become obvious when dynamic context is required. For example, the DOM event listenner binding is common in client programming. When the callback function is triggered, this points to the DOM node where the event is currently occurring, while the dynamic context is very useful in this case. For example, the following code attempts to use the arrow function as an event callback JS Bin:

const button = document.getElementById('myButton');
button.addEventListener('click', () => {
    console.log(this === window); // => true
    this.innerHTML = 'Clicked button';
});Copy the code

The arrow function defined in the global context will execute this pointing to the window. When a click event occurs, the browser will attempt to execute the event callback using button context, but the predefined context of the arrow function cannot be modified. This. InnerHTML is equivalent to window.innerhtml, which has no meaning.

Use function expressions to dynamically change this at runtime.

const button = document.getElementById('myButton');
button.addEventListener('click', function() {
    console.log(this === button); // => true
    this.innerHTML = 'Clicked button';
});Copy the code

When the user clicks on the button, the this in the event callback actually points to the button, so this.innerHTML = ‘Clicked button’ can modify the text in the button as expected.

3. Define the constructor

Constructor this refers to the newly created object, and when new Car() is executed, the context of constructor Car is the newly created object, that is, this instanceof Car === true. Obviously, the arrow function cannot be used as a constructor. In fact, JS forbids you from doing so, and if you do, it will throw an exception.

In other words, the execution of the arrow constructor is meaningless and ambiguous. For example, when we run the following code JS Bin:

const Message = (text) => { this.text = text; }; // Throws "TypeError: Message is not a constructor" const helloMessage = new Message('Hello World! ');Copy the code

When constructing a new Message instance, the JS engine throws an error because Message is not a constructor. In my opinion, ES6 gives a concrete error message when something goes wrong, which is good practice compared to the old JS engine’s design of quietly failing when something goes wrong. We can use function expressions or function declarations to declare constructors to fix the above example.

const Message = function(text) { this.text = text; }; const helloMessage = new Message('Hello World! '); console.log(helloMessage.text); // => 'Hello World! 'Copy the code

4. Go for too short code

Arrow functions allow you to omit parentheses around arguments, curly braces in function bodies, and even return keywords, which can be very helpful for writing shorter code. This reminds me of an interesting assignment my computer science teacher gave my students in college: see who can write the shortest function to calculate the length of a string in C. It’s a great way to learn and explore new language features. However, in real software engineering, the code will be read by many engineers after it is written. Truly write once, read many times. In terms of code readability, the shortest code may not always be the best. To some extent, short code with too much compressed logic is less intuitive to read, as in the following example JS Bin:

const multiply = (a, b) => b === undefined ? b => a * b : a * b; const double = multiply(2); double(3); // => 6 multiply(2, 3); / / = > 6Copy the code

The multiply function returns the product of two numbers or a fixed one-parameter function that can be continued. The code looks short, but most people may not immediately understand what it does at first glance. How can you make it more readable? There are many ways to do this. You can add parentheses, conditional judgments, return statements to arrow functions, or use the ordinary JS Bin function:

function multiply(a, b) { if (b === undefined) { return function (b) { return a * b; } } return a * b; } const double = multiply(2); double(3); // => 6 multiply(2, 3); / / = > 6Copy the code

To make code more readable, it is necessary to strike a balance between brevity and verbose.

5. To summarize

Arrow functions are definitely a major improvement in ES6. Using arrow functions in the right context can make code shorter and cleaner, but some advantages can become disadvantages in other ways. You need to be careful when using arrow functions in scenarios that require dynamic context. Define object methods, define prototype methods, define constructors, and define event callback functions.

One More Thing

The author of this article is Wang Shijun. For commercial reprint, please contact the author for authorization. For non-commercial reprint, please indicate the source. If you found this article helpful, please give it a thumbs up! If you have any questions about the content of this article, please leave a comment. Want to know what I’ll write next? Please subscribe to my Nuggets column.