Categories
Express JavaScript Nodejs

Serve Favicons in Express Apps with serve-favicon

If we have an Express app that renders templates, we can use the serve-favicon middleware to serve Favicons.

Favicons are icons on the left side of a browser tab.

In this article, we’ll look at how to use it to display our favorite favicon in users’ browsers.

Why do we need this module?

The serve-favicon module lets us exclude requests for the favicon in our logger middleware.

It also caches the icon in memory to improve performance by reducing disk access.

In addition, it provides an ETag based on the contents of the icon, rather than file system properties.

Finally, this module serves with the most compatible Content-Type .

This module is exclusive for serving the favicon with the GET /favicon.ico request.

Installation

We can install it by running:

npm install serve-favicon

Options

The favicon function is provided by this module. It takes 2 arguments, which are path, and options and returns a middleware to serve a favicon from the given path .

The options object takes one property, which is the maxAge property. It sets the cache-control max-age directive in milliseconds. The default is 1 yeat. It also can be a string that’s accepted by the ms module.

Examples

Simple Usage

The serve-favicon middleware should come before other middleware so that it won’t conflict with other requests for favicon.ico .

For instance, we can load a favicon with serve-favicon as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const favicon = require('serve-favicon')  
const path = require('path')  
const app = express();  
app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')))  
app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static('public'));
app.get('/', (req, res) => {  
  res.sendFile('public/index.html');  
});
app.listen(3000);

Then we get:

https://thewebdev.info/wp-content/uploads/2020/04/icon.png

Setting Max Age

We can also set the maxAge option as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const favicon = require('serve-favicon')  
const path = require('path')  
const app = express();  
const iconPath = path.join(__dirname, 'public', 'favicon.ico');  
const options = {  
  maxAge: 200 * 60 * 60 * 24 * 1000  
}  
app.use(favicon(iconPath, options));  
app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static('public'));app.get('/', (req, res) => {  
  res.sendFile('public/index.html');  
});
app.listen(3000);

Then we set the expiry of the icon to 200 days.

Conclusion

We can use the serve-favicon middleware to cache requests for favicon and serve it instead of loading it every time when users make a request for the favicon.

The expiry can also be set so the favicon request result won’t be cached forever.

It should be included before other middleware so that it won’t conflict with other middleware that makes request for a favicon.

Categories
JavaScript Vue

Introduction to Vue.js Computed Properties and Watchers

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

In this article, we’ll look at Vue.js computed properties and watchers.

Computed Properties

To make more template expressions more concise and reusable, we can create computed properties to compute results from existing properties as their value changes.

We can define computed properties in a Vue instance by putting them in the computed property of the options object that we pass into the Vue constructor.

For example, we can write the following in src/index.js:

new Vue({  
  el: "#app",  
  data: {  
    message: "foo"  
  },  
  computed: {  
    spelledMessage() {  
      return this.message.split("").join("-");  
    }  
  }  
});

Then we can use it like any other field in our HTML template. So in index.html, we can write:

<!DOCTYPE html>  
<html>  
  <head>  
    <title>Hello</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <p>{{message}}</p>  
      <p>{{spelledMessage}}</p>  
    </div>  
    <script src="./src/index.js"></script>  
  </body>  
</html>

We should then get:

foof-o-o

on our screen.

The spelledMessage method is a getter that’s used by Vue.js. It takes the returned value of it and then put it in place of {{spelledMessage}} .

Therefore, the name of the placeholder in the template must be the same as the name of the method that returns the value that we want in the computed object.

We can also get the value from the returned Vue instance as follows:

const vm = new Vue({  
  el: "#app",  
  data: {  
    message: "foo"  
  },  
  computed: {  
    spelledMessage() {  
      return this.message.split("").join("-");  
    }  
  }  
});console.log(vm.spelledMessage);

We should see f-o-o logged from the console.log.

We can bind to computed properties in templates like any other property. Vue knows that vm.spelledMessage depends on vm.message, so the bindings will be updated when vm.message is updated.

The computed getter method doesn’t commit side effects, which makes it easy to test and understand.

Computed Caching vs Methods

One good feature of computed properties is that the result of it is cached.

There’s no caching for results returned from methods.

So instead of using methods, we should use computed properties for cases where we’re computing something from existing properties.

A computed property is only re-evaluated once a reactive property is updated.

For example, if we have:

const vm = new Vue({  
  el: "#app",  
  data: {  
    message: "foo"  
  },  
  computed: {  
    now() {  
      return Date.now();  
    }  
  }  
});

It’ll never update after its first computed since it doesn’t depend on any reactive property from the data property.

Without caching computed properties keep getting computed as reactive properties that they depend on change, which means the app gets slow as reactive properties that the computed property depends on are changing.

Computed vs Watched Property

Watch properties are useful for watching for changes in a property as the name suggests.

However, because of the caching feature of computed properties, we should use them as much as we can.

For instance, we can simplify the following:

new Vue({  
  el: "#app",  
  data: {  
    name: "Joe",  
    age: 1,  
    person: undefined  
  },  
  watch: {  
    name: {  
      immediate: true,  
      handler(newVal) {  
        this.person = `${newVal} - ${this.age}`;  
      }  
    },  
    age: {  
      immediate: true,  
      handler(newVal) {  
        this.person = `${this.name} - ${newVal}`;  
      }  
    }  
  }  
});

to:

new Vue({  
  el: "#app",  
  data: {  
    name: "Joe",  
    age: 1  
  },  
  computed: {  
    person() {  
      return `${this.name} - ${this.age}`;  
    }  
  }  
});

As we can see, the first example was much more complex. We have to set immediate to true in each property so that it’ll watch the initial value change. Then we have to define the value change handler for each reactive property.

Then in each handler, we have to set the person property so that we can combine name and age.

On the other hand, with computed properties, we only have one method which returns the fields combined in the way we want.

It’s much more convenient for us and the app that we create is also faster because of caching.

Computed Setter

We can also create a setter function for computed properties.

For example, we can write:

new Vue({  
  el: "#app",  
  data: {  
    name: "Joe",  
    age: 1  
  },  
  computed: {  
    person: {  
      get() {  
        return `${this.name} - ${this.age}`;  
      },  
      set(newValue) {  
        const [name, age] = newValue.split("-");  
        this.name = name.trim();  
        this.age = age.trim();  
      }  
    }  
  }  
});

The getter function returns the same thing as before, but now we also have a setter function.

The setter function splits the newValue, which has the computed value from the getter function, we split the result and set the result back to the corresponding reactive properties.

We can use a setter as follows. We can put the following in src/index.js:

new Vue({  
  el: "#app",  
  data: {  
    firstName: "Joe",  
    lastName: "Smith"  
  },  
  computed: {  
    name: {  
      get() {  
        return `${this.firstName} ${this.lastName}`;  
      },  
      set(newValue) {  
        const [firstName, lastName] = newValue.split(" ");  
        this.firstName = (firstName || "").trim();  
        this.lastName = (lastName || "").trim();  
      }  
    }  
  }  
});

And in index.html, we put:

<!DOCTYPE html>  
<html>  
  <head>  
    <title>Hello</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <p>{{name}}</p>  
      <input type="text" v-model="name" />  
    </div>  
    <script src="./src/index.js"></script>  
  </body>  
</html>

Then when we change the value in the input, we get the new values assigned back to the firstName and lastName fields with the setter.

Conclusion

We can use computed properties to compute values from existing properties.

Returned values from computed properties are cached to reduce the amount of computation required.

Computed properties can also have setters, where we can do things with the new values returned from the computed property getter.

Categories
JavaScript Vue

Vue.js Basics — The Vue Instance

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

In this article, we’ll look more closely at the Vue instance, including how to define it and some properties of it.

Characteristics of the Vue Instance

Each Vue.js app begins by defining a new Vue instance. The Vue constructor takes an options object that takes various properties.

We often use vm to refer to a Vue instance, where vm stands for ViewModel.

A Vue app roughly follows the Model-View-ViewModel pattern, where the ViewModel has the business logic of the app, View has the markup that users see, and Model has the data.

For example, we can define a Vue instance as follows:

const vm = new Vue({ });

Each Vue app consists of a root Vue instance and it’s created with new Vue . We can organize it in a tree for easier maintenance.

Data and Methods

The options object that pass into the Vue constructor can have data and methods.

For example, if we define a Vue instance as follows:

const vm = new Vue({  
  el: "#app",  
  data: { foo: "bar" }  
});

Then when we add:

console.log(vm.foo);

below our vm definition, we get 'bar' since data.foo has the value 'bar' .

In other words, if we have:

const data = { foo: "bar" };  
const vm = new Vue({  
  el: "#app",  
  data  
});  
console.log(vm.foo === data.foo);

Then the console.log will log true .

When the data changes, the app will re-render with the new data.

If we create a new property in vm and set it as follows:

let data = { foo: "bar" };  
const vm = new Vue({  
  el: "#app",  
  data  
});  
vm.a = 1;

The app won’t re-render. On the other hand, if we write:

let data = { foo: "bar", a: 1 };  
const vm = new Vue({  
  el: "#app",  
  data  
});

Then the app will re-render. This means that we have to put our data that we want to render in the data field.

If we freeze the object that we pass to data with Object.freeze() , then the Vue app won’t re-render since properties can’t be changed, so new changes can’t propagate since they aren’t set in the object.

So if we have:

let data = { foo: "bar", a: 1 };  
Object.freeze(data);  
const vm = new Vue({  
  el: "#app",  
  data  
});

No changes can be made after the initial render since we froze data with Object.freeze .

The Vue instance also exposes a number of instance properties and methods.

They’re prefixed with the $ so that we know they’re part of the Vue instance.

$el

We have the $el property to get the DOM element that the Vue instance resides in.

For example, if we have:

let data = { foo: "bar" };  
const vm = new Vue({  
  el: "#app",  
  data  
});

console.log(vm.$el === document.getElementById("app"));

Then the console.log will log true since our Vue instance resides in an element with ID app .

$data

The $data property will get us the value of the data property that we set in the options object that we passed into the Vue constructor.

So if we have:

let data = { foo: "bar" };  
const vm = new Vue({  
  el: "#app",  
  data  
});

console.log(vm.$data === data);

Then we get true from the console.log since data references the same object as vm.$data since we set it as the value of the data property of the options object that we passed into the Vue constructor.

$watch

$watch is an instance method that lets us watch for changes in the data object that we set as the value of the options object.

For example, if we have:

let data = { foo: "bar" };  
const vm = new Vue({  
  el: "#app",  
  data  
});

vm.$watch("foo", (newValue, oldValue) => {  
  console.log(newValue, oldValue);  
});

in src/index.js and:

<!DOCTYPE html>  
<html>  
  <head>  
    <title>Hello</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <input type="text" v-model="foo" />  
    </div>  
    <script src="./src/index.js"></script>  
  </body>  
</html>

in index.html . Then when we type in something to the input box, we should get some console.log output from:

vm.$watch("foo", (newValue, oldValue) => {  
  console.log(newValue, oldValue);  
});

This is because changes are constantly being watched in the data.foo property. v-model automatically updates the value for foo as we type into the box.

So as we’re typing, the changes to foo are being watched and it’s logged by the handler function that we passed into the $watch method.

Conclusion

The Vue instance is created by passing in an options object with the data , el , and methods properties to the Vue constructor. It returns an instance of our Vue app.

The instance has the $el property to get the DOM element that our app resides in.

Also, there’s the $data property to get the value of the data property to get the data that we set when we pass it in as the value of the data property of the options object.

Finally, we have the $watch method to watch for changes in our data.

Categories
JavaScript Testing

Running Repetitive Test Code with Jest

In our unit tests, we often have to write test code that runs before and after each test. We’ve to write them often to run code to set up fixtures before tests and run code to clean up everything after tests.

We can easily do this with Jest since it comes with a few hooks to do this.

In this article, we’ll look at how to write repetitive setup and teardown code in a way that isn’t repetitive.

How to Write Setup and Teardown Code

With Jest, we can write setup and teardown code by using the beforeEach and afterEach hooks.

beforeEach runs code before each test, and afterEach runs code after each test. The order applies inside a describe block and if there’s no describe block, for the whole file.

For example, given that we have the following code in example.js :

const getStorage = (key) => localStorage.getItem(key);  
const setStorage = (key, value) => localStorage.setItem(key, value);  
module.exports = {  
    getStorage,  
    setStorage  
}

We write the test for them as follows:

const { getStorage, setStorage } = require('./example');

beforeEach(() => {  
    localStorage.setItem('foo', 1);  
    expect(localStorage.getItem('bar')).toBeNull();  
});

afterEach(() => {  
    localStorage.clear();  
});

test('getStorage("foo") is 1', () => {  
    expect(getStorage('foo')).toBe('1');  
});

test('setStorage saves data to local storage', () => {  
    setStorage('bar', 2);  
    const bar = +localStorage.getItem('bar');  
    expect(getStorage('foo')).toBe('1');  
    expect(bar).toBe(2);  
});

We pass a callback function into the beforeEach hook to run code before each test that we have below.

In this example, we prepopulate local storage with an item with key 'foo' and value '1' .

We also check that local storage doesn’t have the item with key 'bar’ with:

expect(localStorage.getItem('bar')).toBeNull();

All tests will pass with the expect we have above since we’ll run localStorage.clear() in the afterEach hook.

Likewise, we pass in a callback to afterEach to run code after each test is run.

We clear local storage with:

localStorage.clear();

Then when running the tests, we get that item with key'bar' is null since we didn’t populate it in the first test.

In the second test, we’ll get that expect(getStorage(‘foo’)).toBe(‘1’); passing since we populated the local storage with it in our beforeEach hook.

Then since we ran:

setStorage('bar', 2);

to to save an item with key 'bar' and value '2' , we’ll get that:

expect(bar).toBe(2);

passing since we saved the item in the test.

Asynchronous Code

In the example above, we ran synchronous code in our hooks. We can also run asynchronous code in our hooks if they either take a done parameter or return a promise.

If we want to run a function that takes a callback and runs it asynchronously as follows:

const asyncFn = (callback) => {  
    setTimeout(callback, 500);  
}

Then in the beforeEach hook we can run asyncFn as follows:

const { asyncFn } = require('./example');

beforeEach((done) => {  
    const callback = () => {  
        localStorage.setItem('foo', 1);  
        done();  
    }  
    asyncFn(callback);  
    expect(localStorage.getItem('bar')).toBeNull();  
});

It does the same thing as the previous beforeEach callback except it’s done asynchronously. Note that we call done passed in from the parameter in our callback.

If we omit, it, the tests will fail as they time out. The code in the tests is the same as before.

We can wait for promises to resolve by returning it in the callback we pass into the hooks.

For example, in example.js , we can write the following function to run a promise:

const promiseFn = () => {  
    return new Promise((resolve) => {  
        localStorage.setItem('foo', 1);  
        resolve();  
    });  
}

Then we can put promiseFn in module.exports and then run it in our beforeEach by running:

beforeEach(() => {  
    expect(localStorage.getItem('bar')).toBeNull();  
    return promiseFn();  
});

We ran promiseFn which we imported before this hook and we return the promise returned by that function, which sets the local storage like the first example except it’s done asynchronously.

Then after we put everything together, we have the following code in example.js , which we run in our test code in the hooks and for testing:

const getStorage = (key) => localStorage.getItem(key);  
const setStorage = (key, value) => localStorage.setItem(key, value);  
const asyncFn = (callback) => {  
    setTimeout(callback, 500);  
}  
const promiseFn = () => {  
    return new Promise((resolve) => {  
        localStorage.setItem('foo', 1);  
        resolve();  
    });  
}  
module.exports = {  
    getStorage,  
    setStorage,  
    asyncFn,  
    promiseFn  
}

Then we have asyncExample.test.js to change the hook to be asynchronous:

const { getStorage, setStorage, asyncFn } = require('./example');

beforeEach((done) => {  
    const callback = () => {  
        localStorage.setItem('foo', 1);  
        done();  
    }  
    asyncFn(callback);  
    expect(localStorage.getItem('bar')).toBeNull();  
});

afterEach(() => {  
    localStorage.clear();  
});

test('getStorage("foo") is 1', () => {  
    expect(getStorage('foo')).toBe('1');  
});

test('setStorage saves data to local storage', () => {  
    setStorage('bar', 2);  
    const bar = +localStorage.getItem('bar');  
    expect(getStorage('foo')).toBe('1');  
    expect(bar).toBe(2);  
});

Then in example.test.js we have:

const { getStorage, setStorage } = require('./example');

beforeEach(() => {  
    localStorage.setItem('foo', 1);  
    expect(localStorage.getItem('bar')).toBeNull();  
});

afterEach(() => {  
    localStorage.clear();  
});

test('getStorage("foo") is 1', () => {  
    expect(getStorage('foo')).toBe('1');  
});

test('setStorage saves data to local storage', () => {  
    setStorage('bar', 2);  
    const bar = +localStorage.getItem('bar');  
    expect(getStorage('foo')).toBe('1');  
    expect(bar).toBe(2);  
});

and finally in promiseExample.test.js we have:

const { getStorage, setStorage, promiseFn } = require('./example');

beforeEach(() => {  
    expect(localStorage.getItem('bar')).toBeNull();  
    return promiseFn();  
});

afterEach(() => {  
    localStorage.clear();  
});

test('getStorage("foo") is 1', () => {  
    expect(getStorage('foo')).toBe('1');  
});

test('setStorage saves data to local storage', () => {  
    setStorage('bar', 2);  
    const bar = +localStorage.getItem('bar');  
    expect(getStorage('foo')).toBe('1');  
    expect(bar).toBe(2);  
});

BeforeAll and AfterAll

To only run the setup and teardown code once in each file, we can use the beforeAll and afterAll hooks. We can pass in a callback with the code that we want to run like we did with the beforeEach and afterEach hooks.

The only difference is that the callbacks are run once before the test in a file is run and after the test in a file is run instead of running them before and after each test.

Callback of beforeAll runs after the callback for beforeEach and callback for afterAll runs after the callback forafterEach .

The order applies inside a describe block and if there’s no describe block, for the whole file.

Running Only One Test

We can write test.only instead of test to run only one test. This is handy for troubleshooting since we don’t have to run all the tests and it helps us pin issues with the test that’s failing.

To write test code that needs to be run for all tests, we use the beforeEach and afterEach hooks in Jest.

To write test code that’s only run per describe block or file, we can use the beforeAll and afterAll hooks.

Callback of beforeAll runs after the callback for beforeEach and callback for afterAll runs after the callback forafterEach .

Categories
JavaScript

More Things we can do with JavaScript Regular Expressions

Regular expressions let us manipulate strings with ease. They are patterns that let us match the text in pretty much in any way we imagine. Without it, we would have trouble searching for text with complex patterns. It’s also useful to check inputs against regular expressions for form validation. In this article, we’ll look at advanced special characters that are part of regular expressions and also JavaScript regular expression methods.

We can define JavaScript regular expressions with the literal as follows:

const re = /a/

Or, we can use the regular expression constructor to define the regular expression object by passing in a string to the RegExp constructor as follows:

const re = new RegExp('a');

More Special Characters

There’re more special characters that we can use to form regular expressions:

[^xyz]

Matches whatever character isn’t in the brackets and shows up up first. For example, if we have the string xylophone and the pattern [^xyz] , then it matches l since it’s the first character that’s not x , y or x in xylohphone .

[\b]

This pattern matches a backspace character.

\b

This pattern matches a word boundary. A word boundary is the position of a string that’s between a word character followed by a non-word character.

For example, if we have the pattern \babc and the string abc , then we get abc as the match since we have \b at the beginning of the string.

If we have abc\b , then we get abc as the match for abc 1 since \b matches the boundary at the end of the string as we have space after abc .

abc\babc won’t match anything because we have word characters before and after the \b so there’s no word boundary,

\B

Matches a non-word boundary. It matches anything before the first character of the string, after the last character of a string, between 2-word characters, between 2 non-word characters or an empty string.

For example, if we have the pattern ab\B. and the string abc 1 , then abc will be the match.

\cX

This pattern matches a control character of a string, where X is A to Z.

\d

Matches any digit. It’s the same as [0-9] For example, \d matches 1 in 123.

\D

This pattern matches any non-digit characters. It’s the same as [^0-9] . For example, \D matches a in the string abc123 .

\f

Matches the form feed character.

\n

Matches the line feed character.

\r

Matches the carriage return character.

\s

Matches a white space character.

\S

Matches any non-whitespace character.

\t

Matches the tab character.

\v

Matches the vertical tab character.

\W

Matches any non-word character. It’s the same as [^A-Za-z0-9_] . For example, if we have the string /. , then we get the / as the match.

\n

If n is a positive integer, then it’s a backreference to the last substring that matches the n capturing group.

For example, if we have a regular expression a(_)b\1 and the string a_b_c , then we get the matches a_b_ and _ since we have the _ in both substrings.

\0

Matches the null character.

\xhh

Matches the character with the code hh where hh is 2 decimal digits. For example, since the hexadecimal code for © is A9 , we can match it with the pattern \xA9 .

\uhhhh

Matches the character with the code hhhh , where hhhh is 4 hexadecimal digits.

\u{hhhh}

Matches the character with the Unicode value hhhh , where hhhh is 4 hexadecimal digits.

Regular Expression Methods

The JavaScript regular expression object has a few methods that let us do various things with regular expressions like searching strings, testing if strings match a pattern, replace strings and so on.

The methods are below.

exec

The exec method searches for a match of the regular expression in a string. It returns the results array or null if there’re no matches.

For example, if we write:

/[a-z0-9.]+@[a-z0-9.]+.[a-z]/.exec('[abc@abc.com](mailto:abc@abc.com)')

Then we get back:

["[abc@abc.com](mailto:abc@abc.com)", index: 0, input: "[abc@abc.com](mailto:abc@abc.com)", groups: undefined]

We can also use the g flag to search for the whole string for the match. For example, we can write:

/\d+/ig.exec('123')

Then we get 123 as the match.

test

The test method executes a search for a match between a regular expression and the specified string. It returns true if there’s a match and false otherwise.

For example, /foo/.test(‘foo’) returns true , but /foo/.test(‘bar’) returns false .

match

The match method returns all matches of a string against a regular expression.

For example, if we write:

'table tennis'.match(/[abc]/g)

Then we get back:

["a", "b"]

since we have the g flag to search the whole string for matches. If we remove the g flag, then we get the first match only. For instance, if we write:

'table tennis'.match(/[abc]/)

Then we get back:

["a", index: 1, input: "table tennis", groups: undefined]

**matchAll**

The matchAll method returns all matches of a string against a regular expression in a iterator which lets us get the results with the spread operator or a for...of loop.

For example, if we write:

[...'table tennis'.matchAll(/[abc]/g)]

Then we get back:

0: ["a", index: 1, input: "table tennis", groups: undefined]  
1: ["b", index: 2, input: "table tennis", groups: undefined]

since we have the g flag to search the whole string for matches. If we remove the g flag, then we get the first match only. For instance, if we write:

[...'table tennis'.matchAll(/[abc]/)]

Then we get back:

0: ["a", index: 1, input: "table tennis", groups: undefined]

search

The search method gets the index of the match of the string. It returns -1 is no match is found. For example, we can write:

'table tennis'.search(/[abc]/)

Then we get back 1 since we have a as the second character of 'table tennis' .

However, if we have:

'table tennis'.search(/[xyz]/)

Then we get back -1 because all 3 letters don’t exist in 'table tennis' .

replace

The replace method finds matches in a string and then replace the matches with the string that we specify.

For example, if we have:

'table tennis'.replace(/[abc]/, 'z')

Then we get:

"tzble tennis"

split

We can use the split method to split a string according to the pattern that we put in as the delimiter. It returns an array of strings that are split from the original string according to the matches of the regular expression.

For example, if we have:

'a 1 b 22 c 3'.split(/\d+/)

Then we get back:

["a ", " b ", " c ", ""]

As we can see, there’re lots of characters and combinations of them that we can search for with JavaScript regular expressions. It lets us do string validation and manipulation very easy since we don’t have to split them and check them. All we have to do is to search for them with regular expression objects.

Likewise, splitting and replace strings by complex patterns is also made easy with regular expression objects and their split and replace methods respectively.