The material, the first part of the translation of which we are publishing today, is devoted to the new standard JavaScript features that were discussed at the
Google I / O 2019 conference. In particular, here we will talk about regular expressions, about class fields, about working with strings.
Regular Expression Checks
Regular expressions (Regular Expression, for short - RegEx or RegExp) is a powerful string processing technology that is implemented in many programming languages. Regular expressions are very useful in cases where you need, for example, to search for fragments of strings by complex patterns. Until recently, the JavaScript implementation of regular expressions had everything except lookbackhinds.
In order to understand what a retrospective check is, let's talk about lookaheads that are already supported in JavaScript.
→
The second part▍ Advance check
The syntax of leading checks in regular expressions allows you to search for fragments of strings when it is known that other fragments are to the right of them. For example, when working with the string
MangoJuice, VanillaShake, GrapeJuice
you can use the syntax of positive leading check to find the words immediately followed by the word
Juice
. In our case, these are the words
Mango
and
Grape
.
There are two types of leading checks. These are positive lookaheads and negative lookaheads.
Positive lead check
A positive leading check is used to search for lines to the right of which are other, previously known lines. Here's what the regular expression syntax used for this check looks like:
/[a-zA-Z]+(?=Juice)/
This template allows you to select words consisting of lowercase or uppercase letters, followed by the word
Juice
. Do not confuse structures describing leading and retrospective checks with capture groups. Although the conditions of these checks are written in parentheses, the system does not capture them. Let's look at an example of a positive lead check.
const testString = "MangoJuice, VanillaShake, GrapeJuice"; const testRegExp = /[a-zA-Z]+(?=Juice)/g; const matches = testString.match( testRegExp ); console.log( matches );
Negative Lead Check
If we consider, using the above line, the mechanism of action of negative leading checks, it turns out that they allow you to find words to the right of which there is no word
Juice
. The syntax of negative leading checks is similar to the syntax of positive checks. However, there is one feature in it, which is that the symbol
=
(equal) changes to a symbol
!
(exclamation mark). Here's what it looks like:
/[a-zA-Z]+(?!Juice)/
This regular expression allows you to select all words to the right of which there is no word
Juice
. But when applying such a template, all the words in the line will be selected (
MangoJuice, VanillaShake, GrapeJuice
). The fact is that, according to the system, not a single word here ends with
Juice
. As a result, in order to achieve the desired result, you need to clarify the regular expression and rewrite it like this:
/(Mango|Vanilla|Grape)(?!Juice)/
Using this template allows you to select the words
Mango
, or
Vanilla
, or
Grape
, after which there is no word
Juice
. Here is an example:
const testString = "MangoJuice, VanillaShake, GrapeJuice"; const testRegExp = /(Mango|Vanilla|Grape)(?!Juice)/g; const matches = testString.match( testRegExp ); console.log( matches );
▍ Retrospective Check
By analogy with the syntax of leading checks, the syntax of retrospective checks allows you to select sequences of characters only if to the left of these sequences is a certain specified pattern. For example, when processing the string
FrozenBananas, DriedApples, FrozenFish
we can use a positive retrospective check to find words to the left of which there is the word
Frozen
. In our case, the words
Bananas
and
Fish
correspond to this condition.
There are, as is the case with leading checks, positive retrospective checks (positive lookbehind) and negative retrospective checks (negative or negating lookbehind).
Positive retrospective review
Positive retrospective checks are used to search for patterns to the left of which there are other patterns. Here is an example of the syntax used to describe such checks:
/(?<=Frozen)[a-zA-Z]+/
The symbol
<
used here, which was not in the description of leading checks. In addition, the condition in the regular expression is located not to the right of the template of interest to us, but to the left. Using the above template, you can select all words starting with
Frozen
. Consider an example:
const testString = "FrozenBananas, DriedApples, FrozenFish"; const testRegExp = /(?<=Frozen)[a-zA-Z]+/g; const matches = testString.match( testRegExp ); console.log( matches );
Negative Retrospective Check
The mechanism of negative retrospective checks allows you to search for patterns in the lines to the left of which there is no specified pattern. For example, if you need to select words that do not start with
Frozen
in the
FrozenBananas, DriedApples, FrozenFish
line, you can try using this regular expression:
/(?<!Frozen)[a-zA-Z]+/
But, since using this construction will lead to the selection of all words from the string, since none of them starts with
Frozen
, the regular expression needs to be clarified:
/(?<!Frozen)(Bananas|Apples|Fish)/
Here is an example:
const testString = "FrozenBananas, DriedApples, FrozenFish"; const testRegExp = /(?<!Frozen)(Bananas|Apples|Fish)/g; const matches = testString.match( testRegExp ); console.log( matches );
→ Support
This and other similar sections will provide information on the stage of harmonizing the described features of JS in Technical Committee 39 (Technical Committee 39, TC39), which is responsible at ECMA International for supporting the ECMAScript specifications. In such sections, data on versions of Chrome and Node.js (and sometimes on the version of Firefox) will also be given, starting with which you can use the corresponding features.
Class fields
A class field is a new syntax construct used to define the properties of class instances (objects) outside the class constructor. There are two types of class fields: public class fields and private class fields.
▍Public class fields
Until recently, the properties of objects had to be defined inside the class constructor. These properties were public (public). This means that they can be accessed while working with an instance of the class (object). Here is an example of declaring a public property:
class Dog { constructor() { this.name = 'Tommy'; } }
When it was necessary to create a class that would extend a certain parent class, it was necessary to call
super()
in the constructor of the child class. This had to be done before its own properties could be added to the child class. Here's what it looks like:
class Animal {} class Dog extends Animal { constructor() { super();
Thanks to the appearance of the syntax of public fields of a class, it is possible to describe the fields of a class outside the constructor. The system will make an implicit call to
super()
.
class Animal {} class Dog extends Animal { sound = 'Woof! Woof!';
When implicitly calling
super()
it passes all the arguments provided by the user when creating the class instance (this is the standard JavaScript behavior, there is nothing special about private class fields). If the constructor of the parent class needs arguments prepared in a special way, you need to call
super()
yourself. Let's take a look at the results of the implicit constructor call of the parent class when creating an instance of the child class.
class Animal { constructor( ...args ) { console.log( 'Animal args:', args ); } } class Dog extends Animal { sound = 'Woof! Woof!';
▍ Private class fields
As you know, in JavaScript there are no access modifiers for class fields like
public
,
private
or
protected
. All properties of objects are public by default. This means that access to them is unlimited. The closest thing to making a property of an object similar to a private property is to use the
Symbol
data type. This allows you to hide the properties of objects from the outside world. You may have used property names prefixed with an
_
(underscore) to indicate that the corresponding properties should be considered intended only for use within the object. However, this is just a notification for those who will use the object. This does not solve the problem of real restriction of access to properties.
Thanks to the mechanism of private fields of classes, it is possible to make the class properties accessible only inside this class. This leads to the fact that they can not be accessed from the outside and working with an instance of the class (object). Take the previous example and try to access the property of the class from the outside, when the
_
prefix was used.
class Dog { _sound = 'Woof! Woof!';
As you can see, using the
_
prefix does not solve our problem. Private fields of classes can be declared in the same way as public fields, but instead of a prefix in the form of an underscore, you must add a prefix in the form of a pound sign (
#
) to their names. Attempting unauthorized access to the object’s private property declared in this way will result in the following error:
SyntaxError: Undefined private field
Here is an example:
class Dog { #sound = 'Woof! Woof!';
Note that private properties can only be accessed from the class in which they are declared. As a result, descendant classes cannot directly use similar properties of the parent class.
Private (and public) fields can be declared without writing certain values into them:
class Dog { #name; constructor( name ) { this.#name = name; } showName() { console.log( this.#name ); } }
→ Support
String Method .matchAll ()
The
String
data type prototype has a
.match()
method that returns an array of string fragments that match the condition specified by the regular expression. Here is an example using this method:
const colors = "#EEE, #CCC, #FAFAFA, #F00, #000"; const matchColorRegExp = /([A-Z0-9]+)/g; console.log( colors.match( matchColorRegExp ) );
When using this method, however, no additional information is given (like indexes) about the found fragments of the string. If you remove the
g
flag from the regular expression passed to the
.match()
method, it will return an array that will contain additional information about the search results. True, with this approach, only the first fragment of the string matching the regular expression will be found.
const colors = "#EEE, #CCC, #FAFAFA, #F00, #000"; const matchColorRegExp = /#([A-Z0-9]+)/; console.log( colors.match( matchColorRegExp ) );
In order to get something similar, but for several fragments of a string, you will have to use the regular expression method
.exec()
. The constructions that are needed for this are more complicated than the one in which a single string method would be used to obtain similar results. In particular, here we need a
while
, which will execute until
.exec()
returns
null
. Using this approach, keep in mind that
.exec()
does not return an iterator.
const colors = "#EEE, #CCC, #FAFAFA, #F00, #000"; const matchColorRegExp = /#([A-Z0-9]+)/g;
In order to solve such problems, we can now use the string method
.matchAll()
, which returns an iterator. Each call to the
.next()
method of this iterator
.next()
next element from the search results. As a result, the above example can be rewritten as follows:
const colors = "#EEE, #CCC, #FAFAFA, #F00, #000"; const matchColorRegExp = /#([A-Z0-9]+)/g; console.log( ...colors.matchAll( matchColorRegExp ) );
→ Support
Named Groups in Regular Expressions
The concept of groups in the JavaScript implementation of regular expression mechanisms is slightly different from the implementation of a similar concept in other languages. Namely, when, using JavaScript, the RegEx-template is placed in parentheses (except when parentheses are used for retrospective or advanced checks), the template becomes a group.
The fragments of the string captured by the group will be reflected in the results of applying the regular expression.
In the previous example, you could see that the first element of the array with the search results is that which matches the entire regular expression, and the second is that which matches the group. Here is this array element:
["#EEE", "EEE", index: 0, input: "<colors>"]
If there are several groups in a regular expression, then they will fall into the results of processing the string in the order of their description in the regular expression. Consider an example:
const str = "My name is John Doe."; const matchRegExp = /My name is ([az]+) ([az]+)/i; const result = str.match( matchRegExp );console.log( result );
It can be seen that the first line of the output is the entire line corresponding to the regular expression. The second and third elements represent what was captured by the groups.
Using named groups allows you to save what groups capture within the
groups
object whose property names correspond to the names assigned to groups.
const str = "My name is John Doe."; const matchRegExp = /My name is (?<firstName>[az]+) (?<lastName>[az]+)/i; const result = str.match( matchRegExp ); console.log( result ); console.log( result.groups );
It should be noted that named groups work fine together with the
.matchAll()
method.
→ Support
To be continued…
Dear readers! Have you used any of the JavaScript innovations described here?