Categories
Modern JavaScript

Best Features of ES2019 — Catch, Sort, and toString

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 ES2019.

Catch Binding

catch binding is now optional with ES2019.

For instance, we can write:

let jsonData;
try {
  jsonData = JSON.parse(str);
} catch {
  jsonData = {};
}

Then we don’t need to get the thrown object in the parentheses after the catch .

However, other kinds of errors that aren’t related to parsing will be silently ignored, since we just set it the jsonData object to an empty object.

Another example would be to get a deeply nested property.

For instance, we can just write:

function logId(item) {
  let id;
  try {
    id = item.data.id;
  } catch {
    console.log(id);
  }
}

to let us safely get the deeply nested id property of item to id if it exists.

If it doesn’t exist, then the code in the catch block is run.

Stable Array.prototype.sort()

The Array.prototype.sort method is now guaranteed to be stable.

So if something is considered to be the same when sorting, then they’ll stay in the same order in engines that supports ES2019.

If we have:

const arr = [{
    key: 'foo',
    value: 1
  },
  {
    key: 'bar',
    value: 2
  },
  {
    key: 'foo',
    value: 3
  },
];
arr.sort((x, y) => x.key.localeCompare(y.key, 'en'));

Then arr would be in the same order regardless of which environment it’s in.

JSON.stringify

JSON.stringify can now take a lone surrogate.

So if we have:

JSON.stringify('u{D800}')

Then we get:

""ud800""

JSON superset

JSON strings now can have the '"u2028"' character.

Function.prototype.toString Changes

Function.prototype.toString now has some changes since ES2019.

The functions we defined returns a string with the original source code.

If we call toString with a built-in function, then we see [native code] .

For instance, if we have:

isNaN.toString()

Then we get:

"function isNaN() { [native code] }"

Functions created dynamically with the Function and GeneratorFunction engines must create the appropriate source code and attach it to functions.

All other cases would throw a type error.

Conclusion

Catch binding, function toString , sort methods all have changes with ES2019.

Categories
Modern JavaScript

Best Features of ES2019 — Arrays, Strings, and Symbols

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 ES2019.

Array.prototype.flat

The flat method on the array instance is a useful method in ES2019.

It lets us flatten arrays one or more levels deep.

For instance, we can write:

const arr = [1, 2, [3, 4],
  [
    [5, 6]
  ],
  [7, 8]
].flat(0)

Then there’s no change to the array.

If we have:

const arr = [1, 2, [3, 4],
  [
    [5, 6]
  ],
  [7, 8]
].flat(1)

Then we get:

[
  1,
  2,
  3,
  4,
  [
    5,
    6
  ],
  7,
  8
]

And if we have:

const arr = [1, 2, [3, 4],
  [
    [5, 6]
  ],
  [7, 8]
].flat(2)

We get:

[
  1,
  2,
  3,
  4,
  5,
  6,
  7,
  8
]

1 is the default value.

Array.prototype.flatMap()

flatMap combines the map and flat methods together.

For instance, we can write:

const arr = [1, 2, 3].flatMap(x => x)

Then we get:

[
  1,
  2,
  3
]

If we have:

const arr = [1, 2, 3].flatMap(x => [x])

then we get the same thing.

And if we have:

const arr = [1, 2, 3].flatMap(x => [[x]])

then we get:

[
  [
    1
  ],
  [
    2
  ],
  [
    3
  ]
]

The callback lets us map the entries to what we want and call flat on each entry.

Object.fromEntries()

The Object.fromEntries() method lets us create an object from an array of key-value arrays.

For instance, we can write:

const obj = Object.fromEntries([
  ['a', 1],
  ['b', 2]
]);

Then obj is {a: 1, b: 2} .

It does the opposite action from Object.entries() :

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

const arr = Object.entries(obj);

Then we get:

[
  [
    "a",
    1
  ],
  [
    "b",
    2
  ]
]

String.prototype.trimStart

The trimStart string instance method lets us trim the whitespace at the start of a string.

For instance, we can write:

const str = '  foo  '.trimStart()

Then we get:

'foo  '

String.prototype.trimEnd

The trimEnd method lets us trim the end of a string.

For instance, we can write:

'  foo'

trimLeft and trimRight are equivalent to trimStart and trimEnd respectively.

But those are legacy methods since we want consistent with padStart and padEnd .

What are Whitespaces?

The following characters are whitespaces:

  • <TAB> (character tabulation, U+0009)
  • <VT> (line tabulation, U+000B)
  • <FF> (form feed, U+000C)
  • <SP> (space, U+0020)
  • <NBSP> (no-break space, U+00A0)
  • <ZWNBSP> (zero-width bo break space, U+FEFF)
  • Any other Unicode character with the property White_Space in category Space_Separator (Zs).

Line terminators are also whitespaces. They include:

  • <LF> (line feed, U+000A)
  • <CR> (carriage return, U+000D)
  • <LS> (line separator, U+2028)
  • <PS> (paragraph separator, U+2029)

Symbol.prototype.description

The Symbol.prototype.description method returns the description of the symbol.

For instance, if we have:

const sym = Symbol('description');

console.log(sym.description)

Then 'description’ is logged.

Conclusion

ES2019 introduced various arrays, strings, and symbol methods.

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.