前言
很多同学在面试中经常会遇到到一些手写实现类的题目。不要慌,跟笔者一起,开始这一系列的学习吧。最好的时间就是现在~ 今天我们先来学习下日常开发中经常用到的indexOf()的实现。过程中,一定要注意多练习、思考、总结!
基本用法
indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。——【w3school定义】
对indexOf()比较了解的同学应该知道,其表层特征如下:
- String 和 Array 两种数据类型都可以调用该API;
- 该接口有两个参数: str.indexOf(searchVal, fromIndex);
- 其中,searchVal支持多字符,且对大小写敏感;
- 匹配成功,返回首次匹配子内容的索引;
- 匹配失败,返回-1;
另外,经过笔者反复测试,更多特征如下:
- fromIndex < 0,String则置为0,Array则置为 fromIndex+=searchVal.length,需要区分处理;
- fromIndex > searchVal.length,则都返回 -1;
- indexOf操作 String时, searchVal不是字符串会被尝试转换为字符串;
好了,目前我们对indexOf()的用法有了相对清晰的了解。接下来实现一番。
String类型
如果是 String类型 数据使用indexOf(),我们通常可以使用两种方式来实现:
- 正则表达式
- 循环遍历
正则表达式
/**
* 正则表达式实现
* @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
// 定义匹配规则
let reg = new RegExp(`${searchVal}`, 'g') // 为了支持lastIndex,自定义开始匹配位置,需要开启'g',全局匹配
// 初始化开始搜索位置
reg.lastIndex = fromIndex
// 执行匹配
let ret = reg.exec(str)
// console.log(ret)
// 返回匹配结果
return ret ? ret.index : -1
}
在实现中,首先对fromIndex做了边界处理。使用正则表达式定义了查找匹配规则,并对开始搜索位置做了初始化,最终通过正则表达式完成查找操作。这种方式相对简洁和高效【推荐】。
循环遍历
/**
* String实现-2:循环遍历。 需要支持searchVal为多字符串时的匹配。
*
* @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
}
在实现中,同样需要对fromIndex做边界处理。另外,在循环查找操作时,需要考虑 searchVal 为多字符的情况,上述实现都有体现,大家可以动动手尝试下。
测试用例
根据上面针对String类型的实现,可以通过以下测试用例来验证一下。笔者已完成测试,通过。
/*
测试用例
*/
var str = '12345aAxyz'
var arr = [1, 2, 3, 4, 5, 'a', 'A', 'xyz']
console.log('\n###### String indexOf 测试 ######')
// 原生
console.log('正常匹配=>', str.indexOf('2')) // => 1
console.log('非字符串转字符串=>', str.indexOf(2)) // => 1
console.log('多字符串匹配=>', str.indexOf('xyz')) // => 7
console.log('大小写敏感=>', str.indexOf('A')) // => 6
console.log('自定义开始位置=>', str.indexOf(2, 3)) // => -1
console.log('开始位置小于0,则置为0=>', str.indexOf(2, -3)) // => 1
console.log('开始位置>搜索字符串长度,则返回-1=>', str.indexOf(2, 10)) // => -1
// 手写
console.log('\n####### String sIndexOf 测试 #######')
console.log('正常匹配=>', sIndexOf(str, '2')) // => 1
console.log('非字符串转字符串=>', sIndexOf(str, 2)) // => 1
console.log('多字符串匹配=>', sIndexOf(str, 'xyz')) // => 7
console.log('大小写敏感=>', sIndexOf(str, 'A')) // => 6
console.log('自定义开始位置=>', sIndexOf(str, 2, 3)) // => -1
console.log('开始位置小于0,则置为0=>', sIndexOf(str, 2, -3)) // => 1
console.log('开始位置>搜索字符串长度,则返回-1=>', sIndexOf(str, 2, 10)) // => -1
// 手写
console.log('\n####### String sIndexOf2 测试 #######')
console.log('正常匹配=>', sIndexOf2(str, '2')) // => 1
console.log('非字符串转字符串=>', sIndexOf2(str, 2)) // => 1
console.log('多字符串匹配=>', sIndexOf2(str, 'xyz')) // => 7
console.log('大小写敏感=>', sIndexOf2(str, 'A')) // => 6
console.log('自定义开始位置=>', sIndexOf2(str, 2, 3)) // => -1
console.log('开始位置小于0,则置为0=>', sIndexOf2(str, 2, -3)) // => 1
console.log('开始位置>搜索字符串长度,则返回-1=>', sIndexOf2(str, 2, 10)) // => -1
Array类型
循环遍历
/**
* Array实现: 循环遍历
*
* @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
}
在实现中,值得注意的是对 fromIndex 的边界处理略有不同,根据本文开头对原生 indexOf() 的特征描述。当Array类型数据调用时,fromIndex < 0的情况下,fromIndex 最终会从末尾计数,即:fromIndex += len。最后,通过循环遍历完成查找操作。
测试用例
根据上面针对Array类型的实现,可以通过以下测试用例来验证一下。笔者已完成测试,通过。
// 原生
console.log('\n####### Array indexOf 测试 #######')
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
// 手写
console.log('\n####### Array aIndexOf 测试 #######')
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
合并
实现
至此,上面分别针对 String 和 Array 两种不同类型的数据使用 indexOf()的情况做了详细实现。接下来,需要将两者合并为统一的接口来对外提供调用服务。代码如下:
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)
}
在实现中,将以上逻辑封装在了 _indexOf() 方法类,并分别挂载到了 String 和 Array 的原型上。
测试用例
根据上面的合并实现,可以通过以下测试用例来验证一下。笔者已完成测试,通过。
// 原生
console.log('\n####### 原生 indexOf() 测试 #######')
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
// 手写
console.log('\n####### 手写 _indexOf() 测试 #######')
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
完整代码
/**
* String实现-1:正则表达式实现
*
* @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
// 定义匹配规则
let reg = new RegExp(`${searchVal}`, 'g') // 为了支持lastIndex,自定义开始匹配位置,需要开启'g',全局匹配
// 初始化开始搜索位置
reg.lastIndex = fromIndex
// 执行匹配
let ret = reg.exec(str)
// console.log(ret)
// 返回匹配结果
return ret ? ret.index : -1
}
/**
* String实现-2:循环遍历。 需要支持searchVal为多字符串时的匹配。
*
* @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实现: 循环遍历
*
* @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
}
// 最终实现
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)
}
小结
本文从 indexOf() 的使用出发,了解了它的一些表层和隐藏的特性。并针对不同数据类型的特殊处理方式,分别来根据特性来时手写实现indexOf。其中涉及到到 正则表达式、原型、边界值处理、严谨性判断等细节,想要想全写全还是要费点精力哒~ 建议大家动手写写,相信一定会有更加深刻的体会。respect~~~
交流
如果觉得本文对你有帮助,点赞、关注不失联,你的支持是对笔者最大的鼓励!
微信关注 「 乘风破浪大前端 」公众号,发现更多有趣好玩的前端知识和实战。
干货系列文章汇总如下,欢迎 start 、follow 交流学习👏🏻。
关于本文如有任何意见或建议,欢迎评论区讨论、指正。
也许你还想看:
👇可以直接点击 「阅读原文」,获取最新更新、发现更多有趣好用的前端知识和实战。
欢迎加入乘风破浪技术交流群,内推、摸鱼、求助皆可。