Categories
Modern JavaScript

Best Features of ES2018 — Regex

Since 2015, JavaScript has improved immensely.

It’s much more pleasant to use it now than ever.

In this article, we’ll look at the best features of ES2018.

Numbered Capture Groups

Numbered capture groups lets us take apart a string with a regex.

This is the old way to get group matches.

A regex that matches a string returns a matched object.

Then we can get each part by its index.,

For instance, we can write:

const PHONE_REGEX = /([0-9]{3})-([0-9]{3})-([0-9]{4})/;

const matchObj = PHONE_REGEX.exec('123-456-7890');

We have 3 capture groups. 2 with 3 digits and one with 4 digits.

Then we can get each group by its index.

The problem with named capture groups is that we’ve to count parentheses.

We’ve to know what the groups are about from the regex pattern.

And we’ve to change the code if we change the regex.

Named Capture Groups

ES2018 gives us a new way to get a group match.

For instance, we can write:

const PHONE_REGEX = /(?<areaCode>[0-9]{3})-(?<officeCode>[0-9]{3})-(?<number>[0-9]{4})/;

const matchObj = PHONE_REGEX.exec('123-456-7890');
console.log(matchObj.groups)

We added the names to the groups.

Then we can get the matches in the groups property.

So we’ll get:

{ areaCode: "123", officeCode: "456", number: "7890" }

as the value of macthObj.groups .

This means that we can destructure the matches:

const PHONE_REGEX = /(?<areaCode>[0-9]{3})-(?<officeCode>[0-9]{3})-(?<number>[0-9]{4})/;

const matchObj = PHONE_REGEX.exec('123-456-7890');
const {
  areaCode,
  officeCode,
  number
} = matchObj.groups;

Named captured groups are good because it’s easier to identify the capture group.

The matching code is descriptive since the ID describes what it’s capturing.

And we can change the pattern without changing the matching code.

The names make the pattern easier to understand.

Backreferences

We can reference a matching group by its name in a regex.

For instance, we can write:

const REGEX = /^(?<word>[abc]+)k<word>$/;

const matchObj = REGEX.exec('abcabc');
console.log(matchObj.groups)

Then get the matches with marchObj.groups.words .

word has the same pattern.

replace()

We can use replace with named capture groups.

For instance, we can write:

const PHONE_REGEX = /(?<areaCode>[0-9]{3})-(?<officeCode>[0-9]{3})-(?<number>[0-9]{4})/;

console.log('123-456-7890'.replace(PHONE_REGEX,
  '$<areaCode>$<officeCode>$<number>'))

We called replace with our regex and a string with the pattern that we want to format the string to.

We just get the named capture groups and combined them without spaces.

So we get:

'1234567890'

returned.

It also takes a callback that has the groups as one of the parameters:

const PHONE_REGEX = /(?<areaCode>[0-9]{3})-(?<officeCode>[0-9]{3})-(?<number>[0-9]{4})/;

console.log('123-456-7890'.replace(PHONE_REGEX,
  (g0, ac, oc, num, offset, input, {
    areaCode,
    officeCode,
    number
  }) => `${areaCode}${officeCode}${number}`))

The last parameter has the groups.

Also, ac , oc , and num are the groups.

We return a new string so that it’ll be the string returned by replace .

g0 has the full string.

offset specifies where the matches are found.

input has the complete input string.

Named groups that don’t match

If we have named groups that don’t match, then the property will be undefined in the groups property.

Conclusion

Named capture groups are handy for naming matches so we can understand them easier and won’t have to change the matching code.

Categories
Modern JavaScript

Best Features of ES2018 — Promise finally and Template Restrictions

Since 2015, JavaScript has improved immensely.

It’s much more pleasant to use it now than ever.

In this article, we’ll look at the best features of ES2018.

Promise.prototype.finally()

The finally method is added to the Promise instance to run some code regardless of the outcome of the promise.

For instance, we can write:

promise
  .then(result => {
    //...
  })
  .catch(error => {
    //...
  })
  .finally(() => {
    //...
  });

The then callback is run when promise is fulfilled.

catch callback is run when promise is rejected.

finally callback is run regardless of the outcome.

So:

promise
  .finally(() => {
    //...
  });

is the same as:

promise
  .then(
    result => {
      //...
      return result;
    },
    error => {
      //...
      throw error;
    }
  );

The most common use case is similar to the synchronous finally clause.

It lets us clean up after we’re done with a resource.

finally() is the same as finally {} is synchronous code.

The try clause corresponds to the promise’s then callback.

catch is like .catch in promises.

finally is like .finally in promises.

Template Literal Changes

Tag functions how work wirh escape sequences.

For instance, we can write:

const raw = String.raw\`\u{50}`

Then we get '\u{50}' in raw form.

So we get:

function tag(str) {
  console.log(str);
}

tag `\u{50}`

as the value of str :

["P", raw: Array(1)]

The cooked form is 'P' and the raw version is what we had.

Not all text is illegal with backslashes.

So writing something like:

`unicode`

won’t work.

ES2018 drops the syntactic restrictions so that we can enter these kinds of character combinations into the template literal.

Conclusion

Template literal syntax is now less restrictive than before with ES2018.

Some escape sequences are looser.

Also, the finally method is added to Promise instances so that we can run code regardless of the outcome of the promise.

Categories
Modern JavaScript

Best Features of ES2018 — Object Spread vs Object.assign

Since 2015, JavaScript has improved immensely.

It’s much more pleasant to use it now than ever.

In this article, we’ll look at the best features of ES2018.

Cloning Object Prototype with Properties

We can clone the object’s prototype with properties.

To do that, we can set the __proto__ property to the original object’s prototype:

const obj = {
  a: 1,
  b: 2
};

const clone = {
  __proto__: Object.getPrototypeOf(obj),
  ...obj
};

We set the prototype and we spread the other properties to get a more comprehensive clone.

This is the same as:

const clone = Object.assign(
  Object.create(Object.getPrototypeOf(obj)), obj);

Merging Two Objects

The object spread operator is also handy for merging multiple objects into one.

For instance, we can write:

const merged = {
  ...obj1,
  ...obj2
};

which is the same as:

const merged = Object.assign({}, obj1, obj2);

Merging objects is useful for doing things like merging options with the default options:

const data = {
  ...DEFAULTS,
  ...options
};

Spreading Objects versus Object.assign()

The spread operator and Object.assign are very similar.

The difference is that spread define new properties, while Object.assign set them.

For instance, if we have:

Object.assign(target, obj1, obj2);

then target is modified in place with the properties of obj1 and obj2 .

If we want to create a new merged object with the properties from all 3 objects, we can write:

const merged = Object.assign({}, target, obj1, obj2);

It returns a new object with the properties from target , obj1 , and obj2 .

The spread operator is the same as the 2nd way of using Object.assign .

Spread and Object.assign both read values with a get operation.

For instance, if we have:

const obj = {
  get a() {
    return 1
  }
}

const clone = Object.assign({}, obj);

Then the a getter is cloned in clone .

If we have:

const obj = {
  get a() {
    return 1
  }
}

const clone = {
  ...obj
};

Then we get the same result.

The spread operator defines new properties in the target object.

But Object.assign uses a normal set operation to create them.

If we add a setter to the Object.prototype , create the object, and run Object.assign on it:

Object.defineProperty(Object.prototype, 'foo', {
  set(value) {
    console.log('set', value);
  },
});

const obj = {
  foo: 1
};

const clone = `Object.assign({}, obj);`

then the setter is run as we can see from the console log.

On the other hand, if we have:

Object.defineProperty(Object.prototype, 'foo', {
  set(value) {
    console.log('set', value);
  },
});
const obj = {
  foo: 1
};

const clone = {
  ...obj
}

then the setter isn’t run.

With Object.assign , we can stop it from creating non-inherited properties via inherited read-only properties.

For instance, we can write:

Object.defineProperty(Object.prototype, 'foo', {
    writable: false,
    value: 1,
});

const obj = {
  foo: 1
};

const clone = Object.assign({}, obj);

Then we get ‘Uncaught TypeError: Cannot assign to read-only property ‘foo’ of object ‘#<Object>’’.

On the other hand, if we have:

Object.defineProperty(Object.prototype, 'foo', {
  writable: false,
  value: 1,
});

const obj = {
  foo: 1
};
const clone = {
  ...obj
};

Then we don’t get any errors, so the property is rated successfully.

This is because the spread operator defines properties rather than setting them.

Conclusion

Object.assign does similar things, but there are small differences that we can’t overlook.

Categories
Modern JavaScript

Best Features of ES2018 — Object Rest and Spread

Since 2015, JavaScript has improved immensely.

It’s much more pleasant to use it now than ever.

In this article, we’ll look at the best features of ES2018.

Rest Operator on Objects

We can use destructuring with the rest operator in objects.

For instance, we can write:

const obj = {
  a: 1,
  b: 2,
  c: 3
};
const {
  a,
  ...rest
} = obj;

Then a is 1 and rest is:

{
  "b": 2,
  "c": 3
}

If we’re using object destructuring to handle named parameters, the rest operator lets us collect all the remaining parameters.

So if we write:

function foo({
  a,
  b,
  ...rest
}) {
  console.log(
    a,
    b,
    rest
  );
  return a + b;
}

foo({
  a: 1,
  b: 2,
  c: 3
})

Then a is 1, b is 2, and rest is {c: 3} .

We can use the rest operator for destructuring at most once and only at the end.

This makes sense since it’s the only way JavaScript engines know what hasn’t been assigned to a variable yet with destructuring.

So we can’t write:

const {...rest, a} = obj;

or:

const {foo, ...a, ...b} = obj;

They both will give us syntax errors.

We can nest the rest operator.

For instance, we can write:

const obj = {
  qux: {
    a: 1,
    b: 2,
    c: 3,
  },
  foo: 4,
  baz: 5,
  bar: 6,
};
const {
  qux: {
    a,
    ...rest1
  },
  ...rest2
} = obj;

Then a is 1, rest1 is {b: 2, c: 3} .

And rest2 is {foo: 4, baz: 5, bar: 6} .

Spread Operator in Object Literals

The spread operator in object literals is new to ES2018.

For instance, we can use it by writing:

const obj = {
  a: 1,
  b: 2
};

const obj2 = {
  ...obj,
  baz: 3
}

We used the spread operator to copy the properties of obj into a new object and assigned it to obj2 .

So obj2 is {a: 1, b: 2, baz: 3} .

The order matters since they’ll be populated in the order they’re spread.

So if we have:

const obj = {
  a: 1,
  b: 2
};

const obj2 = {
  baz: 3,
  ...obj
}

Then obj2 is {baz: 3, a: 1, b: 2} .

The 2 keys clash, then the later one overwrites the earlier ones.

So if we have:

const obj = {
  a: 1,
  baz: 2
};

const obj2 = {
  baz: 3,
  ...obj
}

Then we get:

{baz: 2, a: 1}

as the value of obj2 .

Uses of the Object Spread Operator

The object spread operator has a few use cases.

One of them is to clone an object.

For instance, we can write:

const obj = {
  a: 1,
  b: 2
};

const clone = {
  ...obj
};

Then a shallow copy of obj is made with the spread operator and so clone is:

{
  a: 1,
  b: 2
}

This is the same as using Object.assign to do the cloning;

const obj = {
  a: 1,
  b: 2
};

const clone = Object.assign({}, obj);

Spread is shorter and cleaner.

Conclusion

The object rest and spread operator are great new operators that are released with ES2018.

It’s clean and convenient.

Categories
Modern JavaScript

Best Features of ES2018 — New Regex Features

Since 2015, JavaScript has improved immensely.

It’s much more pleasant to use it now than ever.

In this article, we’ll look at the best features of ES2018.

Regex Property Escapes

Unicode has properties, which are metadata describing it.

There are properties like Lowercase_Letter to describe lowercase letters, White_space to describe white spaces etc.

There’re several types of properties.

Enumerated property is a property value whose values are few and named.

General_Category is an enumerated property.

Close enumerated property ios an enumerated property whose set values is fixed.

Boolean property is a close enumerated property whose value is true or false .

Numeric properties have values that are real numbers.

String-valued property is a property whose values are strings.

Catalog property is an enumerated property that may be extended as Unicode changes.

Miscellaneous property is a property whose values aren’t any of the above.

There are various kinds of matching properties and property values.

The properties are loose matching, so General_Category is considered the same as GeneralCategory and other variants.

Unicode Property Escapes for Regex

We can use the \p characters to escape the Unicode properties.

This must be used with the /u flag to enable Unicode mode.

\p is the same as p without Unicode mode.

For instance, we can write:

const result = /^\p{White_Space}+$/u.test(' ')

and result would be true .

This means \p{White_Space} matches whitespace.

This is more descriptive than regular regex patterns.

We can also write:

const result = /^\p{Letter}+$/u.test('abc')

to match letters.

To match Greek letters, we write:

const result = /^\p{Script=Greek}+$/u.test('μ')

And we can match Latin letters with:

const result = /^\p{Script=Latin}+$/u.test('ç')

Long surrogate characters can also be matched:

const result = /^\p{Surrogate}+$/u.test('\\u{D83D}')

Lookbehind Assertions

A lookbehind assertion is a construct in a regex that specifies what the surroundings of the current location must look like.

For instance, we can write:

const RE_DOLLAR_PREFIX = /(?<=\$)\d+/g;

const result = '$123'.replace(RE_DOLLAR_PREFIX, '456');

We have the (?<=\$) group to look for digits with a dollar sign before it.

Then when we call replace to replace the number, we just replace the number.

We searched for something with $ and digits after it with that regex.

So result is '$456' .

This doesn’t work if the prefix should be part of the previous match.

We can also add a ! to add a negative lookbehind assertion.,

For instance, we can write:

const RE_DOLLAR_PREFIX = /(?<!\$)baz/g;

const result = '&baz'.replace(RE_DOLLAR_PREFIX, 'qux');

The regex looks for baz without a dollar sign before it.

So if we called replace as we did, we get '&qux’ returned since $ isn’t in the string.

s (dotAll) Flag for Regex

The dotAll dlag is a enahnance of the . flag in a regex.

The . in a regex doesn’t match line terminator characters.

So if we have:

/^.$/.test('\n')

We get false .

To match line terminator characters, we’ve to wite:

/^[^]$/.test('\\n')

to match everything except no character or:

/^\[\s\S]$/.test('\n')

to match space or non-whitespace.

There’re various kinds of line termination characters.

They include:

  • U+000A line feed (LF) (\n)
  • U+000D carriage return (CR) (\r)
  • U+2028 line separator
  • U+2029 paragraph separator

With ES2018, we can use the /s flag to match something that ends with a line terminator:

const result = /^.$/s.test('\\n')

So result should be true .

The long name is dotAll .

So we can write:

/./s.dotAll

and that returns true .

Conclusion

Regex property escapes, lookbehind assertions, and the dotAll flag new additions to JavaScript regexes that let us match various special cases.