preface

Many students in the interview often encountered some handwritten implementation of the topic. Don’t panic, join me in this series of learning. The best time to do this is now. Today we’ll start with an implementation of indexOf() that is often used in daily development. In the process, we must pay attention to practice, think, and summarize!

Basic usage

The indexOf() method returns the first occurrence of a specified string value in a string. — 【 w3School definition 】

Those familiar with indexOf() should know that its surface features are as follows:

  1. This API can be called for both String and Array data types;
  2. This interface takes two arguments: str.indexof (searchVal, fromIndex);
  3. SearchVal supports multiple characters and is case-sensitive.
  4. If the match is successful, the index of the child content matched for the first time is returned.
  5. If the match fails, -1 is returned.

In addition, after repeated tests by the author, more characteristics are as follows:

  1. If fromIndex < 0, set fromIndex to 0 and Array to fromIndex+=searchVal.length.
  2. FromIndex > searchval. length, return -1;
  3. When indexOf operates on String, the searchVal is not a String and is attempted to be converted to a String.

Ok, so now we have a relatively clear idea of the use of indexOf(). And then make it happen.

Type String

If indexOf() is used for String data, we can usually implement this in one of two ways:

  1. Regular expression
  2. To iterate over

Regular expression

/** * Regular expression implementation *@param {*} str
 * @param {*} searchVal
 * @param {number} [fromIndex=0]
 * @returns* /
function sIndexOf(str, searchVal, fromIndex = 0) {
  const len = arr.length;
  if (fromIndex < 0) fromIndex = 0
  if (fromIndex >= len) return -1
  // Define a matching rule
  let reg = new RegExp(`${searchVal}`.'g') // In order to support lastIndex, customize the start matching position, need to enable 'g', global matching
  // Initializes the start search location
  reg.lastIndex = fromIndex
  // Perform a match
  let ret = reg.exec(str)
  // console.log(ret)
  // Return the matching result
  return ret ? ret.index : -1
}

Copy the code

In the implementation, fromIndex is bounded first. The regular expression is used to define the search and match rules, and the initial search position is initialized, and the search operation is finally completed through the regular expression. This approach is relatively simple and efficient.

To iterate over

/** * String implementation -2: loop through. You need to support matching when searchVal is multiple strings. * *@param {*} str
 * @param {*} searchVal
 * @param {number} [fromIndex=0]
 * @returns* /
function sIndexOf2(str, searchVal, fromIndex = 0) {
  let strLen = str.length
  let searchValLen = (searchVal + ' ').length
  if (fromIndex < 0) fromIndex = 0
  if (fromIndex >= strLen) return -1
  for (let i = fromIndex; i <= strLen - searchValLen; i++) {
    if (searchVal == str.slice(i, searchValLen + i)) return i
  }
  return -1
}
Copy the code

In the implementation, fromIndex also needs to be bounded. In addition, in the circular search operation, you need to consider the searchVal multi-character situation, the above implementation is reflected, we can move to try.

The test case

Based on the above implementation for String, you can verify this with the following test case. The author has completed the test and passed.

/* Test case */
var str = '12345aAxyz'
var arr = [1.2.3.4.5.'a'.'A'.'xyz']


console.log('\n###### String indexOf test ######')

/ / native
console.log('Normal match =>', str.indexOf('2'))  / / = > 1
console.log('Non-string to string =>', str.indexOf(2))  / / = > 1
console.log('Multiple string matches =>', str.indexOf('xyz'))  / / = > 7
console.log('Case sensitive =>', str.indexOf('A'))  / / = > 6
console.log('Custom start position =>', str.indexOf(2.3))  / / = > 1
console.log('Start position less than 0, set to 0=>', str.indexOf(2, -3))  / / = > 1
console.log('Start position > search string length, return -1=>', str.indexOf(2.10))  / / = > 1

/ / handwriting
console.log('\n####### String sIndexOf test #######')

console.log('Normal match =>', sIndexOf(str, '2')) / / = > 1
console.log('Non-string to string =>', sIndexOf(str, 2)) / / = > 1
console.log('Multiple string matches =>', sIndexOf(str, 'xyz'))  / / = > 7
console.log('Case sensitive =>', sIndexOf(str, 'A')) / / = > 6
console.log('Custom start position =>', sIndexOf(str, 2.3)) / / = > 1
console.log('Start position less than 0, set to 0=>', sIndexOf(str, 2, -3)) / / = > 1
console.log('Start position > search string length, return -1=>', sIndexOf(str, 2.10)) / / = > 1

/ / handwriting
console.log('\n####### String sIndexOf2 test #######')

console.log('Normal match =>', sIndexOf2(str, '2')) / / = > 1
console.log('Non-string to string =>', sIndexOf2(str, 2)) / / = > 1
console.log('Multiple string matches =>', sIndexOf2(str, 'xyz'))  / / = > 7
console.log('Case sensitive =>', sIndexOf2(str, 'A')) / / = > 6
console.log('Custom start position =>', sIndexOf2(str, 2.3)) / / = > 1
console.log('Start position less than 0, set to 0=>', sIndexOf2(str, 2, -3)) / / = > 1
console.log('Start position > search string length, return -1=>', sIndexOf2(str, 2.10)) / / = > 1

Copy the code

Array type

To iterate over

/** * Array implementation: loop through **@param {*} arr
 * @param {*} searchVal
 * @param {number} [fromIndex=0]
 * @returns* /
function aIndexOf(arr, searchVal, fromIndex = 0) {
  const len = arr.length;
  if (fromIndex < 0) fromIndex += len
  if (fromIndex >= len) return -1
  for (let i = fromIndex; i < len; i++) {
    if (arr[i] === searchVal) return i
  }
  return -1
}
Copy the code

In the implementation, it is worth noting that fromIndex is treated with slightly different bounds, based on the characterization of the native indexOf() at the beginning of this article. When Array data is called and fromIndex < 0, fromIndex is eventually counted from the end: fromIndex += len. Finally, the lookup is done by looping through.

The test case

Based on the above implementation for the Array type, you can verify this with the following test case. The author has completed the test and passed.

/ / native
console.log('\n####### Array indexOf test #######')

console.log(arr.indexOf(2)) / / 1
console.log(arr.indexOf('2'))// -1
console.log(arr.indexOf('xyz')) / / 7
console.log(arr.indexOf('A')) / / 6
console.log(arr.indexOf(2.3)) // -1
console.log(arr.indexOf(2, -3)) // -1

/ / handwriting
console.log('\n####### Array aIndexOf test #######')

console.log(aIndexOf(arr, 2))/ / 1
console.log(aIndexOf(arr, '2'))// -1
console.log(aIndexOf(arr, 'xyz'))/ / 7
console.log(aIndexOf(arr, 'A'))/ / 6
console.log(aIndexOf(arr, 2.3))// -1
console.log(aIndexOf(arr, 2, -3))/ / 1

Copy the code

merge

implementation

So far, the above detailed implementation uses indexOf() for both String and Array data types. Next, you need to merge the two into a unified interface to provide the calling service externally. The code is as follows:

String.prototype._indexOf = Array.prototype._indexOf = function (searchVal, fromIndex) {
  let data = this
  let isArray = Array.isArray(data)
  let isString = Object.prototype.toString.call(data) == '[object String]'
  if(! isArray && ! isString)throw new TypeError('String or Array')
  if (isArray) return aIndexOf(data, searchVal, fromIndex)
  if (isString) return sIndexOf(data, searchVal, fromIndex)
}
Copy the code

In the implementation, this logic is encapsulated in the _indexOf() method class and mounted on the String and Array prototypes, respectively.

The test case

Based on the merge implementation above, you can verify this with the following test case. The author has completed the test and passed.

/ / native
console.log('\n####### Native indexOf() test #######')

console.log(arr.indexOf(2)) / / 1
console.log(arr.indexOf('2'))// -1
console.log(arr.indexOf('xyz')) / / 7
console.log(arr.indexOf('A')) / / 6
console.log(arr.indexOf(2.3)) // -1
console.log(arr.indexOf(2, -3)) // -1


/ / handwriting
console.log('\n####### handwriting _indexOf() test #######')

console.log(arr._indexOf(2)) / / 1
console.log(arr._indexOf('2'))// -1
console.log(arr._indexOf('xyz')) / / 7
console.log(arr._indexOf('A')) / / 6
console.log(arr._indexOf(2.3)) // -1
console.log(arr._indexOf(2, -3)) // -1
Copy the code

The complete code

/** * String implementation -1: regular expression implementation **@param {*} str
 * @param {*} searchVal
 * @param {number} [fromIndex=0]
 * @returns* /
function sIndexOf(str, searchVal, fromIndex = 0) {
  const len = arr.length;
  if (fromIndex < 0) fromIndex = 0
  if (fromIndex >= len) return -1
  // Define a matching rule
  let reg = new RegExp(`${searchVal}`.'g') // In order to support lastIndex, customize the start matching position, need to enable 'g', global matching
  // Initializes the start search location
  reg.lastIndex = fromIndex
  // Perform a match
  let ret = reg.exec(str)
  // console.log(ret)
  // Return the matching result
  return ret ? ret.index : -1
}

/** * String implementation -2: loop through. You need to support matching when searchVal is multiple strings. * *@param {*} str
 * @param {*} searchVal
 * @param {number} [fromIndex=0]
 * @returns* /
function sIndexOf2(str, searchVal, fromIndex = 0) {
  let strLen = str.length
  let searchValLen = (searchVal + ' ').length
  if (fromIndex < 0) fromIndex = 0
  if (fromIndex >= strLen) return -1
  for (let i = fromIndex; i <= strLen - searchValLen; i++) {
    if (searchVal == str.slice(i, searchValLen + i)) return i
  }
  return -1
}

/** * Array implementation: loop through **@param {*} arr
 * @param {*} searchVal
 * @param {number} [fromIndex=0]
 * @returns* /
function aIndexOf(arr, searchVal, fromIndex = 0) {
  const len = arr.length;
  if (fromIndex < 0) fromIndex += len
  if (fromIndex >= len) return -1
  for (let i = fromIndex; i < len; i++) {
    if (arr[i] === searchVal) return i
  }
  return -1
}

// Finally implemented
String.prototype._indexOf = Array.prototype._indexOf = function (searchVal, fromIndex) {
  let data = this
  let isArray = Array.isArray(data)
  let isString = Object.prototype.toString.call(data) == '[object String]'
  if(! isArray && ! isString)throw new TypeError('String or Array')
  if (isArray) return aIndexOf(data, searchVal, fromIndex)
  if (isString) return sIndexOf(data, searchVal, fromIndex)
}

Copy the code

summary

This article started with the use of indexOf() and looked at some of its surface and hidden features. IndexOf is implemented by hand for special processing of different data types. It involves regular expressions, prototypes, boundary value processing, rigorous judgment and other details. If you want to write all of them, it still takes some effort. I suggest you write them by hand, and I believe you will have a deeper experience. respect~~~

communication

If you think this article is helpful to you, please like it and follow it without losing contact. Your support is the biggest encouragement to the author!

Wechat pay attention to “ride the wind and waves big front” public number, find more interesting front-end knowledge and combat.

Dry goods series of articles summarized below, welcome to start, follow exchange learning 👏🏻.

Github.com/sggmico/fe-…

If you have any comments or suggestions about this article, please feel free to discuss and correct them in the comments section.

You might also want to see:

  1. [Vue2.0 source code series] : responsive principle
  2. [Vue2.0 source series] : Computed vs Methods
  3. 【 truth 】 : Vue picture lazy loading how to do?
  4. [Thematic combat] : Take you to thoroughly understand BFC and its application

👇 you can click “read the original” to get the latest updates and find more interesting front-end knowledge and combat.

Welcome to join the technology exchange group, push, touch fish, help can be.