Differentiate Between the 3 Methods for Working With Substrings in JavaScript

substr, substring, slice

Differentiate Between the 3 Methods for Working With Substrings in JavaScript

As at 2021, there are 3 methods in JavaScript to extract substrings from a string, which are:

  • String.prototype.substr
  • String.prototype.substring
  • String.prototype.slice

You can easily get them mixed up, so let's take a closer look at each one of these methods.

TLDR;

  • .substr should not be used.
  • .substring treats negative numbers as index 0.
  • .slice treats negative values as a hint to count backwards from the end of the string to find the indexes.
  • they all treat NaN as index 0.
  • if the first argument (a) is greater than the second argument (b):
    • .substr returns b number of characters starting from index a
    • .substring swaps the two arguments making b the first argument and a the second argument
    • .slice returns an empty string

Let's go!

What is a substring?

Well, it is basically a portion of a string. For example, "methods for" is a substring of the title of this post because it contains some of the characters present in the original string (which is the title of this post).

.substr(startIndex, length)

The .substr method returns n characters from the start index of the original string. The n in this case is the length passed in.

Just think of it like this, start from this index and return length number of characters.

Don't yet understand? Hopefully these examples can help:

// "m in marco" index is 0
// "p in polo" index is 6
// "p in peach" index is 11
// "i in ice" index is 17
// "t in tea" index is 21
// the number of characters in the string is 24.
'marco polo peach ice tea'.substr(0, 5) // returns "marco"
'marco polo peach ice tea'.substr(6, 4) // returns "polo"
'marco polo peach ice tea'.substr(11, 5) // returns "peach"
'marco polo peach ice tea'.substr(17, 3) // returns "ice"
'marco polo peach ice tea'.substr(21, 3) // returns "tea"
  • The first line returns 5 characters from the 0th index.

  • The second line returns 4 characters from the 6th index.

  • The third line returns 5 characters from the 11th index.

And so on.

Here are some things you may want to know:

  • If the start argument is omitted, the original string will be returned.
'this is the way'.substr() // returns "this is the way"
  • If the length argument is omitted or undefined, the rest of the string will be returned.
'this is the way'.substr(6) // returns "s the way"
  • Any argument value that is greater than the length of the string, is treated as if it were the value of the length of the string.
'this is the way'.substr(12, Infinity) // returns "way"
  • If start is a negative number, the index starts counting from the end of the string (reversed).
'this is the way'.substr(-3) // returns "way"
'this is the way'.substr(-10, 6) // returns "is the"
  • If length is a negative number, it is treated as 0.
'this is the way'.substr(3, -1) // returns ""
  • NaN is treated as 0 when passed in as an argument.
'this is the way'.substr('bar', 7) // returns "this is"

According to the MDN docs, .substr should be avoided when possible.

Furthermore, substr() is considered a legacy feature in ECMAScript and could be removed from future versions, so it is best to avoid using it if possible.

this is the way

.substring(startIndex, endIndex)

The .substring method returns characters from the start index, up to the nth character in the string. The n in this case is the endIndex passed in.

Just think of it like this, return the characters in the string that is between start and end, but with start included.

For example;

// index of "m in marco" is 0
// index of "p in polo" is 6
// index of "p in peach" is 11
// index of "i in ice" is 17
// index of "t in tea" is 21
// the number of characters in the string is 24.
'marco polo peach ice tea'.substring(0, 5) // returns "marco"
'marco polo peach ice tea'.substring(6, 10) // returns "polo"
'marco polo peach ice tea'.substring(11, 16) // returns "peach"
'marco polo peach ice tea'.substring(17, 20) // returns "ice"
'marco polo peach ice tea'.substring(21, 24) // returns "tea"

You can understand it in different ways:

  • The first line returns characters from the 0th index up to the 5th character* in the string.

  • The first line returns characters from the 0th index up to the character just before the 5th index.

  • The first line returns the characters between the 0th index and the 5th index, but with the character at the 0th index included.

I know, I know, might need some getting used to.

As with .substr, the following holds:

  • If the start argument is omitted, the original string will be returned.
'this is the way'.substring() // returns "this is the way"
  • If the end argument is omitted, the rest of the string will be returned.
'this is the way'.substring(6) // returns "s the way"
  • Any argument value that is NaN is treated as if it were 0.
'this is the way'.substring('foo', 6) // returns "this i"
  • Any argument value that is greater than the length of the string, is treated as if it were the value of the length of the string.
'this is the way'.substring(12, Infinity) // returns "way"

Other things to know about:

  • Any argument value that is less than 0, is treated as if it were 0.
'to infinity and beyond'.substring(-Infinity, 11) // returns "to infinity"
  • If the first argument is greater than the second argument, the values will be swapped with each other.
'to infinity and beyond'.substring(11, 3) // returns "infinity"

But wait! I wasn't expecting that. Yeah, be on the lookout.

if (firstArg > secondArg) {
    ;[firstArg, secondArg] = [secondArg, firstArg]
}

to infinity and beyond

.slice(startIndex, endIndex)

The .substring and .slice methods are almost identical, but with two subtle differences between them.

  • .substring swaps its two arguments if the first argument is greater than the second argument. While .slice returns an empty string if this is the case.
// substring
'should have gone for the head'.substring(11, 3) // returns "uld have"

// slice
'should have gone for the head'.slice(11, 3) // returns ""
  • .substring treats negative numbers as 0. While .slice counts backward from the end of the string to find the indexes.
// substring
'should have gone for the head'.substring(-11, 24) // returns "should have gone for the"

// slice
'should have gone for the head'.slice(-11, 24) // returns "or the"

should have gone for the head

Differences and Similarities.

Now let's look at the differences between these methods and their similarities.

Differences

  • .substring and .slice both expect an index as the second argument while .substr expects the number of characters to be returned as the second argument.

  • .substring swaps its two arguments if the first argument is greater than the second argument. While .slice returns an empty string if this is the case.

  • .substring treats negative numbers as 0. While .substr and .slice treats negative numbers as a hint to count from the end of the string.

Similarities

  • They all return consecutive characters between two positions of the original string.
  • They are all non-destructive, that is, they do not mutate the original string but return the resulting value.
  • If the first argument is omitted, the original string will be returned.
  • They all treat NaN as 0.

Wrap Up

Apart from directly extracting a substring once, a good use case for .substring and .slice is having a cursor move through a string and extract values conditionally from the string. You just need to keep track of the cursor and the different start positions, then easily extract characters between the sets of indexes.

Using .substr for that will result in performing an extra logic so you can get the length to use as a second argument.

The browser support for these methods is very green but, using .substr in IE8 and earlier has a caveat. Luckily, there is a polyfill for that.

Cover Photo by Karolina Grabowska from Pexels.