Categories
JavaScript Vue

How to Make a Vue.js App with Buefy Widget Library

Buefy is a lightweight UI component library for Vue.js. It is based on the Bulma CSS framework, which is a framework similar to Bootstrap and Material Design libraries like Vuetify and Vue Material. It provides components like form inputs, tables, modals, alerts, etc, which are the most common components that Web apps use. The full list of components is located at https://buefy.org/documentation.

In this article, we will build a password manager using Buefy and Vue.js. It is a simple app with inputs for entering name, URL, username, and password. The user can edit or delete any entry they entered.

Getting Started

To start building the app, we run the Vue CLI to scaffold the project. We run npx @vue/cli create password-manager to generate the app. In the wizard, we choose ‘Manually select features’ and select Babel, Vuex, and Vue Router.

Next, we install some libraries that we use. We need Axios for making HTTP requests, the Buefy library, and Vee-Validate for form validation. To install them, we run:

npm i axios buefy vee-validate

Building the App

After installing the libraries, we can start building our app. First, in the components folder, create a file namedPasswordForm.vue and add:

<template>  
  <ValidationObserver ref="observer" v-slot="{ invalid }">  
    <form @submit.prevent="onSubmit" novalidate>  
      <ValidationProvider name="name" rules="required" v-slot="{ errors }">  
        <b-field  
          label="Name"  
          :type="errors.length > 0 ? 'is-danger': '' "  
          :message="errors.length > 0 ? 'Name is required': ''"  
        >  
          <b-input type="text" name="name" v-model="form.name"></b-input>  
        </b-field>  
      </ValidationProvider> <ValidationProvider name="url" rules="required|url" v-slot="{ errors }">  
        <b-field  
          label="URL"  
          :type="errors.length > 0 ? 'is-danger': '' "  
          :message="errors.join('. ')"  
        >  
          <b-input type="text" name="url" v-model="form.url"></b-input>  
        </b-field>  
      </ValidationProvider> <ValidationProvider name="username" rules="required" v-slot="{ errors }">  
        <b-field  
          label="Username"  
          :type="errors.length > 0 ? 'is-danger': '' "  
          :message="errors.length > 0 ? 'Username is required': ''"  
        >  
          <b-input type="text" name="username" v-model="form.username"></b-input>  
        </b-field>  
      </ValidationProvider> <ValidationProvider name="password" rules="required" v-slot="{ errors }">  
        <b-field  
          label="Password"  
          :type="errors.length > 0 ? 'is-danger': '' "  
          :message="errors.length > 0 ? 'Password is required': ''"  
        >  
          <b-input type="password" name="password" v-model="form.password"></b-input>  
        </b-field>  
      </ValidationProvider> 

      <br /> 

      <b-button type="is-primary" native-type="submit" style="margin-right: 10px">Submit</b-button> 

      <b-button type="is-warning" native-type="button" @click="cancel()">Cancel</b-button>  
    </form>  
  </ValidationObserver>  
</template>

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

export default {  
  name: "PasswordForm",  
  mixins: [requestsMixin],  
  props: {  
    edit: Boolean,  
    password: Object  
  },  
  methods: {  
    async onSubmit() {  
      const isValid = await this.$refs.observer.validate();  
      if (!isValid) {  
        return;  
      }
      if (this.edit) {  
        await this.editPassword(this.form);  
      } 
      else {  
        await this.addPassword(this.form);  
      }  
      const response = await this.getPasswords();  
      this.$store.commit("setPasswords", response.data);  
      this.$emit("saved");  
    },  
    cancel() {  
      this.$emit("cancelled");  
    }  
  },  
  data() {  
    return {  
      form: {}  
    };  
  },  
  watch: {  
    password: {  
      handler(p) {  
        this.form = JSON.parse(JSON.stringify(p || {}));  
      },  
      deep: true,  
      immediate: true  
    }  
  }  
};  
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->  
<style scoped lang="scss">  
</style>

This component has the form for users to enter a password entry. We use the ValidationObserver component to watch for the validity of the form inside the component and ValidationProvider to check for the validation rule of the inputted value of the input inside the component.

Inside the ValidationProvider, we have our Buefy b-field input. We get the errors array from the ValidationProvider ‘s slot and check if any errors exist in the type and message props. The label prop corresponds to the label tag of the input. The b-input is the actual input field. We bind to our form model here.

Below the inputs, we have the b-button components, which are rendered as buttons. We use the native-type prop to specify the type of the button, and the type prop is used for specifying the style of the button.

Once the user clicks Save, the onSubmit function is called. Inside the function, this.$refs.observer.validate(); is called to check for form validity. observer is the ref of the ValidationObserver . The observed form of validity value is here. If it resolves to true , then we call editPassword or addPassword depending if the edit prop is true. These 2 functions are from requestsMixin which we will create later. If that succeeds, then we call getPasswords which is also from the mixin, and then this.$store.commit is called to store the latest password entries in our Vuex store. After that, we emit the saved event to close the modal that the form is in.

Next, we create a mixins folder in the src folder and then create requestsMixin.js in the mixins folder. Then we add:

const APIURL = "http://localhost:3000";  
const axios = require("axios");
export const requestsMixin = {  
  methods: {  
    getPasswords() {  
      return axios.get(`${APIURL}/passwords`);  
    }, 

    addPassword(data) {  
      return axios.post(`${APIURL}/passwords`, data);  
    }, 

    editPassword(data) {  
      return axios.put(`${APIURL}/passwords/${data.id}`, data);  
    }, 

    deletePassword(id) {  
      return axios.delete(`${APIURL}/passwords/${id}`);  
    }  
  }  
};

This adds the code to make requests to the back end to save our password data.

Next in Home.vue, we replace the existing code with:

<template>  
  <div class="page">  
    <h1 class="center">Password Manager</h1>  
    <b-button @click="openAddModal()">Add Password</b-button> <b-table :data="passwords">  
      <template scope="props">  
        <b-table-column field="name" label="Name">{{props.row.name}}</b-table-column>  
        <b-table-column field="url" label="URL">{{props.row.url}}</b-table-column>  
        <b-table-column field="username" label="Username">{{props.row.username}}</b-table-column>  
        <b-table-column field="password" label="Password">******</b-table-column>  
        <b-table-column field="edit" label="Edit">  
          <b-button @click="openEditModal(props.row)">Edit</b-button>  
        </b-table-column>  
        <b-table-column field="delete" label="Delete">  
          <b-button @click="deleteOnePassword(props.row.id)">Delete</b-button>  
        </b-table-column>  
      </template>  
    </b-table> <b-modal :active.sync="showAddModal" :width="500" scroll="keep">  
      <div class="card">  
        <div class="card-content">  
          <h1>Add Password</h1>  
          <PasswordForm @saved="closeModal()" @cancelled="closeModal()" :edit="false"></PasswordForm>  
        </div>  
      </div>  
    </b-modal> <b-modal :active.sync="showEditModal" :width="500" scroll="keep">  
      <div class="card">  
        <div class="card-content">  
          <h1>Edit Password</h1>  
          <PasswordForm  
            @saved="closeModal()"  
            @cancelled="closeModal()"  
            :edit="true"  
            :password="selectedPassword"  
          ></PasswordForm>  
        </div>  
      </div>  
    </b-modal>  
  </div>  
</template>

<script>  
// @ is an alias to /src  
import { requestsMixin } from "@/mixins/requestsMixin";  
import PasswordForm from "@/components/PasswordForm";

export default {  
  name: "home",  
  data() {  
    return {  
      selectedPassword: {},  
      showAddModal: false,  
      showEditModal: false  
    };  
  },  
  components: {  
    PasswordForm  
  },  
  mixins: [requestsMixin],  
  computed: {  
    passwords() {  
      return this.$store.state.passwords;  
    }  
  },  
  beforeMount() {  
    this.getAllPasswords();  
  },  
  methods: {  
    openAddModal() {  
      this.showAddModal = true;  
    },  
    openEditModal(password) {  
      this.showEditModal = true;  
      this.selectedPassword = password;  
    },  
    closeModal() {  
      this.showAddModal = false;  
      this.showEditModal = false;  
      this.selectedPassword = {};  
    },  
    async deleteOnePassword(id) {  
      await this.deletePassword(id);  
      this.getAllPasswords();  
    },  
    async getAllPasswords() {  
      const response = await this.getPasswords();  
      this.$store.commit("setPasswords", response.data);  
    }  
  }  
};  
</script>

We add a table to display the password entries here by using the b-table component, and add the b-table-column column inside the b-table to display custom columns. The b-table component takes a data prop which contains an array of passwords, then the data is exposed for use by the b-table-column components by getting the props from the scoped slot. Then we display the fields, by using the prop.row property. In the last 2 columns, we add 2 buttons to let the user open the edit modal and delete the entry respectively. The entries are loaded when the page loads, by calling getAllPasswords in the beforeMount hook.

This page also has 2 modals, one for the add view and one for editing the entry. In each modal, we nest the PasswordForm component that we created earlier inside. We call the openEditModal to open the edit modal. In the function, we set the selectedPassword field to pass it onto the PasswordForm so that users can edit it and set this.showEditModal to true. The openAddModal function opens the add password modal by changing this.showAddModal to true .

Next in App.vue, we replace the existing code with:

<template>  
  <div>  
    <b-navbar type="is-warning">  
      <template slot="brand">  
        <b-navbar-item tag="router-link" :to="{ path: '/' }">Password Manager</b-navbar-item>  
      </template>  
      <template slot="start">  
        <b-navbar-item :to="{ path: '/' }" :active="path  == '/'">Home</b-navbar-item>  
      </template>  
    </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 lang="scss">  
.page {  
  padding: 20px;  
}

button {  
  margin-right: 10px;  
}

.center {  
  text-align: center;  
}

h1 {  
  font-size: 32px !important;  
}  
</style>

This adds the Buefy b-navbar component, which is the top navigation bar component provided by Buefy. The b-navbar contains different slots for adding items to the different parts of the left bar. The brand slot folds the app name of the top left, and the start slot has the links in the top left.

We also have the router-view for showing our routes. In the scripts section, we watch the $route variable to get the current route the user has navigated to set the active prop of the the b-navbar-item , which highlights the link if the user has currently navigated to the page with the URL referenced.

In the styles section, we add some padding to our pages and margin for the buttons, and also center some text and change the heading size.

Next 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 Buefy from "buefy";
import { ValidationProvider, extend, ValidationObserver } from "vee-validate";
import { required } from "vee-validate/dist/rules";
import "buefy/dist/buefy.css";
extend("required", required);
extend("url", {
  validate: value => {
    return /^(http://www.|https://www.|http://|https://)?[a-z0-9]+([-.]{1}[a-z0-9]+)*.[a-z]{2,5}(:[0-9]{1,5})?(/.*)?$/.test(
      value
    );
  },
  message: "URL is invalid."
});
Vue.use(Buefy);
Vue.component("ValidationProvider", ValidationProvider);
Vue.component("ValidationObserver", ValidationObserver);
Vue.config.productionTip = false;
new Vue({
  router,
  store,
  render: h => h(App)
}).$mount("#app");

This adds the Buefy library and styles to our app and adds the validation rules that we need. Also, we added the ValidationProvider and ValidationObserver to our app so we can use it in the PasswordForm .

Next in router.js , we replace the existing code with:

import Vue from 'vue'  
import Router from 'vue-router'  
import Home from './views/Home.vue'Vue.use(Router)export default new Router({  
  mode: 'history',  
  base: process.env.BASE_URL,  
  routes: [  
    {  
      path: '/',  
      name: 'home',  
      component: Home  
    }  
  ]  
})

This includes the home page route.

Then in store.js , we replace the existing code with:

import Vue from "vue";  
import Vuex from "vuex";Vue.use(Vuex);export default new Vuex.Store({  
  state: {  
    passwords: []  
  },  
  mutations: {  
    setPasswords(state, payload) {  
      state.passwords = payload;  
    }  
  },  
  actions: {}  
});

This adds our passwords state to the store so we can observe it in the computed block of PasswordForm and HomePage components. We have the setPasswords function to update the passwords state and we use it in the components by call this.$store.commit(“setPasswords”, response.data); like we did in PasswordForm . Also, we imported the Bootstrap CSS in this file to get the styles.

After all the hard work, we can start our app by running npm start.

Fake App Backend

To start the back end, we first install the json-server package by running npm i json-server. Then, go to our project folder and run:

json-server --watch db.json

In db.json, change the text to:

{  
  "passwords": [  
  ]  
}

So we have the passwords endpoints defined in the requests.js available.

Categories
JavaScript JavaScript Basics

How to Identify Falsy Values in JavaScript

The following values are falsy in JavaScript, which means that if they are casted to a boolean value, it will be false.

  • false
  • 0
  • empty string: "" , '' , or ``
  • null
  • undefined
  • NaN — not a number value

If you evaluate them as boolean like so, you get:

Boolean(false); // false
Boolean(0); // false
Boolean(""); // false
Boolean(''); // false
Boolean(``); // false
Boolean(undefined); // false
Boolean(null); // false
Boolean(NaN); // false

as opposed to truthy values like objects, which are anything else but the values above:

Boolean({}) // true
Boolean(true) // true
Boolean([]) // true

You can also use the !! shortcut to cast to Boolean, the right ! cast the variable into a boolean and negate the value, and the left ! negate it back to the actual boolean value.

!!(false); // false
!!(0); // false
!!(""); // false
!!(''); // false
!!(``); // false
!!(undefined); // false
!!(null); // false
!!(NaN); // false

Similarly for truthy values:

!!({}) // true
!!(true) // true
!!(\[\]) // true

With shortcut evaluation, boolean ANDs and ORs are evaluated up to the first truthy value:

const abc = false && "abc"; // false, since false is false and "abc" is true

const def = false || "def"; // true, since false is false and "def" is true

It is always good to check for falsy values like null and undefined to avoid crashes.

Categories
JavaScript

Using the URL Object in JavaScript

Manipulating and extracting parts from a URL is a pain if we have to write all the code ourselves. Fortunately, most browsers have the URL object built into their standard library. Now, we can pass in the URL, as a string, to the URL constructor and create an instance of it. Then, we can use the handy value properties and methods to manipulate and get the parts of a URL that we want.

To create a URL object with the URL constructor, we use the new keyword like in the following code:

new URL('http://medium.com');

In above code, we create an instance of the URL object with an absolute URL. We can also create a URL object by passing-in a relative URL as the first argument and the base URL as the second argument. For example, we can write:

new URL('/@hohanga', 'http://medium.com');

Note that the second argument must be a base URL. This means that it must be a valid URL and not just part of it. It must start with the protocol like http:// or https://. We can also define them in a chain-like in the following code:

const mozillaUrl = 'https://developer.mozilla.org';  
const mozillaDevUrl = new URL("/", mozillaUrl);  
const mozillaUrl2 = new URL(mozillaUrl);  
const docsUrl = new URL('/en-US/docs', mozillaUrl2);  
const urlUrl = new URL('Web/API/URL', docsUrl);

If we call toString() on the URL objects above, we should get something like:

https://developer.mozilla.org
https://developer.mozilla.org/
https://developer.mozilla.org/
https://developer.mozilla.org/en-US/docs
https://developer.mozilla.org/en-US/Web/API/URL

The second argument is an optional argument and it should only be passed in when the first argument is a relative URL. The strings or URL objects that we pass in are converted to USVString objects which correspond to the set of all possible sequences of Unicode scalar values. In our code, we can treat them the same as regular strings. A TypeError will be thrown if both argument are relative URLs or if the base and the relative URL together aren’t valid. We can pass in the URL object directly to the second argument because the toString method of the URL object will convert a URL object into a full URL string before being manipulated in the constructor.

A URL object can have the following properties. This article will have the first half of the list of properties and Part 2 of this article will have the second half.

Hash Property

The hash property has the part of the URL that comes after the pound sign (# ). The string isn’t percent decoded, so special symbols like the following are still encoded. They’re encoding with the following mapping. The characters on the left side are converted to the ones on the right side during encoding:

  • ‘:’%3A
  • ‘/’%2F
  • ‘?’%3F
  • ‘#’%23
  • ‘[‘%5B
  • ‘]’%5D
  • ‘@’%40
  • ‘!’%21
  • ‘$’%24
  • “‘“%27
  • ‘(‘%28
  • ‘)’%29
  • ‘*’%2A
  • ‘+’%2B
  • ‘,’%2C
  • ‘;’%3B
  • ‘=’%3D
  • ‘%’%25
  • ‘ ‘%20 or +

For example, if we have the URL https://example.com#hash . Then we can extract the hash with the hash property as follows:

const exampleUrl = new URL('https://example.com#hash');  
console.log(exampleUrl.hash);

Then we get '#hash' in the console.log statement. The property is a USVString , but it’s converted to a string when we retrieve it like we did above. This is not a read-only property, so we can also assign to it like in the following code:

exampleUrl.hash = '#newHash';

For example, if we have:

const exampleUrl = new URL('https://example.com#hash');  
exampleUrl.hash = '#newHash';  
console.log(exampleUrl.hash);

Then we get https://example.com/#newHash as the new URL when we get it with the href property.

Host Property

The host property of the URL object is a USVString that contains the host name. If a port is included after the : , then we also get the port number along with the host. For example, if we have:

const exampleUrl = new URL('https://example.com:8888');  
console.log(exampleUrl.host);

Then we get example.com:8888 . Like other USVString properties, it’s converted to a string when we retrieve it. This is not a read only property, so we can also assign to it like in the following code:

exampleUrl.host = 'example.com';

We can also set the host property to change the URL structure. For example, if we have:

const exampleUrl = new URL('https://example.com:8888/en-US/docs/Web/API/URL/href/');

exampleUrl as the new URL when we get it with the href property.

Hostname Property

With the hostname property, we can get the host name from the URL as a USVString. It will not include the port even if it’s included in the URL string that we used to construct the URL object. For example, if we have:

const exampleUrl = new URL('https://example.com:8888'));  
console.log(exampleUrl.hostname);

Then we get example.com from the console.log statement. Like other USVString properties, it’s converted to a string when we retrieve it. This is not a read-only property, so we can also assign to it like in the following code:

exampleUrl.hostname = ‘newExample.com’;

Then we get that the new URL is https://newexample.com:8888/ when we get it with the href property.

Href Property

The href property of the URL object is USVString that has the whole URL. For example, if we have:

const exampleUrl = new URL(‘https://example.com:8888/en-US/docs/Web/API/URL/href/');  
console.log(exampleUrl.href);

Then when we run the code above, we get https://example.com:8888/en-US/docs/Web/API/URL/href/ which is what we passed into the URL constructor. We can also set the href property by assigning our own value to it. For example, we can write:

const exampleUrl = new URL('https://example.com:8888/en-US/docs/Web/API/URL/href/');  
exampleUrl.href = 'https://newExample.com';  
console.log(exampleUrl.href);

Origin Property

The origin property is a read-only property that returns a USVString with the Unicode serialization of the origin of the URL. The structure of the origin will depend on the type of URL that’s used to construct the URL object. For http or https URLs, the origin will be the protocol followed by :// , then followed by the domain, then followed by the colon (: ), then followed by the port. The default port will only be omitted, which is 80 for http and 443 for https , if it’s explicitly specified. For file protocol URLs, the value will be browser dependent. For blob: URLs, the origin will be the part following blob: . For example, "blob:http://example.com" will be returned as "http://example.com" . For example, if we have:

const url = new URL("https://example.org:443")  
console.log(url.origin);

Then we get:

https://example.org

If we have an http URL like the following:

const url = new URL("http://example.org:80")  
console.log(url.origin);

Then we get:

http://example.org

If we have a non-default port for http or https URLs, like in the following:

const url = new URL("http://example.org:8888")  
console.log(url.origin);
const url2 = new URL("http://example.org:8888")  
console.log(url.origin);

Then we get http://example.org:8888
and http://example.org:8888 respectively.

If we have a blob URL, like in the following:

const url = new URL("blob:http://example.org:8888")  
console.log(url.origin);

Then we get http://example.org:8888 . And for file URLs like the following:

const url = new URL("file://c:/documents/document.docx")  
console.log(url.origin);

Then we get file:// in Chrome.

Password Property

The password property is a writable property that extracts the password portion of the URL before the domain name. Like the other properties, the string is a USVString . If it’s set without the username property being set first, then assigning to this property will silently fail. For example, if we have:

const url = new URL('https://username:password@example.com');  
console.log(url.password);

Then we get password in the console.log statement above. We can also set the password property to the password of our choice. For example, if we have:

const url = new URL('https://username:password@example.com');  
url.password = 'newPassword'  
console.log(url);

Then we get https://username:newPassword@example.com as the new URL when we get it with the href property.

Pathname Property

The pathname property is a USVString property which has the part following the first slash (/). It’s writable property. For example, we can get the pathname value from the URL in the following code:

const url = new URL('https://example.com/path1/path2');  
console.log(url.pathname);

Then we get /path1/path2 when we run the console.log statement above. We can also set the pathname property to the relative URL of your choice by setting it to the part after the domain. For example, we can write:

const url = new URL('https://example.com/path1/path2');  
url.pathname = '/newpath1/newpath2';  
console.log(url.href);

Then we get https://example.com/newpath1/newpath2 when we run the console.log statement above.

Manipulating and extract parts from a URL is a pain if we have to write all the code to do it ourselves. Fortunately, most browsers have the URL object built in to their standard library. Now we can pass in the URL as a string to the URL constructor and to create an instance of a URL. Then we can use the handy value properties and methods to manipulate and get the parts of a URL that we want.

To create an URL object with the URL constructor, we use the new keyword like in the following code:

new URL('http://medium.com');

To see more ways to create an URL object, see Part 1 for more details.

A URL object can have the following properties. This article will have the second half of the list of properties and Part 1 of this article will have the first half. All string values are stored as USVString. USVString are objects which correspond to the set of all possible sequences of Unicode scalar values. All USVString values are converted to strings before we get them, so we can treat them all like regular strings.

Value Properties

port Property

The port property is a USVString property that has the port number of the URL. For example, if we have:

const url = new URL('https://example.com:8888');  
console.log(url.port);

Then we get 8888 from the console.log statement. We can also set the port to get a new URL with by assigning the port property with a new value. For example, if we have the following code:

const url = new URL('https://example.com:8888');  
url.port = '3333';  
console.log(url.href);

Then we get https://example.com:3333/ from the console.log statement when we run the code above.

protocol Property

With the protocol property, we can get the the protocol portion of the URL as a USVString , which is the very first part of the URL. Example protocols include http: , https: , ftp: , file: , etc. For instance, if we have the URL https://example.com, then https is the protocol portion of the URL. If we have the following code:

const url = new URL('https://example.com:8888');  
console.log(url.protocol);

Then we get https: from the console.log statement in the code above.

For other protocols like ftp , it also works:

const url = new URL('ftp://example.com:8888');  
console.log(url.protocol);

If we run the code above, then we get ftp: from the console.log statement in the code above.

We can also set the protocol property to get a new URL. For example, if we have:

const url = new URL('ftp://example.com:8888');  
url.protocol = 'http://'
console.log(url.href);

Then we get http://example.com:8888/ from the console.log above. It also works if we omit the slashes or omit both the colon and the slashes. For example, if we have:

const url = new URL('ftp://example.com:8888');  
url.protocol = 'http:' 
console.log(url.href);

Then we also get http://example.com:8888/ from the console.log above. Likewise, if we have:

const url = new URL('ftp://example.com:8888');  
url.protocol = 'http'
console.log(url.href);

We get the same thing.

search Property

To get the search string or query string from a URL, we can use the search property. The query string is anything after the ? in a URL. Like all the other properties, this one is also USVString property. Note that this property get the whole query string. If you want the individual keys and values from the query string, then we can use the searchParams property. We can use the search property like the following code:

const url = new URL('http://example.com:8888?key1=value1&key2=value2');  
console.log(url.search);

Then we get back ?key1=value1&key2=value2 from the console.log statement when we run the code above. We can also change the query string of the URL by assigning it a new value. For example, if we have the following code:

const url = new URL('http://example.com:8888?key1=value1&key2=value2');  
url.search = 'newKey1=newValue1&newKey2=newValue2';  
console.log(url.href);

Then we get http://example.com:8888/?newKey1=newValue1&newKey2=newValue2 from the console.log statement when we run the code above.

searchParams Property

The search property only gets us the whole query string. To parse the query string into keys and values, we can use the searchParams property, which will get us a URLSearchParams object which has the list of keys and values listed on the query string. The keys and values will be decoded so that special characters are all intact. This is a read only property so we can’t set the query string directly by assigning a new value to this property. For example, to get the query string as a list of keys and values, we can write the following code:

const url = new URL('http://example.com:8888/?key1=value1&key2=value2');  
console.log(url.searchParams.get('key1'));  
console.log(url.searchParams.get('key2'));

Then we get value1 from the first console.log statement and value2 from the second console.log statement. The URLSearchParams object has a get method to get the value of the given query string key by the key name.

Username Property

We can get and set the username part of the URL with the username property. The username is a URL comes before the password and domain name. For example, if we have the following code:

const url = new URL('http://username:password@example.com');  
console.log(url.username);

Then when we run the code above, we get username from the console.log statement above. Also, we can use it to set the username in a URL. For instance, if we have the following code:

const url = new URL('http://username:password@example.com');  
url.username = 'newUserName';  
console.log(url.href);

Then we get http://newUserName:password@example.com/ from the console.log statement in the code above.

Instance Methods

URL object instances also have 2 methods. It has a toString() and a toJSON() method. The toString() method returns a USVString containing the whole URL. It’s the same as the href property for getting the whole URL, but we can’t use the toString() method to set the whole URL like we do with the href property. For example, if we have:

const url = new URL('http://username:password@example.com');  
console.log(url.toString());

Then we get http://username:password@example.com/ from the console.log statement above.

The toJSON() method returns a JSON serialized version of the URL object. However, in most cases, it’s actually the same as what toString() will return. For example, if we have:

const url = new URL('http://username:password@example.com');  
console.log(url.toJSON());  
console.log(url.toString());  
console.log(url.toJSON() === url.toString());

Then we get:

http://username:password@example.com/
http://username:password@example.com
true

from the console.log statements when the code above is run. Then url.toJSON() and url.toString() calls returned the same output, and it’s confirmed by the comparison statement url.toJSON() === url.toString() , which evaluated to true .

Static Methods

There are 2 static methods in the URL constructor. It has the createObjectURL() method and the revokeObjectURL() method. The createObjectURL() static method creates a DOMString containing a URL representing the object given in the parameter. A DOMString is a UTF-16 encoded string. Since JavaScript uses UTF-16 strings, DOMStrings are mapped directly to strings. Passing null to a method that accepts a DOMString will convert null to 'null' . The createObbjectURL() method accepts an object, which can be a File, Blob, or MediaSource object to create a URL for.For example, we can call the createObjectURL() method like in the following code:

const objUrl = URL.createObjectURL(new File([""], "filename"));  
console.log(objUrl);

Then we get something like:

blob:https://fiddle.jshell.net/4cfe2ef3-74ea-4145-87a7-0483b117b429

Each time we create an object URL with the createObjectURL() method, we have to release it by calling the revokeObjectURL() method to clear it from memory. However, browser will release object URLs automatically when the document is unloaded, but for the sake for improving performance, we should release it manually. The revokeObjectURL() method takes in an object URL as its argument. For example, if we have the object URL above created, then we can use the revokeObjectURL() method to release the object URL from memory like in the following code:

const objUrl = URL.createObjectURL(new File([""], "filename"));  
console.log(objUrl);  
URL.revokeObjectURL(objUrl);

The revokeObjectURL() method returns undefined .

When any part of the URL has special characters, they’re encoded by the rules found in RFC-3896, which has a long list of rules on how to encode different types of characters. It also includes non-English characters, and the percent encoding standard for encoding URLs. The full list of rules are at https://tools.ietf.org/html/rfc3986.

With the URL object, manipulating and extract parts from a URL is no longer a pain since we don’t have to write all the code to do it ourselves. Most browsers have the URL object built in to their standard library. Now we can pass in the URL as a string to the URL constructor and to create an instance of a URL. Then we can use the handy value properties and methods to manipulate and get the parts of a URL that we want. It can manipulate all parts of the URL except the query string portion, which can be manipulated by parsing it into a URLSeachParams object and then call its methods and set its properties to modify the query string. To toString and toJSON methods both convert a URL object into a full URL string.

Categories
JavaScript TypeScript

Type Inference in TypeScript

Since TypeScript entities have data types associated with them, the TypeScript compiler can guess the type of the data based on the type or value that is assigned to a variable. The automatic association of the type to a variable, function parameter, functions, etc. according to the value of it is called type inference.

Basic Type Inference

TypeScript can infer the data type of a variable as we assign values to them. For example, if we assign a value to a number, then it automatically knows that the value is a number without us telling it explicitly in the code that the variable has the data type number. Likewise, this is true for other primitive types like strings, booleans, Symbols, etc. For example, if we have:

let x = 1;

Then the TypeScript compiler automatically knows that x is a number. It can deal with this kind of straightforward type inference without much trouble.

However, when we assign data that consists of multiple data types, then the TypeScript compiler will have to work harder to identify the type of the variable that we assigned values to it. For example, if we have the following code:

let x = [0, 'a', null];

Then it has to consider the data type of each entry of the array and come up with a data type that matches everything. It considers the candidate types of each array element and then combines them to create a data type for the variable x. In the example above, we have the first array element being a number, then the second one being a string, and the third one being the null type. Since they have nothing in common, the type has to be a union of all the types of the array elements, which are number, string, and null. Wo when we check the type in a text editor that supports TypeScript, we get:

(string | number | null)[]

Since we get 3 different types for the array elements. It only makes sense for it to be a union type of all 3 types. Also, TypeScript can infer that we assigned an array to it, hence we have the [].

When there’s something in common between the types, then TypeScript will try to find the best common type between everything if we have a collection of entities like in an array. However, it isn’t very smart. For example, if we have the following code:

class Animal {  
  name: string = '';  
}

class Bird extends Animal{}

class Cat extends Animal{}

class Chicken extends Animal{}

let x = [new Bird(), new Cat(), new Chicken()];

Then it will infer that x has the type (Bird | Cat | Chicken)[]. It doesn’t recognize that each class has an Animal super-class. This means that we have to specify explicitly what the type is like we do in the code below:

class Animal {  
  name: string = '';  
}

class Bird extends Animal{}

class Cat extends Animal{}

class Chicken extends Animal{}

let x: Animal[] = [new Bird(), new Cat(), new Chicken()];

With the code above, we directed the TypeScript compiler to infer the type of x as Animal[], which is correct since Animal is the super-class of all the other classes defined in the code above.

Contextual Typing

Sometimes, TypeScript is smart enough to infer the type of a parameter of a function if we define functions without specifying the type of the parameter explicitly. It can infer the type of the variable since a value is set in a certain location. For example, if we have:

interface F {  
  (value: number | string | boolean | null | undefined): number;  
}

const fn: F = (value) => {  
  if (typeof value === 'undefined' || value === null) {  
    return 0;  
  }  
  return Number(value);  
}

Then we can see that TypeScript can get the data type of the value parameter automatically since we specified that the value parameter can take on the number, string, boolean, null, or undefined types. We can see that if we pass in anything with the types listed in the F interface, then they’ll be accepted by TypeScript. For example, if we pass in 1 into the fn function we have above, then the TypeScript compiler would accept the code. However, if we pass in an object to it as we do below:

fn({});

Then we get the error from the TypeScript compiler:

Argument of type '{}' is not assignable to parameter of type 'string | number | boolean | null | undefined'.Type '{}' is not assignable to type 'true'.(2345)

As we can see, the TypeScript compiler can check the type of the parameter by just looking at the position of it and then check against the function signature that’s defined in the interface to see if the type if actually valid. We didn’t have to explicitly set the type of the parameter for TypeScript to check the data type. This saves us a lot of work since we can just use the interface for all functions with the same signature. This saves lots of headaches since we don’t have to repeatedly set types for parameters and also type checking is automatically done as long as define the types properly on the interfaces that we defined.

One good feature that TypeScript brings in the checking of data types to see if we have any values that have unexpected data types or content. TypeScript can infer types based on what we assign to variables for basic data like primitive values. If we assign something more complex to a variable, then it often is not smart enough to infer the type of the variable that we assigned values to automatically. In this case, we have to annotate the type of the variable directly.

It can also do contextual typing where the type of the variable is inferred by its position in the code. For example, it can infer the data type of function parameters by the position it is in the function signature if we define the function signature in the interface that we used to type the function variable that we assign to.

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.