📜 ⬆️ ⬇️

Unknown javascript features

JavaScript is often called the easiest language for newbies, whose programming is the hardest to achieve mastery. The author of the material, the translation of which we publish, says that he cannot but agree with this statement. The thing is, JS is a really old and really flexible language. It is full of mysterious syntax and obsolete features that are still supported by it.

image

Today we will talk about the little-known features of JavaScript and the options for their practical application.

JavaScript is always something new.


I have been working with JavaScript for many years now and I constantly come across something that I didn’t suspect exists. Here I tried to list similar little-known features of the language. In strict mode, some of them will not work, but in normal mode, they are perfectly correct samples of JS-code. It should be noted that I do not undertake to advise readers to take all this into service. Although what I am talking about seems to me to be quite interesting, you, having begun to use all this, if you work in a team, you can, to put it mildly, surprise your colleagues.
')
→ The code that we will discuss here can be found here.

Please note that I did not include such things as raising variables, closures, proxies, prototype inheritance, async / await, generators, and the like. Although these features of the language can be attributed to difficult to understand, they are not little known.

Void operator


JavaScript has a unary void operator. You may have encountered it in the form of void(0) or void 0 . Its only purpose is to evaluate the expression to the right of it and return undefined . 0 used here simply because it is so accepted, although it is not necessary, and any correct expression can be used here. True, this statement will return undefined anyway.

 //  void void 0                  // undefined void (0)                // undefined void 'abc'              // undefined void {}                 // undefined void (1 === 1)          // undefined void (1 !== 1)          // undefined void anyfunction()      // undefined 

Why add a special keyword to the language to return undefined if you can just use the standard value undefined ? Isn't it true that there is some redundancy?

As it turned out, before the appearance of the ES5 standard in most browsers, the standard value undefined could be assigned a new value. Let's say you could successfully execute this command: undefined = "abc" . As a result, the value of undefined might not be what it should be. In those days, the use of void allowed us to provide confidence in the use of this particular undefined .

Brackets when calling constructors are optional


The brackets, which are added after the class name, by calling the constructor, are completely optional (unless the constructor needs to pass arguments).

In the following example, the presence or absence of brackets does not affect the correct operation of the program.

 //     const date = new Date() const month = new Date().getMonth() const myInstance = new MyClass() //     const date = new Date const month = (new Date).getMonth() const myInstance = new MyClass 

Brackets when working with IIFE can not be used


The IIFE syntax has always seemed strange to me. Why are there all these brackets?

As it turned out, brackets are needed only to inform the JavaScript parser that some code is a functional expression, and not an incorrect attempt to declare a function. Knowing this fact allows us to understand that there are many ways to get rid of the brackets that enclose IIFE, and at the same time write working code.

 // IIFE (function () { console.log('Normal IIFE called') })() // Normal IIFE called void function () { console.log('Cool IIFE called') }() // Cool IIFE called 

Here the operator void informs the parser that the code following it is a functional expression. This makes it possible to get rid of the brackets around the function declaration. And, by the way, any unary operator ( void , + , ! , - , and so on) can be used here, and the code will remain working. Isn't that great?

However, if you are an attentive reader, then you may wonder that the unary operator affects the result returned from IIFE. In fact, the way it is. But the good thing is that if you need the result of doing IIFE, which you, for example, save in some variable, then you do not need brackets around IIFE. Here is an example.

 // IIFE,    let result = (function () { // ... -  return 'Victor Sully' })() console.log(result) // Victor Sully let result1 = function () { // ... -  return 'Nathan Drake' }() console.log(result1) // Nathan Drake 

The brackets around the first IIFE only improve the readability of the code without affecting its operation.

If you want to better deal with IIFE - take a look at this material.

Construction with


Do you know that there is a with clause in JavaScript that supports expression blocks? It looks like this:

 with (object)  statement //       with (object) {  statement  statement  ... } 

The with construction adds all the properties of the object passed to it to the chain of scopes used when executing commands.

 //    with const person = { firstname: 'Nathan', lastname: 'Drake', age: 29 } with (person) { console.log(`${firstname} ${lastname} is ${age} years old`) } // Nathan Drake is 29 years old 

It may seem that with is a great tool. It seems that it is even better than the new features of JS for the destructuring of objects , but in fact it is not.

The with design is deprecated and is not recommended to be used. In strict mode, its use is prohibited. It turns out that with blocks cause problems with performance and security.

Function Constructor


Using the function keyword is not the only way to define a new function. Functions can be defined dynamically using the Function constructor and the new operator. Here's what it looks like.

 //  Function const multiply = new Function('x', 'y', 'return x*y') multiply(2,3) // 6 

The last argument passed to the constructor is a string with the function code. The other two arguments are the parameters of the function.

It is interesting to note that the Function constructor is the “parent” of all constructors in JavaScript. Even the Object constructor is the Function constructor. And its own Function constructor is also Function . As a result, a call of the object.constructor.constructor... , executed for any JS object, will return the Function constructor as a result a sufficient number of times.

Function Properties


We all know that functions in JavaScript are first class objects. Therefore, no one bothers to add new properties to the functions. This is perfectly normal, but rarely used like this.

When can this be needed?

In fact, there are several situations in which this feature of functions may be useful. Consider them.

â–Ť Customizable features


Suppose we have a function greet() . We need it to display different greeting messages depending on the regional settings used. These settings can be stored in some variable external to the function. In addition, the function may have a property that defines these settings, in particular, the user's language settings. We will use the second approach.

 //  ,   function greet () { if (greet.locale === 'fr') {   console.log('Bonjour!') } else if (greet.locale === 'es') {   console.log('Hola!') } else {   console.log('Hello!') } } greet() // Hello! greet.locale = 'fr' greet() // Bonjour! 

Functions with static variables


Here is another similar example. Suppose we need to implement a certain generator that produces a sequence of ordered numbers. Usually in such situations, in order to store information about the last generated number, static variable counters are used in classes or IIFE. With this approach, we limit access to the meter and prevent global variables from being contaminated with additional variables.

But what if we need flexibility, if we need to read or even modify the value of such a counter and not clutter up the global scope?

Of course, you can create a class with the corresponding variable and with methods that allow you to work with it. Or you can not bother with such cases and just use the properties of functions.

 //  ,   function generateNumber () { if (!generateNumber.counter) {   generateNumber.counter = 0 } return ++generateNumber.counter } console.log(generateNumber()) // 1 console.log(generateNumber()) // 2 console.log('current counter value: ', generateNumber.counter) // current counter value: 2 generateNumber.counter = 10 console.log('current counter value: ', generateNumber.counter) // current counter value: 10 console.log(generateNumber()) // 11 

Properties of the arguments object


I'm sure most of you know that there is an arguments object in the functions. This is a massive object, accessible within all functions (with the exception of pointer functions that do not have their own arguments object). It contains the list of arguments passed to the function when it is called. In addition, it has some interesting properties:


Consider an example.

 //  callee  caller  arguments const myFunction = function () { console.log('Current function: ', arguments.callee.name) console.log('Invoked by function: ', arguments.callee.caller.name) } void function main () { myFunction() } () // Current function: myFunction // Invoked by function: main 

The ES5 standard prohibits the use of callee and caller properties in strict mode, but they are still widely found in many JavaScript-compiled programs, for example, in libraries. Therefore, it is useful to know about them.

Tagged template literals


Surely you, if you have any relation to programming in JavaScript, have heard of template literals . Pattern literals are one of the many great innovations of the ES6 standard. However, are you aware of tagged pattern literals?

 //    `Hello ${username}!` //    myTag`Hello ${username}!` 

Tagged template literals allow the developer to control how the template literal turns into a string. This is done by using special tags. A tag is just the name of a parser function that receives an array of strings and values ​​interpreted by a string pattern. When using the tag function, it is expected that it will return the finished string.

In the following example, our tag — highlight — interprets the data of the template literal and injects this data into the finished string, placing it in the HTML <mark> tag to highlight it when displaying such text on a web page.

 //    function highlight(strings, ...values) { //  i -      let result = '' strings.forEach((str, i) => {   result += str   if (values[i]) {     result += `<mark>${values[i]}</mark>`   } }) return result } const author = 'Henry Avery' const statement = `I am a man of fortune & I must seek my fortune` const quote = highlight`${author} once said, ${statement}` // <mark>Henry Avery</mark> once said, <mark>I am a man of fortune // & I must seek my fortune</mark> 

Interesting ways to use this feature can be found in many libraries. Here are some examples:


Getters and Setters in ES5 Standard


JavaScript objects are, for the most part, fairly simple. Suppose we have a user object, and we are trying to access its age property using the user.age construction. With this approach, if this property is defined, we get its value, and if not defined - we get undefined . Everything is very simple.

But working with properties should not be so primitive at all. JS objects implement the concept of getters and setters. Instead of directly returning the value of a certain property of an object, we can write our own getter function, which returns what we consider necessary. The same applies to writing new values ​​to properties using setters.

Getters and setters allow you to implement advanced schemes of working with properties. When reading or writing properties, you can use the concepts of virtual fields, you can check the values ​​of the fields, and some useful side effects can occur when writing or reading them.

 //    const user = { firstName: 'Nathan', lastName: 'Drake', // fullname -    get fullName() {   return this.firstName + ' ' + this.lastName }, //      set age(value) {   if (isNaN(value)) throw Error('Age has to be a number')   this._age = Number(value) }, get age() {   return this._age } } console.log(user.fullName) // Nathan Drake user.firstName = 'Francis' console.log(user.fullName) // Francis Drake user.age = '29' console.log(user.age) // 29 // user.age = 'invalid text' // Error: Age has to be a number 

Getters and setters are not new to the ES5 standard. They were always present in the language. ES5 only adds convenient syntax tools to work with them. Details about getters and setters can be read here .

Among the examples of use of getters are the popular Node.js- Colors library.

This library extends the String class and adds many getter methods to it. This allows you to convert a string into its “colored” version in order to use this string later in logging. This is done by working with string properties .

Comma operator


JS has a comma operator. It allows you to write several expressions in one line, separated by a comma, and return the result of the calculation of the last expression. Here is what these designs look like.

 let result = expression1, expression2,... expressionN 

Here the values ​​of all expressions will be calculated, after which the value of expressionN will be in the result variable.

It is possible that you have already used the comma operator in for loops.

 for (var a = 0, b = 10; a <= 10; a++, b--) 

Sometimes this operator is very useful when you need to write several expressions in one line.

 function getNextValue() {   return counter++, console.log(counter), counter } 

It can be useful when constructing small switch functions.

 const getSquare = x => (console.log (x), x * x) 

Operator plus


If you need to quickly turn a string into a number, the plus operator is useful to you. He is able to work with a variety of numbers, not only as it may seem, with positive ones. We are talking about negative, octal, hexadecimal numbers, and numbers in the exponential notation. Moreover, it can convert Date objects and Moment.js library objects into timestamps.

 //  "" +'9.11'          // 9.11 +'-4'            // -4 +'0xFF'          // 255 +true            // 1 +'123e-5'        // 0.00123 +false           // 0 +null            // 0 +'Infinity'      // Infinity +'1,234'         // NaN +new Date      // 1542975502981 ( ) +momentObject    // 1542975502981 ( ) 

Double exclamation mark


It should be noted that what is sometimes called a “double exclamation point operator” (Bang Bang or Double Bang) is not really an operator. This is a “logical NOT” operator, or a logical negation operator, looking like an exclamation point, repeated two times. Double exclamation mark is good because it allows you to convert any expression into a logical value. If the expression, from the point of view of JS, is true, after processing it with a double exclamation mark, it will return true . Otherwise, it will return false .

 //     !!null            // false !!undefined       // false !!false           // false !!true            // true !!""              // false !!"string"        // true !!0               // false !!1               // true !!{}              // true !![]              // true 

Bitwise negation operator


Let's face it: no one cares about bitwise operators. I'm not talking about using them. However, the bit-negative operator can be used in many situations.

When this operator is applied to numbers, it converts them as follows: from the number N get -(N+1) . Such an expression gives 0 if N is -1 .

This possibility can be used with the indexOf() method when it checks for the existence of an element in an array or in a string, since this method does not find the element and returns -1 .

 //      indexOf let username = "Nathan Drake" if (~username.indexOf("Drake")) { console.log('Access denied') } else { console.log('Access granted') } 

Here it should be noted that in the ES6 and ES7 standards, respectively, at strings and arrays, the method includes() . It is definitely much more convenient for determining the presence of elements than using the bitwise negation operator and indexOf() .

Named blocks


In JavaScript, there is a concept of labels, using which you can assign names (labels) to cycles. You can then use these labels to refer to the corresponding loop when you use the break or continue instructions. Labels can also be assigned to regular blocks of code.

Labeled loops are useful when working with nested loops. But they can also be used to conveniently organize code in blocks or when creating blocks, in which code execution can be interrupted.

 //    declarationBlock: { //       //     var i, j } forLoop1: //     - "forLoop1" for (i = 0; i < 3; i++) {       forLoop2: //     -  "forLoop2"  for (j = 0; j < 3; j++) {       if (i === 1 && j === 1) {        continue forLoop1     }     console.log('i = ' + i + ', j = ' + j)  } } /* i = 0, j = 0 i = 0, j = 1 i = 0, j = 2 i = 1, j = 0 i = 2, j = 0 i = 2, j = 1 i = 2, j = 2 */ //      loopBlock4: { console.log('I will print') break loopBlock4 console.log('I will not print') } // I will print 

Note that, unlike some other languages, there is no goto instruction in JS. As a result, labels are used only with the break and continue instructions.

Results


In this article we talked about the little-known features of JavaScript, knowledge of which is useful to any JS programmer, at least, in order to be ready for a meeting with something unusual in someone else's code. If you are interested in the topic “unknown JS” - you can take a look at this our publication.

Dear readers! If you know about some little-known possibilities of JS and see the options for their practical application, ask about them.

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


All Articles