JavaScript ES6: weaknesses
- Transfer
In June 2018, ECMAScript 2015 ( ES6 ) celebrated its three-year anniversary. In ES6, firstly, a lot of 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 the release scheme for small annual releases of the standard, rather than bringing it out a 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.
Douglas Crockford, in his book “JavaScript: Strengths,” also 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 unattractive as some of the old problematic features of JS, such as the lax equality operator, which performs implicit type conversion, function,
Now, guided by these considerations, let's talk about the weaknesses of ES6.
Before the release of ES6, variables in JavaScript could be declared using a keyword
So, it allows you to create variables that are added to a global object, or those whose scope is limited to functions. However, the keyword
The value of a variable (constant) declared using a keyword
The use of a keyword
A keyword
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 :
Tagged pattern strings cannot be called completely useless. Here is a reviewsome options for their use. 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.
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:
However, the code written with its help becomes difficult to understand if, using such an operator, you start using nested constructs:
The same can be said about destructive assignment. This mechanism allows you to pull out the values of variables from objects or arrays:
In addition, when using it, you can rename variables, get nested values, set default values:
All this is wonderful - until the matter reaches the construction of complex expressions using all these possibilities. For example, the 4 variables declared in the terms mentioned below:
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.
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 it uses, I understand that it is time to simplify the code in order to improve its readability.
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):
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 importing 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.
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.
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.
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?
, 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,” also 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 unattractive as some of the old problematic features of JS, such as the lax equality operator, which performs implicit type conversion, function,
eval()
and instruction with
. 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" were included in this list for the following reasons:- They are, in fact, "traps". That is, it seems that they are designed 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 they require that those who try to understand their code know about 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 a keyword
var
. 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 a keyword function
. The keyword var
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 keyword
var
pays no attention to code blocks. Also refer to a variable declared using the keyword.var
it is also possible in the code located before the command of its announcement. 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 appeared in ES6 for declaring variables: let
and const
. They solved the main problems 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 let
and const
does not allow access to variables prior to their declaration. This will lead to an errorReferenceError
. 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 using a keyword
const
cannot be rewritten 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 is the most key word 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)
The use of a keyword
const
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 const
may lead to confusion, and due to the fact that if the presence of the keyword let
presence const
looks redundant, I decided to always use let
.Tagged pattern strings
A keyword
const
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;
functionmyTag(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 a reviewsome options for their use. 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 destructive 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) // 2, 4
In addition, when using it, you can rename variables, get nested 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) // 2, 4, 5, 6
All this is wonderful - until the matter reaches the construction of complex expressions using all these possibilities. For example, the 4 variables declared in the terms mentioned below:
userName
, eventType
, eventDate
, and eventId
. Their values are taken from different places of the structure of the 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 it uses, 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 = 'Thisis the default export
export default mainValue
export const secondaryValue = 'Thisis a secondary value;
export const secondaryValue2 = 'Thisis 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 importing 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 exports, programmers using intelligent development tools get convenient features such as automatic imports .
- 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?