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.

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.