Categories
JavaScript JavaScript Basics

The Comprehensive List of JavaScript String Methods

Strings are an important global object in JavaScript. They represent a sequence of characters. They can be used by various operators, like comparison operators, and there are various methods that can be used to manipulate them. There are different ways to manipulate strings, look up parts of a string in various positions, and trim and replace strings. With each release of the JavaScript specification, more methods are added to make string search and manipulating easier than ever.


Static Methods

Static methods are called without having to create a string literal or object. There are the following methods:

String.fromCharCode()

With the fromCharCode method, we can construct a string by passing in a comma-separated list of HTML character codes or the hexadecimal Unicode character code as arguments and get a string with the actual characters returned. For example, we can write:

const str = String.fromCharCode(38, 8226)

Then, we get &• when we log str with console.log. We can pass in hexadecimal character codes, like in the following code:

const str = String.fromCharCode(0x0026, 0x2022)

Then, we also get &• when we log str with console.log.

String.fromCodePoint()

The String.fromCodePoint() method returns a string given by the character codes that you entered into the arguments. The code points can be HTML character codes or the hexadecimal Unicode character code. For example, we can write:

const str = String.fromCodePoint(38, 8226)

Then, we get &• when we log str with console.log. We can pass in hexadecimal character codes, like in the following code:

const str = String.fromCodePoint(0x0026, 0x2022)

Then, we also get &• when we log str with console.log.


Instance Methods

Instance methods of a string are called on the string literal or the string object. They do something to the instance of the string that’s being called on.

String.prototype.charAt()

charAt returns the character located at the index of the string.

For example:

const str = "Hello";  
const res = str.charAt(0); // 'H'

String.charCodeAt()

charCodeAt returns the UTF-16 character code located at the index of the string.

For example:

const str = "Hello";  
const res = str.charCodeAt(0); // 72

String.prototype.codePointAt()

codePointAt returns the code point value of the UTF-16 encoded code point located at the index of the string.

For example:

const str = "Hello";  
const res = str.codePointAt(0); // 72

String.prototype.concat()

The concat method combines two strings and returns a new string. For example, we can write:

const str1 = "Hello";  
const str2 = " World";  
const res = str1.concat(str2); // 'Hello World'

String.prototype.endsWith()

endsWith checks if a string ends with the substring you passed in.

For example:

const str = "Hello world.";  
const hasHello = str.endsWith("world."); // trueconst str2 = "Hello world.";  
const hasHello2 = str.endsWith("abc"); // false

String.prototype.includes()

includes checks if the passed-in substring is in the string. It returns true if it’s in the string, and false otherwise.

const str = "Hello";  
const hasH = str.includes('H'); // true  
const hasW = str.includes('W'); // false

String.prototype.indexOf()

indexOf finds the index of the first occurrence of the substring. It returns -1 if not found.

For example:

const str = "Hello Hello.";  
const hasHello = str.indexOf("Hello"); // 0  
const hasHello2 = str.indexOf("abc"); // -1

String.lastIndexOf()

lastIndexOf finds the index of the last occurrence of the substring. It returns -1 if not found.

For example:

const str = "Hello Hello.";  
const hasHello = str.lastIndexOf("Hello"); // 6  
const hasHello2 = str.lastIndexOf("abc"); // -1

String.prototype.localeCompare()

The localeCompare method compares whether one string comes before another by using the locale’s rules for comparing the order of precedence of the string. The general syntax to call this method is:

referenceStr.localeCompare(compareString, locales, options)

It returns the following values depending on whether referenceStr comes before compareStr:

  • Negative when the referenceStr occurs before compareStr
  • Positive when the referenceStr occurs after compareStr
  • Returns 0 if they are equivalent

The first argument of the method is the string you want to compare with.

The second argument of the method optionally takes a locale string or an array of locale strings, which are BCP-47 language tag with optional Unicode extension keys. Unicode extension keys, including "big5han", "dict", "direct", "ducet", "gb2312", "phonebk", "phonetic", "pinyin", "reformed", "searchjl", "stroke", "trad", and "unihan" are also allowed in our locale strings. They specify the collations that we want to compare strings with. However, when there are fields in the options in the second argument that overlap with this, then the options in the argument overrides the Unicode extension keys specified in the first argument.

Numerical collations can be specified by adding kn to your locale string in your first argument. For example, if we want to compare numerical strings, then we can write:

console.log('10'.localeCompare('2', 'en-u-kn-true'));

Then, we get one since we specified kn in the locale string in the constructor. This makes the collator compare numbers, and 10 comes after two.

Also, we can specify whether upper or lowercase letters should be sorted first with the kf extension key. The possible options are upper, lower, or false. false means that the locale’s default will be the option. This option can be set in the locale string by adding it to the locale string as a Unicode extension key, and if both are provided then the option property will take precedence. For example, to make uppercase letters have precedence over lowercase letters, we can write:

console.log('Able'.localeCompare('able', 'en-ca-u-kf-upper'));

This will sort the same word with uppercase letters first. When we run console.log, we get -1 since we have an uppercase ‘A’ in ‘Able,’ and a lowercase ‘a’ for ‘able.’ On the other hand, if we instead pass in en-ca-u-kf-lower in the constructor, like in the code below:

console.log('Able'.localeCompare('able', 'en-ca-u-kf-lower'));

Then after console.log we get one because kf-lower means that we sort the same word with lowercase letters before the ones with uppercase letters.

The third argument of the method optionally takes an object that can have multiple properties. The properties that the object accepts are localeMatcher, usage, sensitivity, ignorePunctuation, numeric, and caseFirst. numeric is the same as the kn option in the Unicode extension key in the locale string, and caseFirst is the same as the kf option. The localeMatcher option specifies the locale matching algorithm to use. The possible values are lookup and best fit. The lookup algorithm searches for the locale until it finds the one that fits the character set of the strings that are being compared. best fit finds a locale that is possibly more suited than the lookup algorithm.

The usage option specifies whether the Collator is used for sorting or searching for strings. The default option is sort.

The sensitivity option specifies the way that the strings are compared. The possible options are base, accent, case, and variant. base compares the base of the letter, ignoring the accent. For example, a is not the same as b, but a is the same as á, and a is the same as Ä . accent specifies that a string is only different if their base letter or their accent is unequal, ignoring case. So, a isn’t the same as b, but a is the same as A. a is not the same as á.

The case option specifies that strings that are different in their base letters or case are considered unequal, and so a wouldn’t be the same as A and a wouldn’t be the same as c, but a is the same as á. variant means that strings that have different base letters, accents, other marks, or cases are considered unequal. For example, a wouldn’t be the same as A and a wouldn’t be the same as c, but a also wouldn’t be the same as á.

The ignorePunctuation specifies whether punctuation should be ignored when sorting strings. It’s a boolean property and the default value is false.

String.prototype.match()

The match method retrieves the substring matches of a string given the regular expression to match with. The match method takes a regular expression as an argument. It can either be a regular expression literal or a RegExp object. If no regular expression is given, we’ll get an array with an empty string. If the regular has the g flag, then all the matches in a string will be returned in the array. Otherwise, only the first will be returned.

The matches are returned as an array. For example, we can do a case insensitive search with the i flag:

const str = 'foo';  
const re = /foo/i;  
const found = str.match(re);  
console.log(found);

If we run the code above, we get:

[  
  "foo"  
]

from the console.log statement above.

If we toggle the g flag on and off, we get different results. If the regular has the g flag, then all the matches in a string will be returned in the array. Otherwise, only the first will be returned. If we have the g flag on like in the code below:

const str = 'The quick brown fox jumps over the lazy dog. It barked.';  
const globalRe = /[A-Z]/g;  
const globalFound = str.match(globalRe);  
console.log(globalFound);  
console.log(JSON.stringify(globalFound, (key, value) => value, 2));

Then we get:

[  
  "T",  
  "I"  
]

from the console.log statement above. However, if we removed the g flag, like in the code below:

const re = /[A-Z]/;  
const found = str.match(re);  
console.log(found);  
console.log(JSON.stringify(found, (key, value) => value, 2));

Then we get:

[  
  "T"  
]

from the console.log statement above.

String.prototype.matchAll()

The matchAll method would return all the results that are found by passing in a regular expression to the argument of the matchAll method. It returns an iterator with arrays of the result as the array entries. For example, if we have the following code to do a global search:

const str = 'The quick brown fox jumps over the lazy dog. It barked.';  
const globalRe = /[A-Z]/g;  
const globalFound = [...str.matchAll(globalRe)];  
console.log(globalFound);  
console.log(JSON.stringify(globalFound, (key, value) => value, 2));

Then we get:

[  
  [  
    "T"  
  ],  
  [  
    "I"  
  ]  
]

On the other hand, if we don’t have the global flag in the regular expression to do a global search, like in the following code:

const re = /[A-Z]/;  
const found = [...str.matchAll(re)];  
console.log(found);  
console.log(JSON.stringify(found, (key, value) => value, 2));

Then we get:

[  
  [  
    "T"  
  ]  
]

from the console.log statement above.

String.prototype.normalize()

The normalize method returns the Unicode Normalization Form of a given string. It will convert non-string arguments into a string before conversion. For example, if we have:

const first = '\u00e5'; // "å"
const second = '\u0061\u030A'; // "å"

Then we call the normalize method in each string to normalize them into the same character code:

first.normalize('NFC') === second.normalize('NFC')

The code above will evaluate to true since we normalized the strings to the same character code. However, if we didn’t call normalize:

first === second

Then the code above would return false . If we run console.log on both strings to get their character codes like in the code below:

console.log(first.normalize('NFC').charCodeAt(0));
console.log(second.normalize('NFC').charCodeAt(0));

We get 229 for the first characters for both strings since they’re the same after normalization. On the other hand, if we run it before normalization with normalize like in the code below:

console.log(first.charCodeAt(0));
console.log(second.charCodeAt(0));

We get that the first string logs 229 and the second logs 97.

String.prototype.padStart()

The padStart() function pads the current string with another string (multiple times, if needed) until the resulting string reaches the given length. The padding is applied from the start of the current string.

The function takes 2 parameters. The first is the target length of your string. If the target length is less than the length of your string, then the string is returned as-is. The second parameter is the string that you want to add the padding with. It’s an optional parameter and it defaults to ' ' if nothing is specified.

For example, we can write the following:

'def'.padStart(10);         // "       def"
'def'.padStart(10, "123");  // "1231231def"
'def'.padStart(6,"123465"); // "abcdef"
'def'.padStart(8, "0");     // "00000def"
'def'.padStart(1);          // "def"

Note that each string is filled up to the target length with the string in the second argument. The whole string in the second argument may not always be included. Only the part that lets the function fills the string up to the target length is included.

String.prototype.padEnd()

The padEnd() function pads the current string with another string (multiple times, if needed) until the resulting string reaches the given length. The padding is applied from the end of the current string.

The function takes 2 parameters. The first is the target length of your string. If the target length is less than the length of your string, then the string is returned as-is. The second parameter is the string that you want to add the padding with. It’s an optional parameter and it defaults to ' ' if nothing is specified.

For example, we can write the following:

'def'.padEnd(10);         // "def       "
'def'.padEnd(10, "123");  // "def1231231"
'def'.padEnd(6,"123465"); // "defabc"
'def'.padEnd(8, "0");     // "def00000"
'def'.padEnd(1);          // "def"

String.prototype.repeat()

To use the repeat function, you pass in the number of times you want to repeat the string as an argument. It returns a new string.

For example:

const hello = "hello";
const hello5 = A.repeat(5);
console.log(hello5); // "hellohellohellohellohello"

String.prototype.replace()

The replace() function included with strings is useful for replacing parts of strings with another. It returns a new string with the string after the substring is replaced.

Example:

const str = "Hello Bob";
const res = str.replace("Bob", "James"); // 'Hello James'

replace() can also be used to replace all occurrences of a substring.

For example:

const str = "There are 2 chickens in fridge. I will eat chickens today.";
const res = str.replace(/chickens/g, "ducks"); // "There are 2 chickens in fridge. I will eat chickens today."

If the first argument is a regular expression that searches globally, it will replace all occurrences of the substring.

String.search()

search gets the position of the substring passed into the function. It returns -1 if the substring is not found in the string.

Example:

const str = "Hello";
const res = str.search('H'); // 0

String.prototype.slice()

The slice method extracts a part of the string and returns a new string. We can pass in both positive and negative numbers. If the range is positive, then the returned substring will start and end with the indexes that are passed in. If they’re negative, then the starting index starts from the last character of the string with index -1 and then count backward back to the start of the string, going down by 1 when a character is left of another character. If the second argument is omitted, then it’s assumed to be the index of the last character of the string.

For example, we can write:

const str = 'Try not to become a man of success. Rather become a man of value.';
console.log(str.slice(31));
// output: "ess. Rather become a man of value."console.log(str.slice(4, 19));
// output: "not to become a"console.log(str.slice(-4));
// output: "lue."console.log(str.slice(-9, -5));
// output: "of v"

String.prototype.startsWith()

startsWith checks if a string starts with the substring you pass in.

For example:

const str = "Hello world.";
const hasHello = str.startsWith("Hello"); // trueconst str2 = "Hello world.";
const hasHello2 = str.startsWith("abc"); // false

String.prototype.split()

The split method splits a string into an array of substrings. The split method can split a string using a regular expression as the delimiter. For example, we can write:

const str = 'The 1 quick 2 brown fox jump.';
const splitStr = str.split(/(\d)/);console.log(splitStr);
// gets \["The ", "1", " quick ", "2", " brown fox jump."\]

This splits a sentence into an array of strings with the numbers separating the words. It can also take a string as the delimiter for separating the string like in the following example:

const str = 'The 1 quick 2 brown fox jump.';
const splitStr = str.split(' ');console.log(splitStr);
// gets \["The", "1", "quick", "2", "brown", "fox", "jump."\]

It can also take an array of strings as separators of the given string instance. For example:

const str = 'The 1 quick 2 brown fox jump.';
const splitStr = str.split(\[' '\]);
console.log(splitStr);
// gets \["The", "1", "quick", "2", "brown", "fox", "jump."\]

String.prototype.substr()

JavaScript strings have a substr function to get the substring of a string.

It takes a starting index as the first argument and a number of characters from the start index, and so on as the second argument.

It can be used like this:

const str = "Hello Bob";
const res = str.substr(1, 4); // ello

String.prototype.substring()

JavaScript strings also has a substring function that takes the start and end index as two arguments and returns a string with the ones between the start and end indices.

The start index is included but the end index is excluded.

Example:

const str = "Hello Bob";
const res = str.substring(1, 3); // el

<img alt="" class="t u v lu aj" src="https://miro.medium.com/max/1400/0*4SZwSC2Q8P8AoyXU" width="700" height="1050" srcSet="https://miro.medium.com/max/552/0*4SZwSC2Q8P8AoyXU 276w, https://miro.medium.com/max/1104/0*4SZwSC2Q8P8AoyXU 552w, https://miro.medium.com/max/1280/0*4SZwSC2Q8P8AoyXU 640w, https://miro.medium.com/max/1400/0*4SZwSC2Q8P8AoyXU 700w" sizes="700px" role="presentation"/>

Photo by Allef Vinicius on Unsplash

String.prototype.toLocaleLowerCase()

Converts a string to lowercase, according to the locale of your browser. The new string is returned instead of modifying the original string.

For example:

const str = "Hello Bob!";
const res = str.toLocaleLowerCase(); // hello bob!

String.prototype.toLocaleUpperCase()

Converts a string to uppercase, according to the locale of your browser. The new string is returned instead of modifying the original string.

For example:

const str = "Hello Bob!";
const res = str.toLocaleUpperCase(); // 'HELLO BOB!'

String.toLowerCase()

Converts a string to lowercase. The new string is returned instead of modifying the original string.

For example:

const str = "Hello Bob!";
const res = str.toLocaleLowerCase(); // 'hello bob!'

String.prototype.toUpperCase()

Converts a string to uppercase. The new string is returned instead of modifying the original string.

For example:

const str = "Hello Bob!";
const res = str.toLocaleUpperCase(); // 'HELLO BOB!'

String.prototype.trim()

Remove starting and ending white space from a string.

For example:

const str = "         Hello Bob!     ";
const res = str.trim(); // 'Hello Bob!'

String.prototype.trimStart()

Now the string object has a trimStart() function to trim the beginning whitespace of a string. There’s also a trimLeft() method which is an alias to this method.

For example, we can write:

let message = '   Hi! How\'s it going?   ';console.log(message);// We get '   Hi! How's it going?   'let message = '   Hi! How\'s it going?   ';
console.log(message.trimStart());// We get 'Hi! How's it going?   '

As we can see, the whitespace on the left side is gone. We can do the same thing with trimLeft() :

let message = '   Hi! How\'s it going?   ';
console.log(message.trimLeft());// We get 'Hi! How's it going?   '

Note that a new string is returned with trimStart or trimLeft, so the original string stays intact. This prevents us from mutating the original string, which is handy for preventing mistakes from accidentally mutating objects.

String.prototype.trimEnd()

The trimEnd method removes whitespace from the right end of the string. trimRight is an alias of the trimEnd method. For example, we write:

let message = '   Hi! How\'s it going?   ';
console.log(message);
// We get '   Hi! How's it going?   'let message = '   Hi! How\'s it going?';
console.log(message.trimEnd());
// We get '   Hi! How\'s it going?'

Note that a new string is returned with trimEnd or trimRight, so the original string stays intact. This prevents us from mutating the original string, which is handy for preventing mistakes from accidentally mutating objects.

String.valueOf()

With the valueOf method, we can convert a string object into a primitive string. For example, we can write:

const sPrim = 'foo';
const sObj = new String(sPrim);
console.log(typeof sPrim);
console.log(typeof sObj.valueOf());

Then we get that both console.log statements would show ‘string’.

Conclusion

Strings are an important global object in JavaScript. It represents a sequence of characters. It has methods to manipulate strings, look up parts of a string in various positions, and trim and replace strings. With each release of the JavaScript specification, more methods are added to make string search and manipulating easier than ever.

Categories
JavaScript JavaScript Basics

Great New Features in ES2018 You May Have Missed

Ever since ES2015 was released, which was a great leap forward in itself, JavaScript has been improving at a fast pace. Every year since then, new features have been added to JavaScript specifications.

Features like new syntax and new methods for building in JavaScript have been added consistently. In ES2016 and 2017, The Object object had methods like Object.values and Object.entries added.

String methods like padStart and padEnd were added in ES2017. async and await, which is a shorthand syntax for chaining promises, were also added in ES2017.

The includes methods for arrays were added in ES2016. ES2018 was another release with lots of new features. With ES2018, the spread syntax is now available for object literals. Rest parameters were also added.

The for await...of loop, which is a loop syntax iterating through promises sequentially, was also added. The SharedArrayBuffer object was added for representing raw binary data that cannot become detached.

A finally function was also added to the Promise object.


Spread Operator in Objects

The spread syntax works by copying the values of the original array, and then inserting them into another array or putting them in the order they appeared in the array as the list of arguments in a function in the same order.

When the spread operator is used with objects, the key-value pairs appear in the same order they appeared in the original object.

With ES2018, the spread operator works with object literals. Then key-value pairs of an object can be inserted into another object with the spread operator.

If there are two objects with the same key that the spread operator is applied to in the same object, the one that’s inserted later will overwrite the one that’s inserted earlier.

For example, if we have the following:

let obj1 = {foo: 'bar', a: 1};  
let obj2 = {foo: 'baz', b: 1};  
let obj3 = {...obj1, ...obj2 }

Then we get {foo: “baz”, a: 1, b: 1} as the value of obj3 because obj1 is spread before obj2. They both have foo as a key in the object.

First foo: 'bar' is inserted by the spread operator to obj3. Then foo: 'baz' overwrites the value of foo after obj2 is merged in since it has the same key, foo, but was inserted later.

This is great for merging objects as we don’t have to loop through the keys and put in the values, which is much more than one line of code.

One thing to note is that we can’t mix the spread operator between regular objects and iterable objects. For example, we will get TypeError if we write the following:

let obj = {foo: 'bar'};  
let array = [...obj];

Rest Operator

The rest operator is a JavaScript operator where we can store an indefinite number of arguments in a function as an array. It looks exactly like the spread operator, which lets us spread entries of an array or a list of key-value pairs of an object into another object.

For example, if we have a function that has a long list of arguments, we can use the rest operator to shorten the list of parameters in a function.

The rest operator is used with the following syntax:

const fn = (a,b,..restOfTheArgs) => {...}

Where restOfTheArgs is an array with the list of arguments other than the first two.

For example, if we want to write a sum function that takes an indefinite list of numbers as arguments and sum up the numbers, we can write:

const sum = (a,b,...otherNums) => {  
  return a + b + otherNums.reduce((x,y)=>x+y, 0);  
}

As we can see, this is very handy for functions that have any list of arguments. Before we had this, we had to use the arguments object available in functions to get a list of arguments. This is not ideal as we allow them to pass in anything into the arguments.

With the rest operator, we have the best of both worlds. We can have some fixed parameters, while the rest stay flexible. This makes functions more flexible than functions with a fixed number of parameters, while having some flexibility of functions that take an indefinite number of arguments.

The arguments object has all the arguments of the function. Also, it’s not a real array, so array functions aren’t available to them. It’s just an object with indexes and keys to denote the argument’s positions.

Methods like sort, map, forEach, or pop cannot be run on the argument’s object. It also has other properties. This creates confusion for programmers. The arguments that are converted to an array with the rest operator do not have these issues, as they are real arrays.

To call the sum function we wrote, we can write:

const result = sum(1,2,3,4,5,6,7,8,9,10);

The result will be 55, since we summed up all the arguments together. otherNums is an array with all the numbers other than 1 and 2.

We can also use the rest operator to destructure a list of arguments into a list of variables. This means that we can convert a list of parameters into an array with the spread operator, and then decompose the array of arguments into a list of variables.

This is very useful as we can get the entries of the array that’s operated on by the rest operator and convert them to named variables. For example, we can write:

const sum = (a,b,...[c,d,e])=> a+b+c+d+e;

This way, we can use the rest operator, but limit the number of arguments that your function accepts. We take the function parameters a and b, and we also take c, d, and e as parameters.

However, it’s probably clearer without using the rest operator since all the parameters are fixed, we can just list the parameters directly.


for await...of

The for await...of loop allows us to create a loop that iterates over a list of promises as well as over the entries of a normal iterables.

It works with iterable objects like arrays, string, argument object, NodeList object, TypedArray, Map, Set, and user-defined synchronous and asynchronous iterable objects.

To use it, we write:

for await (let variable of iterable) {  
  // run code on variable
}

The variable may be declared with const, let, or var, and the iterable is the iterable objects that we are iterating over. We can iterate over asynchronous iterable objects like the following:

const asynNums = {  
  [Symbol.asyncIterator]() {  
    return {  
      i: 6,  
      next() {  
        if (this.i < 20) {  
          return Promise.resolve({  
            value: this.i++,  
            done: false  
          });  
        }
        return Promise.resolve({  
          done: true  
        });  
      }  
    };  
  }  
};

(async () => {  
  for await (let num of asynNums) {  
    console.log(num);  
  }  
})();

We should see 6 to 19 logged. It also works with async generators:

async function* asynNumGenerator() {  
  var i = 6;  
  while (i < 20) {  
    yield i++;  
  }  
}

(async () => {  
  for await (let num of asynNumGenerator()) {  
    console.log(num);  
  }  
})();

We should see the same thing logged. It also works great with promises:

const arr = Array.from({  
  length: 20  
}, (v, i) => i)let promises = [];

for (let num of arr) {  
  const promise = new Promise((resolve, reject) => {  
    setTimeout(() => {  
      resolve(num);  
    }, 100)  
  })  
  promises.push(promise);  
}

(async () => {  
  for await (let num of promises) {  
    console.log(num);  
  }  
})();

As we can see, if we run the code above, we should see 0 to 19 logged sequentially, which means that promises were iterated through in sequence. This is very handy as we never have anything that can iterate through asynchronous code before this loop syntax was introduced.


Promise.finally()

The finally function is added to the Promise object which runs when the promise is settled. That means it runs whenever the promise is fulfilled or rejected.

It takes a callback function that runs whenever the promise is settled. This lets us run code regardless of how a promise ends. Also, this means that we no longer have to duplicate code in the callbacks for then and catch functions.

To use the finally function, we use it as the following:

promise  
.finally(()=>{  
  // run code that when promise is settled  
})

The finally is useful is we want to run code whenever a promise ends like cleanup code or processing after the code ends.

It’s very similar to calling .then(onFinally, onFinally). However, we do not have to declare it twice to make a variable for it and pass it in twice. The callback for the finally function doesn’t take any argument, as there’s no way to determine the status of the promise beforehand.

It’s used for cases when we don’t know what will happen to the promise, so there’s no need to provide any argument. If a promise is fulfilled then the resolved value will be intact after calling the finally function.

For example, if we have:

Promise.resolve(3).then(() => {}, () => {})

This will resolve to undefined, but if we have:

Promise.resolve(3).finally(() => {})

The code above will resolve to 3. Similarly, if the promise if rejected, we get:

Promise.reject(5).finally(() => {})

This will be rejected with the value 5.


SharedArrayBuffer

The SharedArrayBuffer is an object that’s used to represent a fixed-length raw binary data buffer.

It’s similar to the ArrayBuffer object, but we can use it to create views on shared memory. It can’t become detached, unlike an ArrayBuffer. The constructor of it takes the length of the buffer as an argument.

The length is the size in byte for the array buffer to create. It returns a SharedArrayBuffer of the specified size with contents initialized to 0.

Shared memory can be created and updated by multiple workers on the main thread. However, this may take a while until the changes are propagated to all contexts.


Conclusion

With ES2018, we have more handy features to help developers develop JavaScript apps.

The highlights include the spread operator for objects, which let us copy key-value pairs to other objects and the rest operator to let us pass optional arguments.

The for await...of loop lets us iterate through collections of asynchronous code which could never easily be done before.

Categories
JavaScript JavaScript Basics

How to Get the Length of An Object

There are 2 ways to get the length of the list of keys of an object.

Object.keys

Object.keys gets the top level list of keys of an object and returns an array of them. For example:

const a = {foo: 1, bar: 2};  
const length = Object.keys(a).length // 2

Object.getPropertyNames

Object.getPropertyNames also gets a list of all top level of keys of an object and return them as an array. For example:

const a = {foo: 1, bar: 2};  
const length = Object.getOwnPropertyNames(a).length // 2

for…in Loop

There is a special loop for looping through the keys of an object. You can do the following:

const a = {foo: 1, bar: 2};  
let keysCount = 0;  
for (let key in a) {  
    keysCount++;  
}  
console.log(keysCount) // 2
Categories
JavaScript Vue

How to Add Geolocation to a Vue.js App

Many apps want provide an experience based on the user’s location. This is where the HTML Geolocation API comes in. You can use it easily to get the location of the current device.

To get the location of the device in the browser using plain JavaScript, we write:

if (navigator.geolocation) {  
  navigator.geolocation.getCurrentPosition(getPosition);  
}

function getPosition(position) {  
  console.log(position.coords.latitude, position.coords.longitude);  
}

As you can see, getting the latitude and longitude is very easy.

We can also easily add geolocation to any Vue.js app. The vue-browser-geolocation package is a great add-on for adding geolocation capabilities to your app. It provides a promise-based API for getting the location of the device, so we can easily use async and await to get the location with this package.

Getting Started

In this article, we will build an app to get the list of Canadian politicians in your local area if you are in Canada from the Represent Civic Information API.

We will use the vue-browser-geolocation package to get the location, and then use the API to get the list of items from the API. We will get the list of politicians from the local area, the constituent boundaries from the local area, and the list of representatives from the given legislative body selected from a list.

To start, we run the Vue CLI to create the project. Run npx @vue/cli create politicians-app to create the app. In the wizard, we choose ‘Manually select features’, then choose to include Babel, and Vue Router.

Next we install some packages we need to build the app. We will use Axios for making HTTP requests, BootstrapVue for styling, Vue Avatar for showing the avatar picture of the politician, and vue-browser-geolocation to get the location of the device. Run npm i axios bootstrap-vue vue-avatar vue-browser-geolocation to install all the packages.

Making API Requests

Now we can start writing the app. We start by creating a mixin for making HTTP requests that we use in our components. Create a mixins folder in the src folder and create requestsMixin.js in the mixins folder. Then we add the following to the file:

const axios = require("axios");  
const APIURL = "https://represent.opennorth.ca";

export const requestsMixin = {  
  methods: {  
    getRepresentatives(lat, lng) {  
      return axios.get(`${APIURL}/representatives/?point=${lat},${lng}`);  
    }, 

    getBoundaries(lat, lng) {  
      return axios.get(`${APIURL}/boundaries/?contains=${lat},${lng}`);  
    }, 

    getRepresentativeSetsRepresentatives(set, lat, lng) {  
      return axios.get(  
        `${APIURL}/representatives/${set}/?point=${lat},${lng}`  
      );  
    }, 

    getRepresentativeSets() {  
      return axios.get(`${APIURL}/representative-sets/?offset=0&limit=200`);  
    }  
  }  
};

We use the Represent Civic Information API to get the representatives and boundaries by location, and also get the list of legislative bodies in Canada. The full list of endpoints is at https://represent.opennorth.ca/api/#representativeset.

Getting Geolocation

Next create the pages for display the data. In the views folder, create Boundaries.vue and add:

<template>  
  <div class="page">  
    <h1 class="text-center">Your Constituent Boundary</h1>  
    <template v-if="noLocation">  
      <h2 class="text-center">Enable geolocation to see list of boundaries</h2>  
    </template>  
    <template v-if="!noLocation">  
      <b-card v-for="(b, i) in boundaries" :key="i" class="card">  
        <b-card-title>  
          <h2>{{b.name}}</h2>  
        </b-card-title> <b-card-text>  
          <p>  
            <b>Boundary Set Name:</b>  
            {{b.boundary_set_name}}  
          </p>  
        </b-card-text>  
      </b-card>  
    </template>  
  </div>  
</template>

<script>  
import { requestsMixin } from "@/mixins/requestsMixin";

export default {  
  name: "boundaries",  
  mixins: [requestsMixin],  
  data() {  
    return {  
      boundaries: [],  
      noLocation: true  
    };  
  },  
  beforeMount() {  
    this.getBounds();  
  },  
  methods: {  
    async getBounds() {  
      try {  
        const coordinates = await this.$getLocation({  
          enableHighAccuracy: true  
        });  
        const { lat, lng } = coordinates;  
        const response = await this.getBoundaries(lat, lng);  
        this.boundaries = response.data.objects;  
        this.noLocation = false;  
      } catch (error) {  
        this.noLocation = true;  
      }  
    }  
  }  
};  
</script>

In this page, we get the constituency boundaries for the location that the user’s device is currently in with the vue-browser-geolocation package. this.$getLocation in the getBounds function is provided by the package. The promise resolves to an object with the latitude and longitude when geolocation is enabled by the user. We wrap the code with a try...catch in case that it is disabled. If geolocation is disabled, we show a message to let the user know that they have to enable geolocation to see data.

If geolocation is enabled, then this.getBoundaries will be called. The function is provided by the requestsMixin that we created earlier. We included it with the mixin property of this component. The items are displayed in BootstrapVue cards.

Next we replace the code in Home.vue with the following:

<template>  
  <div class="page">  
    <h1 class="text-center">Your Local Representatives</h1>  
    <template v-if="noLocation">  
      <h2 class="text-center">Enable geolocation to see list of representatives</h2>  
    </template>  
    <template v-if="!noLocation">  
      <b-card v-for="(r, i) in reps" :key="i" class="card">  
        <b-card-title>  
          <h2>  
            <avatar :src="r.photo_url" :inline="true"></avatar>  
            <span class="title">{{r.name}}</span>  
          </h2>  
        </b-card-title> <b-card-text>  
          <h5>Offices:</h5>  
          <div v-for="(o,i) in r.offices" :key="i">  
            <p>  
              <b>Address:</b>  
              {{o.postal}}  
            </p>  
            <p>  
              <b>Telephone:</b>  
              {{o.tel}}  
            </p>  
            <p>  
              <b>Type:</b>  
              {{o.type}}  
            </p>  
          </div>  
          <p>  
            <b>Party:</b>  
            {{r.party_name}}  
          </p>  
        </b-card-text> <b-button :href="r.url" variant="primary" target="_blank">Go to Source</b-button>  
      </b-card>  
    </template>  
  </div>  
</template>

<script>  
import { requestsMixin } from "@/mixins/requestsMixin";  
import Avatar from "vue-avatar";

export default {  
  name: "home",  
  mixins: [requestsMixin],  
  data() {  
    return {  
      reps: [],  
      noLocation: true  
    };  
  },  
  components: {  
    Avatar  
  },

  beforeMount() {  
    this.getAllRepresentatives();  
  },  
  methods: {  
    async getAllRepresentatives() {  
      try {  
        const coordinates = await this.$getLocation({  
          enableHighAccuracy: true  
        });  
        const { lat, lng } = coordinates;  
        const response = await this.getRepresentatives(lat, lng);  
        this.reps = response.data.objects;  
        this.noLocation = false;  
      } catch (error) {  
        this.noLocation = true;  
      }  
    }  
  }  
};  
</script>

We use the same this.$geoLocation function as Boundaries.vue. In this page, we get the list of representatives for the location that the user’s device is currently in. The geolocation feature is the same as Boundaries.vue. We run the getAllRepresentatives function in beforeMount so that it loads when the page loads.

The items from the API are displayed in the BootstrapVue cards. We display the picture in an avatar with the vue-avatar package.

Next we create Representatives.vue in the views folder. We add the following code to the file:

<template>  
  <div class="page">  
    <h1 class="text-center">Representative Sets</h1>  
    <template v-if="noLocation">  
      <h2 class="text-center">Enable geolocation to see list of representatives</h2>  
    </template>  
    <template v-if="!noLocation">  
      <b-form>  
        <b-form-group label="Representative Set" label-for="repSet">  
          <b-form-select  
            name="repSet"  
            v-model="form.repSet"  
            :options="repSets"  
            required  
            @change="getRepSetReps()"  
          ></b-form-select>  
        </b-form-group>  
      </b-form>  
      <b-card v-for="(r, i) in reps" :key="i" class="card">  
        <b-card-title>  
          <h2>  
            <avatar :src="r.photo_url" :inline="true"></avatar>  
            <span class="title">{{r.name}}</span>  
          </h2>  
        </b-card-title> <b-card-text>  
          <h5>Info:</h5>  
          <p>  
            <b>Elected Office:</b>  
            {{r.office}}  
          </p>  
          <p>  
            <b>District Name:</b>  
            {{r.district_name}}  
          </p>  
          <p>  
            <b>Party:</b>  
            {{r.party_name || 'None'}}  
          </p>  
          <h5>Offices:</h5>  
          <div v-for="(o,i) in r.offices" :key="i">  
            <p>  
              <b>Address:</b>  
              {{o.postal}}  
            </p>  
            <p>  
              <b>Telephone:</b>  
              {{o.tel}}  
            </p>  
            <p>  
              <b>Type:</b>  
              {{o.type}}  
            </p>  
          </div>  
        </b-card-text>  
      </b-card>  
    </template>  
  </div>  
</template>

<script>  
import { requestsMixin } from "@/mixins/requestsMixin";  
import Avatar from "vue-avatar";

export default {  
  name: "boundaries",  
  mixins: [requestsMixin],  
  data() {  
    return {  
      repSets: [],  
      reps: [],  
      form: {  
        repSet: "strathcona-county-council"  
      },  
      noLocation: true,  
      coordinates: {}  
    };  
  },  
  components: {  
    Avatar  
  },  
  beforeMount() {  
    this.getRepSets();  
    this.getLocation();  
  },  
  methods: {  
    async getRepSets() {  
      const response = await this.getRepresentativeSets();  
      this.repSets = response.data.objects.map(s => {  
        const [part1, part2, value] = s.url.split("/");  
        return {  
          text: s.name,  
          value  
        };  
      });  
    }, 

    async getLocation() {  
      try {  
        const coordinates = await this.$getLocation({  
          enableHighAccuracy: true  
        });  
        this.coordinates = coordinates;  
        this.noLocation = false;  
      } catch (error) {  
        this.noLocation = true;  
      }  
    }, 

    async getRepSetReps() {  
      const { lat, lng } = this.coordinates;  
      const response = await this.getRepresentativeSetsRepresentatives(  
        this.form.repSet,  
        lat,  
        lng  
      );  
      this.reps = response.data.objects;  
    }  
  }  
};  
</script>

When the page loads, we call this.getRepSets and this.getLocation to populate the dropdown with the legislative bodies in Canada and get the location respectively. We get the items in the this.getRepSetReps function with the coordinates provided when the dropdown selection is changed and when geolocation API enabled. We only display the dropdown when geolocation is enabled so that we won’t let the user select anything without it being enabled.

The items are also displayed in BootstrapVue cards in this page.

Then we change the existing code in App.vue to:

<template>  
  <div>  
    <b-navbar toggleable="lg" type="dark" variant="info">  
      <b-navbar-brand href="#">Canadian Politicians App</b-navbar-brand><b-navbar-toggle target="nav-collapse"></b-navbar-toggle><b-collapse id="nav-collapse" is-nav>  
        <b-navbar-nav>  
          <b-nav-item to="/" :active="path == '/'">Home</b-nav-item>  
          <b-nav-item to="/boundaries" :active="path == '/boundaries'">Boundaries</b-nav-item>  
          <b-nav-item to="/representatives" :active="path == '/representatives'">Representatives</b-nav-item>  
        </b-navbar-nav>  
      </b-collapse>  
    </b-navbar>  
    <router-view />  
  </div>  
</template><script>  
export default {  
  data() {  
    return {  
      path: this.$route && this.$route.path  
    };  
  },  
  watch: {  
    $route(route) {  
      this.path = route.path;  
    }  
  }  
};  
</script>

<style>  
.page {  
  padding: 20px;  
  margin: 0 auto;  
}

.card,  
form {  
  max-width: 800px;  
  margin: 0 auto;  
}

.title {  
  position: relative;  
  top: -13px;  
  left: 10px;  
}  
</style>

We add the BootstrapVue toolbar and the links to each page. In the top bar, we set the active prop for the links so that we highlight the link of the current page that is displayed. In the scripts section, we watch the $route object provided by Vue Router for the current path of the app and assign it to this.path so that we can use it to set the active prop.

In the style block, we add padding and margin to the pages with the page class. We set the form and card widths so that they won’t be too wide, and we add the title class so the titles in the cards align with the avatars.

In main.js, we replace the existing code with:

import Vue from 'vue'  
import App from './App.vue'  
import router from './router'  
import store from './store'  
import VueGeolocation from 'vue-browser-geolocation';  
import BootstrapVue from 'bootstrap-vue'  
import 'bootstrap/dist/css/bootstrap.css'  
import 'bootstrap-vue/dist/bootstrap-vue.css'Vue.use(BootstrapVue)  
Vue.use(VueGeolocation);Vue.config.productionTip = falsenew Vue({  
  router,  
  store,  
  render: h => h(App)  
}).$mount('#app')

This adds BootstrapVue library and styles to our app, along with the vue-browser-geolocation package.

In router.js we replace the existing code with:

import Vue from "vue";  
import Router from "vue-router";  
import Home from "./views/Home.vue";  
import Boundaries from "./views/Boundaries.vue";  
import Representatives from "./views/Representatives.vue";

Vue.use(Router);export default new Router({  
  mode: "history",  
  base: process.env.BASE_URL,  
  routes: [  
    {  
      path: "/",  
      name: "home",  
      component: Home  
    },  
    {  
      path: "/boundaries",  
      name: "boundaries",  
      component: Boundaries  
    },  
    {  
      path: "/representatives",  
      name: "representatives",  
      component: Representatives  
    }  
  ]  
});

Now users can go the packages we linked to in the top bar, and also by entering the URLs directly.

Now we run the app by running npm run serve .

Categories
JavaScript JavaScript Basics

Formatting Dates With the DateTimeFormat Object

Different parts of the world have different date formats. To deal with this, JavaScript has the Intl.DateTimeFormat constructor to let us format dates into different formats according to different locales.

This means that we can format dates for different places without manipulating date strings ourselves, making our lives much easier. The Intl.DateTimeFormat constructor takes a locale string as the argument.

With the DateTimeFormat constructed with the constructor, we can use the format instance method, which takes in a Date object to return a date string with the date formatted for the locale you specified in the Intl.DateTimeFormat constructor.


The Constructor and the Format Method

To use the Intl.DateTimeFormat constructor, we can use it like in the following example:

const date = new Date(2019, 0, 1, 0, 0, 0);  
console.log(new Intl.DateTimeFormat('en-US').format(date));  
console.log(new Intl.DateTimeFormat('fr-ca').format(date));  
console.log(new Intl.DateTimeFormat(['ban', 'de']).format(date));

In the above example, we created the Date object, and then we used the Intl.DateTimeFormat constructor with one or more locales passed in as a string.

Then, we called format by passing in the Date object. The first example would log 1/1/2019 since the United States use the MM/DD/YYYY date format.

The second example would log 2019–01–01 since French Canadian dates are in YYYY-MM-DD format. The third example takes an array where there are multiple locales, the ones further to the right are the fallback locales for the ones for the left.

In our example, since we have an invalid locale string ban in the first entry of the array, we use the German date format de instead, to format the Date object, so we get 1.1.2019 since Germany uses the DD.MM.YYYY date format.

As we can see from the examples above, the Intl.DateTimeFormat constructor takes a locale string or an array of locale strings. It also accepts Unicode extension keys for locale strings, so that we can append them to our language tag.

The extension keys nu for setting the numbering system, ca for the calendar to format the date, and hc for the hour cycle are supported.

nu’s possible values can be arab, arabext, bali, beng, deva, fullwide, gujr, guru, hanidec, khmr, knda, laoo, latn, limb, mlym, mong, mymr, orya, tamldec, telu, thai, tibt.

ca’s possible values include buddhist, chinese, coptic, ethiopia, ethiopic, gregory, hebrew, indian, islamic, iso8601, japanese, persian, roc.

hc’s possible values include h11, h12, h23, h24. For example, to format dates according to the Buddhist calendar, we can write:

const date = new Date(2019, 0, 1, 0, 0, 0);  
console.log(new Intl.DateTimeFormat('en-US-u-ca-buddhist').format(date));

Then we get 1/1/2562 in the console.log since the Buddhist calendar’s year 0 is at 545 BC.

The second argument takes an object that lets us set various options in its properties to format the date.

The localeMatcher option specifies the locale-matching algorithm to use. The possible values are lookup and best fit. The lookup algorithm searches for the locale until it finds the one that fits the character set of the strings that are being compared.

best fit finds the locale that is at least, but possibly more, suited than the lookup algorithm. The timeZone option lets us set the time zone which formats the date.

The most basic implementation only recognizes UTC, but others may recognize IANA time zone names in the IANA time zone data database, like Asia/Shanghai, Asia/Kolkata, America/New_York.

The hour12 option specifies whether the format is for 12-hour time or 24-hour time. The default is locale dependent. It overrides the hc language tag in the first argument.

It’s a true or false value where true means format the date time with 12-hour time. The hourCycle option has possible values of h11, h12, h23, h24 and overrides the hc language tag. hour12 takes precedence over this option if it’s specified.

The formatMatcher property specifies the matching algorithm to use. Possible values are basic and best fit. best fit is the default value.

The following subsets of the date time are required to match the date object to the correct format:

  • weekday, year, month, day, hour, minute, second
  • weekday, year, month, day
  • year, month, day
  • year, month
  • month, day
  • hour, minute, second
  • hour, minute

weekday represents the weekday value, possible values are:

  • long (e.g., Friday)
  • short (e.g., Fri)
  • “narrow" (e.g., T). Note that two weekdays may have the same narrow style for some locales. For example, Tuesday‘s narrow style is also T.

era represents the era, possible values are:

  • long (e.g., Anno Domini)
  • short (e.g., AD)
  • narrow (e.g., A)

year represents the year. The possible values are:

  • numeric (e.g., 2019)
  • 2-digit (e.g., 19)

month is the representation of the month. The possible values are:

  • numeric (e.g., 2)
  • 2-digit (e.g., 02)
  • long (e.g., February)
  • short (e.g., Feb)
  • narrow (e.g., M). Note that two months may have the same narrow style for some locales (e.g. May‘s narrow style is also M).

day represents the day. Possible values are:

  • numeric (e.g., 2)
  • 2-digit (e.g., 02)

hour is the representation of the hour. The possible values are numeric, 2-digit.

minute is the representation of the minute. The possible values are numeric, 2-digit.

second is the representation of the second. The possible values are numeric, 2-digit.

timeZoneName is the representation of the time zone name. The possible values are:

  • long (e.g., Pacific Standard Time)
  • short (e.g., GMT-8)

The default value for each component property above is undefined, but if all components are undefined then the year, month, and day are assumed to be numeric.

An example for using the options is below:

const date = new Date(2019, 0, 1, 0, 0, 0);  
const options = {  
  weekday: 'long',  
  year: 'numeric',  
  month: 'long',  
  day: 'numeric'  
};  
console.log(new Intl.DateTimeFormat('en-ca', options).format(date));

In the code above, we set the locale to Canadian English, and in the options object we set weekday to long, year to numeric, month to long, and day to numeric, so that we get the full weekday name in English, year as a number, month with the full name, and day as a number.

The console.log will log “Tuesday, January 1, 2019”. The example above can have a time zone added to it, as in the following example:

const date = new Date(2019, 0, 1, 0, 0, 0);  
const options = {  
  weekday: 'long',  
  year: 'numeric',  
  month: 'long',  
  day: 'numeric',  
  timeZone: 'America/Vancouver',  
  timeZoneName: 'long'  
};  
console.log(new Intl.DateTimeFormat('en-ca', options).format(date));

In the example above, we added the timeZone and timeZoneLong so that we get the time zone displayed, so that we get “Tuesday, January 1, 2019, Pacific Standard Time”.

The Intl.DateTimeFormat constructor handles non-English format equally well. For example, if we want to format a date into Chinese, we can change the locale and keep the options the same as above like in the following code:

const date = new Date(2019, 0, 1, 0, 0, 0);  
const options = {  
  weekday: 'long',  
  year: 'numeric',  
  month: 'long',  
  day: 'numeric',  
  timeZone: 'America/Vancouver',  
  timeZoneName: 'long'  
};  
console.log(new Intl.DateTimeFormat('zh-Hant', options).format(date));

Then, we get “2019年1月1日 星期二 太平洋標準時間”, which is the same as “Tuesday, January 1, 2019, Pacific Standard Time” in traditional Chinese.


Other Instance Methods

Instances of the Intl.DateTimeFormat constructor object also have a few instance methods in addition to the format() method.

It also has the formatToParts() method to return an array of objects representing different parts of the date string. The resolvedOptions() method returns a new object with properties that reflect the locale and formatting options that we computed during the initialization of the object.

The formatRange() method accepts two Date objects as arguments and formats the date range in the most concise way, based on the locale and options provided when we instantiate the DateTimeFormat object.

Finally, it has the formatRangeToParts() method which accepts two Date objects as arguments and formats the date range in the most concise way based on the locale and options provided when we instantiate the DateTimeFormat object and return the date time parts in an array of objects.

For example, if we have the following code that calls the formatRangeToParts() method:

const startDate = new Date(2019, 0, 1, 0, 0, 0);  
const endDate = new Date(2019, 0, 2, 0, 0, 0);  
const options = {  
  weekday: 'long',  
  year: 'numeric',  
  month: 'long',  
  day: 'numeric',  
  timeZone: 'America/Vancouver',  
  timeZoneName: 'long'  
};  
const dateRange = new Intl.DateTimeFormat('zh-Hant', options).formatRangeToParts(startDate, endDate)  
console.log(dateRange);

Then we get the following date and time parts logged:

[  
  {  
    "type": "year",  
    "value": "2019",  
    "source": "startRange"  
  },  
  {  
    "type": "literal",  
    "value": "年",  
    "source": "startRange"  
  },  
  {  
    "type": "month",  
    "value": "1",  
    "source": "startRange"  
  },  
  {  
    "type": "literal",  
    "value": "月",  
    "source": "startRange"  
  },  
  {  
    "type": "day",  
    "value": "1",  
    "source": "startRange"  
  },  
  {  
    "type": "literal",  
    "value": "日 ",  
    "source": "startRange"  
  },  
  {  
    "type": "weekday",  
    "value": "星期二",  
    "source": "startRange"  
  },  
  {  
    "type": "literal",  
    "value": " ",  
    "source": "startRange"  
  },  
  {  
    "type": "timeZoneName",  
    "value": "太平洋標準時間",  
    "source": "startRange"  
  },  
  {  
    "type": "literal",  
    "value": " – ",  
    "source": "shared"  
  },  
  {  
    "type": "year",  
    "value": "2019",  
    "source": "endRange"  
  },  
  {  
    "type": "literal",  
    "value": "年",  
    "source": "endRange"  
  },  
  {  
    "type": "month",  
    "value": "1",  
    "source": "endRange"  
  },  
  {  
    "type": "literal",  
    "value": "月",  
    "source": "endRange"  
  },  
  {  
    "type": "day",  
    "value": "2",  
    "source": "endRange"  
  },  
  {  
    "type": "literal",  
    "value": "日 ",  
    "source": "endRange"  
  },  
  {  
    "type": "weekday",  
    "value": "星期三",  
    "source": "endRange"  
  },  
  {  
    "type": "literal",  
    "value": " ",  
    "source": "endRange"  
  },  
  {  
    "type": "timeZoneName",  
    "value": "太平洋標準時間",  
    "source": "endRange"  
  }  
]

As we can see from the console output above, we get the date and time parts in each entry of the array, with the parts of the startDate coming first and the endDate parts being in the latter parts of the array.

If we call the formatRange() method, like in the code below:

const startDate = new Date(2019, 0, 1, 0, 0, 0);  
const endDate = new Date(2019, 0, 2, 0, 0, 0);  
const options = {  
  weekday: 'long',  
  year: 'numeric',  
  month: 'long',  
  day: 'numeric',  
  timeZone: 'America/Vancouver',  
  timeZoneName: 'long'  
};  
const dateRange = new Intl.DateTimeFormat('en', options).formatRange(startDate, endDate)  
console.log(dateRange);

Then we get:

"Tuesday, January 1, 2019, Pacific Standard Time – Wednesday, January 2, 2019, Pacific Standard Time"

From the console.log.

To call the resolvedOptions method, we can write the code below:

const date = new Date(2019, 0, 1, 0, 0, 0);  
const options = {  
  weekday: 'long',  
  year: 'numeric',  
  month: 'long',  
  day: 'numeric',  
  timeZone: 'America/Vancouver',  
  timeZoneName: 'long'  
};  
const resolvedOptions = new Intl.DateTimeFormat('en', options).resolvedOptions(date)  
console.log(resolvedOptions);

Then we get the options that we passed in for formatting the date back:

{  
  "locale": "en",  
  "calendar": "gregory",  
  "numberingSystem": "latn",  
  "timeZone": "America/Vancouver",  
  "weekday": "long",  
  "year": "numeric",  
  "month": "long",  
  "day": "numeric",  
  "timeZoneName": "long"  
}

In the console.log.

To use the formatToParts() method, we can use it like the format() method, like in the following code:

const date = new Date(2019, 0, 1, 0, 0, 0);  
const options = {  
  weekday: 'long',  
  year: 'numeric',  
  month: 'long',  
  day: 'numeric',  
  timeZone: 'America/Vancouver',  
  timeZoneName: 'long'  
};  
const dateParts = new Intl.DateTimeFormat('en', options).formatToParts(date)  
console.log(dateParts);

Then we get the parts of the formatted date in the console.log like in the output below:

[  
  {  
    "type": "weekday",  
    "value": "Tuesday"  
  },  
  {  
    "type": "literal",  
    "value": ", "  
  },  
  {  
    "type": "month",  
    "value": "January"  
  },  
  {  
    "type": "literal",  
    "value": " "  
  },  
  {  
    "type": "day",  
    "value": "1"  
  },  
  {  
    "type": "literal",  
    "value": ", "  
  },  
  {  
    "type": "year",  
    "value": "2019"  
  },  
  {  
    "type": "literal",  
    "value": ", "  
  },  
  {  
    "type": "timeZoneName",  
    "value": "Pacific Standard Time"  
  }  
]

As we can see, the Intl.DateTimeFormat constructor is very useful for formatting dates for different locales. It eliminates a lot of hassle when formatting dates.

The constructor takes one or more locales as the first argument and a variety of options as the second argument.

It can format dates into one string and also format them and break them up into parts. This saves a lot of hassle when formatting dates for different regions since we don’t have to do any date string manipulation.