📜 ⬆️ ⬇️

Explicit JavaScript features

Image


Reading another article about little-known features of the JavaScript language and quietly writing some irresponsible decisions in the browser console, I often say in my head saying, well, on the fly, then of course everything is wrong !? After all, the language has long acquired a huge community and has a surprisingly wide coverage of industrial development. If so, then why do we often forget about its ability to be understandable for everyone and literally propagandize all these specific and “remembered” constructions? Just make it Obvious!


Reasoning on the topic


You can skip this graphomania.


If we talk about industrial development, in most cases the requirement for the code to be supported is even more important than solving the problem posed by the business. For many, this is obvious, for some - partly (there are of course rare D'Artagnan). The clearer our code, the less risk for it - to get to the dusty shelf, and for us and our successors - to earn problems with the nervous system.


It's no secret that JavaScript is amazing in its flexibility, which is both its greatest virtue and an annoying curse. The way of the JavaScript-developer is long and extremely interesting: we absorb book by book, article by article and we gain unique experience, but in some places it is really language-specific. The widest distribution of the language and at the same time the rich number of accumulated and fed up non-obviousness contribute to the formation of two fronts: those who almost worship the language and those who look at it as an awkward and swinging duck.


And everything would be fine, but often representatives of both fronts work on the same project. And the usual, accepted practice is misunderstanding (unwillingness to understand and even ignoring) each other’s code. And in the case itself, "I was settling for a Java developer, and not this is yours!" . JavaScript followers themselves say that "no one really knows JavaScript!" yes "I can write it in one line on js!" . I confess that I myself am abusing abnormal programming at my leisure ...


You start to feel this problem when you take the place of a marginal and gain some experience with people and their code on both sides of the barricades. Planing and other meetings are more productive when all developers understand each other not only at the level of business parties, but at least a little at the level of their implementation. The notorious bass factor has less impact on the project, when in the case of illness of a single front-end, the rest of the team do not disdain to correct some line of the .js file. The process of sharing knowledge in a team and beyond it becomes more transparent for everyone when everyone has a more detailed picture. Well, all in the same vein.


I don’t urge anyone to “full-stack” or “T-shuffle” (how to say it right now?), But why don't we raise this curtain a little even from the JavaScript community? To do this, you just need to add a little bit more explicitness to our code, using the flexibility of the language, not to show off, but to understand us.


Growing up and taking responsibility


For its part, JavaScript has long recognized its role not as a language for the interactivity of Internet pages and the “gluing together” of their resources, but as a powerful and sufficient tool for creating full-fledged cross-platform and often very scalable applications.


Originally developed for web designers, this “most misunderstood programming language” has been trampling for a long time, despite the rapidly growing popularity and importance. For 13-14 years preceding the edition of ECMAScript 5.1, it is difficult to recall some important changes in the standard or to understand the vector of its development. At that time, the community contributed a lot to the formation of the language ecosystem: Prototype, jQuery, MooTools, and so on. Having received this feedback from the developers, JavaScript has done a lot of work on the bugs: the loud 6-year release of ES6 in 2015 and now the annual ECMAScript releases, thanks to the TC39 revised by the committee the process of introducing new features into the specification.


Well, when our applications became quite large, the prototype OOP model for describing user types ceased to justify itself due to an unusual approach. Well seriously, what is it?


function Animal() { /* Call me via new and I will be the constructor ;) */ } function Rabbit() {} Rabbit.prototype = Object.create(Animal.prototype); Rabbit.prototype.constructor = Rabbit; 

No classes appeared in the language, but their syntax appeared. And the code has become available for adherents of the traditional class-oriented paradigm:


 class Animal { constructor() { /* Obviously, the constructor is here! */ } } class Rabbit extends Animal {} 

Now at the stage of the candidate for release are private class fields . It is hard to believe that sooner or later we will cease to make one another laugh at the agreement on the naming of private properties through the underscore.


At the same time, in a language where a function is an object of the first order and constant eventfulness takes place, it is quite common:


 let that = this; setTimeout(function() { that.n += 1; }, 1000); 

And here begins the explanation about this contexts and closures in JavaScript, which scares every second external developer. But in many cases, the language allows you to avoid unnecessary surprises, obviously using Function.prototype.bind or just like this:


 setTimeout(() => this.n += 1, 1000); 

We also had pointer functions, and these are really functions, not functional interfaces (yes, Java?). Together with an extended set of methods for working with an array, they also help to write the usual declarative pipeline calculations:


 [-1, 2, -3, 4] .filter(x => x > 0) .map(x => Math.pow(2, x)) .reduce((s, x) => s + x, 0); 

Language rightfully considers itself a multi-paradigm. But here is a simple example about the signature of a function:


 function ping(host, count) { count = count || 5; /* send ping to host count times */ } 

At first, passing by will ask a question that the function probably takes only the first argument, and then what the hell is that in this case, the count becomes a boolean !? Indeed, the function has two uses: with and without counting . But this is absolutely not obvious: you have to look into the implementation and understand. Using JSDoc can help you figure it out , but this is not common practice. And here JavaScript went to meet, adding support for not overloading, but at least default parameters:


 function ping(host, count = 5) { /* ... */ } 

Summarizing, JavaScript acquired a huge number of familiar things: generators, iterators, Set collections and Map dictionaries, typed arrays, and even regular expressions began to delight with the support of lookbehind ! Language does everything to be suitable for many things and to become friendly for everyone.


Auspicious way to the obvious


The language itself is certainly well done, and it is difficult to argue with that! But what is wrong with us? Why do we constantly remind the world that JavaScript is somehow not so? Let's look at the examples of some widely used techniques and ask them their feasibility.


Cast


Yes, JavaScript has a dynamic and weak type system and allows you to perform operations on anything, implicitly performing transformations for us. But often, an explicit type conversion is necessary and we can observe the following:


 let bool = !!(expr); let numb = +(expr); let str = ''+(expr); 

These tricks are known to every JavaScript developer, and they are motivated by the fact that they say that you can “quickly” turn something into something: speed here is a short entry. Can it also write false as ! 1 ? If the developer is so worried about the printed characters, then in his favorite IDE, you can easily set up the necessary live template or autocomplete. And if - for the size of the published code, then we always run it through the obfuscator, who knows better than us how to deactivate all this. Why not so:


 let bool = Boolean(expr); let numb = Number(expr); let str = String(expr); 

The result is the same, only understandable to everyone.


For string conversions, we have toString , but for numeric ones there is an interesting valueOf , which can also be overridden. A classic example that introduces the "uninitiated" into a stupor:


 let timestamp = +new Date; 

But after all Date has a known getTime method, let's use it:


 let timestamp = (new Date()).getTime(); 

or finished function:


 let timestamp = Date.now(); 

There is absolutely no reason to exploit implicit type conversions.


Logical operators


Logical operators AND (&&) and OR (||), which in JavaScript are not entirely logical, deserve special attention: they accept and return values ​​of any type. We will not go into the details of the operation of the logical expression evaluator, consider examples. Previously presented option with the function:


 function ping(host, count) { count = count || 5; /* ... */ } 

It may well look like this:


 function ping(host, count) { // OR arguments.length? if (typeof count == 'undefined') { count = 5; } /* ... */ } 

Such a check is also customary, and in some cases can help to avoid mistakes.


This seems rather wild for a developer who initially chose the JavaScript path. But for most of the others, this code is really wild:


 var root = (typeof self == 'object' && self.self === self && self) || (typeof global == 'object' && global.global === global && global); 

Yes, it is compact, and yes, popular libraries can afford it. But please, let's not abuse this, since our code will be read not by the contributors in JavaScript, but by the developers who solve business problems for the allotted time frame.


There may be such a pattern:


 let count = typeof opts == 'object' && opts.count || 5; 

This is definitely shorter than the usual ternary operator, but when reading such a code, first of all you remember the priorities of the operations used.


If we write a predicate function, which is passed to the same Array.prototype.filter , then wrapping the return value in Boolean is a good tone. The purpose of this function becomes obvious immediately and there is no dissonance among developers whose languages ​​have the “correct” logical operators.


Bitwise operations


A common example of checking for an element in an array or a substring in a string is using bitwise NOT (NOT), which is offered even by some textbooks:


 if (~[1, 2, 3].indexOf(1)) { console.log('yes'); } 

What problem does it solve? we don’t have to check ! == -1 , because indexOf will get the index of the element or -1, and the tilde will add 1 and change the sign. Thus, the expression will turn into a "lie" in the case of the index -1.


But avoiding duplication of code can be done differently: to put the check into a separate function of some utils-object, as it is done by all, than to use bitwise operations for other purposes. In lodash, there is an includes function for this, and it does not work through asshole tilde You can rejoice, because in ECMAScript 2016 the method Array.prototype.includes was fixed (there are also lines).


But it was not there! Another tilde (along with XOR) is used to round the number, discarding the decimal part:


 console.log(~~3.14); // 3 console.log(2.72^0); // 2 

But there is a parseInt or Math.floor for this purpose. Bitwise operations here are convenient for speed dialing in the console, as they also have a low priority over the rest of the arithmetic. But on the code review it is better not to miss.


Syntax and language constructs


Some strange practices are difficult to attribute to any particular section. For example, they say that parentheses are optional when calling a constructor and the following two expressions are identical:


 let rabbit = new Rabbit(); let rabbit = new Rabbit; 

And indeed it is! but why create a question from scratch? Not every language can boast such a "feature". And if you still want, then let it be an agreement on the entire project. Otherwise, there is a false feeling that there is some difference.


A similar situation with the declaration of a set of variables. The syntax of the var and let directives allows you to declare (and define) several variables at once, listed separated by commas:


 let count = 5, host, retry = true; 

Someone uses line breaks for readability, but in any case such syntax is not a frequent occurrence in popular languages. No one will give a hand and ask if you write this:


 let count = 5; let retry = true; let host; 

Again, if there is an agreement on a good style at the project / company level, then there are no questions. You just don’t have to overly combine syntax options by mood.


There are specific constructions in the language, such as, for example, IIFE - it allows you to call a function immediately at the place of its definition. The trick is that the parser recognizes a functional expression, not a function declaration. And this can be done in lots of different ways: classically wrapping parentheses, through void or any other unary operator. And this is nothing wonderful! It is necessary to choose the only option and not to depart from it unnecessarily:


 (function() { /* ... */ }()); 

No need to use operators to hack a parser. When a newcomer comes to the project, I want to immerse him in the business logic of the application, and not to give explanations from where all these exclamation marks and voids were spied. There is also a second classic bracket entry and an interesting comment from Crockford about this.


The appearance of class syntax in ES6 was not accompanied by the usual access modifiers. And sometimes the developer wants to pee on classrooms, and observe privacy. Which leads to this code of Frankenstein:


 class Person { constructor(name) { let _name = name; this.getName = function() { return _name; } } toString() { return `Hello, ${this.getName()}`; } } 

That is, accessors are created for the instance in the constructor, and privacy is achieved by accessing the local property variables through the closure. This example looks quite concise, but this is a completely unscalable approach, unless a documented framework solution is built around it. Gentlemen, let's use either the existing classes (and wait for the standardization of private fields) or the popular pattern module. To create some kind of intermediate mix solution here is such as the classes cease to be classes, and the code is intelligible.


To summarize, common sense will be to share the style-guide adopted in the project, the config for the linter, or just code fragments with colleagues who contribute its non-JavaScript component to the project. The language offers several options literally for each typical task, therefore, to improve one another’s understanding and to get under the common denominator is not difficult (or almost).


Misadventure


This topic is of course holivar and examples can be cited much more, but the main message of the article is that non-obviousness in JavaScript should not be misused where this can be avoided. The nature of the language is unique: it allows you to write both elegant and expressive (moderately “stubborn”) solutions, as well as understandable and accessible to all. I fundamentally disagree with the common opinion that JavaScript "punished itself" or "was buried under a pile of good intentions and errors." Because now most of the strangeness is demonstrated not by the language, but by the culture of developers and (not) indifferent that has developed around it.


')

Source: https://habr.com/ru/post/432102/


All Articles