In June 2018, ECMAScript 2015 (
ES6 ) celebrated its three-year anniversary. In ES6, firstly, many new features of JavaScript appeared, and secondly, a new era of language development begins with this standard. In addition, it was the last large-scale release of JS, as now the TC39 uses a scheme for issuing small annual releases of the standard, rather than introducing its new edition every few years.

The last 4 years of ES6, quite justifiably, has attracted widespread attention. The author of the material, the translation of which we are publishing today, says that he, all this time, thanks to
Babel , wrote all the code using the modern version of the JS specifications. He believes that enough time has passed to critically analyze the new features of ES6. In particular, he is interested in what he used for some time, and then he stopped using it because it worsened his code.
About JS weaknesses
Douglas Crockford, in his
book “JavaScript: Strengths,” wrote about what can be considered weaknesses of a language. This is something which, in his opinion, is not worth using. Fortunately, among the innovations of ES6, there is nothing as unsightly as some of the old problematic features of JS, such as the lax equality operator that performs implicit type conversion, the
eval()
function and the
with
instruction. The new features of ES6 are much better designed. However, there are some things in it that I avoid. Those features that are on my list of JS "weaknesses" fell into this list for the following reasons:
')
- They are, in fact, "traps". That is, it seems that they are intended to perform certain actions, and in most cases, work as expected. However, sometimes they behave unexpectedly, which can easily lead to errors.
- They increase the volume of the language in exchange for a small benefit. Such features give the developer some small advantages, but require that someone who tries to figure out his code, knowledge of certain mechanisms, usually hidden somewhere. This is doubly true for API capabilities, when using such a feature means that other code that interacts with code written by some developer must be aware of the use of this API feature.
Now, guided by these considerations, let's talk about the weaknesses of ES6.
Keyword const
Before the release of ES6, variables in JavaScript could be declared using the
var
keyword. In addition, the variables could not be declared at all, then they, even if used in functions, fall into the global scope. The role of variables can be played by the properties of objects, and functions are declared using the
function
keyword. The
var
keyword has certain features.
So, it allows you to create variables that are added to a global object, or those whose scope is limited to functions. However, the
var
keyword pays no attention to blocks of code. In addition, it is possible to refer to a variable declared using the
var
keyword in the code located before the declaration command. This phenomenon is known as elevating variables. These features, if not taken into account, can lead to errors. In order to rectify the situation, two new keywords for declaring variables appeared in ES6:
let
and
const
. They solved the basic problems of
var
. Namely, we are talking about the fact that variables declared using these keywords have a block scope, as a result, for example, a variable declared in a loop is not visible outside of it. In addition, the use of
let
and
const
does not allow access to variables before they are declared. This will result in a
ReferenceError
error. It was a big step forward. However, the emergence of two new keywords, as well as their features, led to additional confusion.
The value of a variable (constant) declared with the
const
keyword cannot be overwritten after the declaration. This is the only difference between
const
and
let
. This new feature looks useful, and it can really bring some benefits. The problem lies in the very keyword
const
. The way constants, declared with its help, behave does not correspond to what most developers associate with the notion of "constant".
const CONSTANT = 123; // "TypeError: invalid assignment to const `CONSTANT`" CONSTANT = 345; const CONSTANT_ARR = [] CONSTANT_ARR.push(1) // [1] - console.log(CONSTANT_ARR)
Using the
const
keyword prevents a new value from being written to a constant, but does not make the objects referenced by such constants immune. This feature provides poor protection against changing values ​​when working with most data types. As a result, due to the fact that the use of
const
can be confusing, and due to the fact that if the keyword
let
present, the presence of
const
looks redundant, I decided to always use
let
.
Tagged pattern strings
The
const
keyword is an example of how a specification creates too many ways to solve too few tasks. In the case of tagged patterned strings, we have the opposite situation. The syntax of such strings was considered by the TC39 committee as a way to solve problems of string interpolation and working with multi-line strings. Then they decided to expand this opportunity by using macros.
If you have not previously encountered tagged patterned strings, note that they are a bit like string
decorators . Here is an example of working with them with
MDN :
var person = 'Mike'; var age = 28; function myTag(strings, personExp, ageExp) { var str0 = strings[0]; // "that " var str1 = strings[1]; // " is a " // ( ) // , // , . // var str2 = strings[2]; var ageStr; if (ageExp > 99){ ageStr = 'centenarian'; } else { ageStr = 'youngster'; } return str0 + personExp + str1 + ageStr; } var output = myTag`that ${ person } is a ${ age }`; console.log(output); // that Mike is a youngster
Tagged pattern strings cannot be called completely useless. Here is an
overview of some of their uses. For example, they are useful when cleaning up HTML code. And, at the moment, their use demonstrates the most accurate approach in situations where you need to perform the same operation on all the input data of an arbitrary string pattern. However, you need this relatively rarely, you can do the same with the appropriate API (although this solution is longer). And, for most tasks, using the API will be no worse than using tagged template strings. This feature does not add new features to the language. It adds to it new approaches to working with data, which should be familiar to those who have to read the code written using tagged template strings. And I want my code to remain as clean and clear as possible.
Overcomplicated restructuring assignment expressions
Some features of the language look great when used to solve simple tasks; however, when tasks become more complex, these features can get out of control. For example, I like the ternary conditional operator:
let conferenceCost = isStudent ? 50 : 200
However, the code written with its help becomes difficult to understand if, using such an operator, you start using nested constructs:
let conferenceCost = isStudent ? hasDiscountCode ? 25 : 50 : hasDiscountCode ? 100 : 200;
The same can be said about destructive assignment. This mechanism allows you to pull out the values ​​of variables from objects or arrays:
let {a} = {a: 2, b: 3}; let [b] = [4, 5]; console.log(a, b)
In addition, when using it, you can rename variables, get embedded values, set default values:
let {a: val1} = {a: 2, b: 3}; let [{b}] = [{a:3, b:4} , {c: 5, d: 6}]; let {c=6} = {a: 2, c: 5}; let {d=6} = {a: 2, c: 5}; console.log(val1, b, c, d)
All this is wonderful - until the matter reaches the construction of complex expressions using all these possibilities. For example, in the expression below, 4 variables are declared:
userName
,
eventType
,
eventDate
, and
eventId
. Their values ​​are taken from different places in the
eventRecord
object
eventRecord
.
let eventRecord = { user: { name: "Ben M", email: "ben@m.com" }, event: "logged in", metadata: { date: "10-10-2017" }, id: "123" }; let { user: { name: userName = "Unknown" }, event: eventType = "Unknown Event", metadata: [date: eventDate], id: eventId } = obj;
To understand this code is almost impossible. This problem can be solved with the help of a much more readable code, if we use several restructuring operations or refuse them altogether.
let eventRecord = { user: { name: "Ben M", email: "ben@m.com" }, event: "logged in", metadata: { date: "10-10-2017" }, id: "123" }; let userName = eventRecord.user.userName || 'Unknown'; let eventDate = eventRecord.metadata.date; let {event:eventType='UnknownEvent', id:eventId} = eventRecord;
I do not have a clear guideline indicating that the expression of destructuring assignment needs to be processed. However, every time I look at a similar expression and cannot instantly understand what task it solves, what variables are used in it, I understand that it is time to simplify the code in order to improve its readability.
Default Export
ES6 has one nice feature. It lies in the way its developers approached the standardization of what was previously done with the help of various libraries, often competing with each other. So in the specification appeared classes, promises, modules. This is all that the JS developer community used before ES6, finding it in third-party libraries. For example, the ES6 modules are a great substitute for what resulted in the AMD / CommonJS format war, and provide a convenient syntax for importing.
ES6 modules support two basic ways to export values: named export (named export) and default export, or default export (default export):
const mainValue = 'This is the default export export default mainValue export const secondaryValue = 'This is a secondary value; export const secondaryValue2 = 'This is another secondary value;
A module can use several named export commands, but only one default export command. When importing what is exported using the default export command, you can give what was exported by default in the import file any name, since no name search is performed during the execution of this search operation. When using named export, you need to use the variable names from the export file, although renaming is also possible.
// import renamedMainValue from './the-above-example'; // import {secondaryValue} from './the-above-example'; // import {secondaryValue as otherValue} from './the-above-example';
The default export enjoyed
special attention from the developers of the ES6 standard, and they intentionally created a simpler syntax for it. However, in practice, I managed to find out that using the technology of named exports is preferable for the following reasons.
- When using named export, the names of exported variables, by default, correspond to the names of imported variables, which simplifies their search for those who do not use intelligent development tools.
- When using named export, programmers using intelligent development tools get such convenient features as automatic import .
- Named export makes it possible to consistently export anything from modules in the right quantities. Default export restricts the developer to only exporting a single value. As a workaround, you can apply the export of an object with several properties. However, this approach loses the value of the tree-shaking algorithm used to reduce the size of JS applications collected by something like a webpack. Using only modules with named exports simplifies the work.
In general, it can be noted that the naming of entities is a good practice, since it allows you to uniquely identify them both in code and in conversations about this code. That is why I use named export.
Results
You have just learned about the possibilities of ES6, which, according to the author of this material, are unsuccessful. Perhaps you will join this opinion, perhaps - no. Any programming language is a complex system, the possibilities of which can be viewed from different points of view. However, we hope that this article will be useful to all those who seek to write clear and high-quality code.
Dear readers! Is there something in modern JavaScript that you are trying to avoid?
