String.prototype.search():我希望很久以前就知道的方法
简而言之: String.prototype.search()
基本上是,.indexOf()
但使用了正则表达式。从 IE 4 开始,所有浏览器都支持它,但 ES6 使其功能更加强大Symbol.search
。
我写 JavaScript 大概有 18 年了。大概从 2002 年开始写,当时 IE 6 风头正盛,Firefox 才刚刚发布,Chrome 还不存在。
我写 JavaScript 已经快二十年了,一直以来都喜欢钻研文档,学习所有可用的功能,以及浏览器里每个对象的每种方法。但有时……有时,过了这么久,我还是会发现一些已经存在很久,而我却一无所知的东西。
今天我发现了这样一种方法:String.prototype.search()
。天哪,真希望我很久以前就知道这个方法。
它的作用
字符串.search()
方法非常简单:正如我在 tl;dr 中提到的,它基本上是.indexOf()
,但有一个关键的区别:它使用正则表达式!
这是来自MDN 页面的演示。它演示了如何在字符串中找到第一个非空格、非字母数字的字符:
const paragraph = 'The quick brown fox jumps over the lazy dog. If the dog barked, was it really lazy?';
// any character that is not a word character or whitespace
const regex = /[^\w\s]/g;
console.log(paragraph.search(regex));
// expected output: 43
console.log(paragraph[paragraph.search(regex)]);
// expected output: "."
当我看到它时,我大吃一惊。并不是因为它一定有那么疯狂,而是因为我从来不知道它可以为我所用。多年来,我使用笨重、可读性较差的 无数次拼凑了这个方法String.prototype.match()
。此方法有效,当我想要捕获组和所有那些东西时,它是我的首选解决方案,但对于简单地找到字符串中某个模式的第一个实例的索引来说,.search(regex)
它是如此干净。首先,至少对我来说,这里发生的事情一目了然,而.match()
方法总是让我花一分钟才能理解。另一方面,.match()
需要额外的处理,因为它有三种返回值:
- 如果没有找到匹配项,则返回
null
- 如果找到匹配项:
- 如果你的正则表达式有全局标志(
/.../g
,如上面 MDN 的示例),它会返回所有匹配的数组,并且无法获取它们的索引 - 如果你的正则表达式没有全局标志,它会返回一个具有
index
属性的对象
- 如果你的正则表达式有全局标志(
所以.match()
变得复杂。
我有时会使用的另一个选项是RegExp.prototype.exec()
。它的优点是,index
无论全局标志如何,它在找到匹配项时始终返回一个带有属性的对象。缺点是,如果要在多个字符串上运行它,仍然需要小心全局标志,因为它会从上一个匹配项的索引开始搜索。这有时很有用,但在简单情况下效果不佳。
为了强调这一点,下面是并排比较:
// old way
const match = paragraph.match(regex)
const index = match ? match.index : -1
// new way
const index = paragraph.search(regex)
我不知道。我对这种事很感兴趣。也许你不会。但如果这还不让你兴奋,也许这个会:
ES6 如何使其更加强大
我遇到的情况String.prototype.search()
有点奇怪。当时我正在浏览 Paul Miller 的优秀 polyfill 库ES6 Shim的 README 文件,在底部的“注意事项”部分发现了这样一段话:
- 知名
Symbol
- 为了使它们能够跨域工作,它们是通过全局
Symbol
注册表创建的Symbol.for
。这并不违反规范,但这意味着Symbol.for('Symbol.search') === Symbol.search
将会是true
,而在新的兼容域中默认情况下不会是。
如果你不明白,我们来做个 30 秒的速成课程,讲解一下符号。如果你明白,就跳过下一节。
关于符号的简单说明
这将是一个非常快速的概述,所以如果符号在你读完之后仍然对你没有太大的理解,我强烈建议你进行一些谷歌搜索,因为它们对于 JS 的升级非常重要(在我看来)。
Symbols 是 ECMAScript 2015(又称 ES6)中引入 JavaScript 的一种新原始类型。其基本思想是创建一个完全唯一的键作为对象属性名,这样其他人就不可能在之后意外地使用相同的名称破坏你的属性,尤其是在共享对象和全局窗口属性上。在 Symbols 出现之前,共享对象的键通常会以大量的下划线开头,例如___myThing
,或者带有随机生成的前缀,例如142857_myThing
。如果你没有遇到过这种情况,可能觉得这只是一个罕见的极端情况,但相信我,在 JS 历史上,这种情况曾多次令人沮丧。
对于使用 创建的普通标准 Symbol,Symbol('foo')
除非您将其传递出去,否则除了您之外,其他人都无法访问它们。但是,有一组特殊的“知名 Symbol”可供所有人访问。您可以使用 通过在全局 Symbol 注册表中注册一个名称来创建自己的 Symbol Symbol.for()
,正如上文所述。此外,浏览器还定义了几个知名 Symbol,它们是 Symbol 对象的属性。这些 Symbol 用作特殊的属性名,用于启用对象的某些功能。
也许最著名的是Symbol.iterator
,它允许我们为类定义自定义迭代行为,然后通过展开语法和 [for ... of 循环] 来迭代我们的对象。不久前,我写了一篇关于 ES6 迭代器及其与生成器关系的文章,如果你对这个主题感兴趣的话可以深入探讨一下(深入研究的话,你会发现内容非常丰富):
好的,希望我们至少能够理解并读完故事的其余部分。
回到故事
在阅读了 ES6 Shim 的注意事项部分中的注释后,我的问题是:“它到底是Symbol.search
用来做什么的?” 我之前从未遇到过这个特别著名的符号,因此我阅读了MDN 页面上的Symbol.search
,这又让我找到了String.prototype.search
。
说到这里我已经有点啰嗦了,为了快速总结一下,总结一下:当你调用 时myString.seach(x)
,引擎会检查你传入的x
,在键 下是否有定义方法。如果没有,它会尝试通过调用[Symbol.search]
转换为,但这只适用于字符串。RegExp
new RegExp(x)
(附注: MDN 页面在这里具有误导性。它说:“如果传递了一个非 RegExp 对象 regexp,则会使用 new RegExp(regexp) 将其隐式转换为 RegExp。” 但正如我们接下来将看到的,这并不完全正确;如果传递一个带有属性的对象,它不会转换为 RegExp [Symbol.search]
。)
所以,这意味着我们可以编写一个自定义的字符串搜索函数,并将其包装在一个对象中。这看起来可能有点小众,因为你可以直接将字符串传递给函数,这当然是事实。但语法方面我觉得挺不错的:
// Find the index of the first character following a string like:
// "Name:\t"
const nameFinder = {
[Symbol.search](s) {
const result = /Name:\s*/.exec(s)
if (result) {
const {0: label, index} = result
return index + label.length
}
else {
return -1
}
}
}
// imagine this was read in from a file
const doc = `Customer Information
ID: 11223344
Name: John Smith
Address: 123 Main Street
...`
const customerNameStart = doc.search(nameFinder)
const customerName = doc.slice(customerNameStart, doc.indexOf('\n', customerNameStart))
想象一下,在 Node 脚本中循环遍历客户信息文件目录,尝试提取他们的姓名,每次都重复使用同一个搜索对象,甚至将姓名查找器和其他字段的类似查找器存储在单独的模块中并导入它们。我觉得这应该很棒!(就我一个?)
结论
说实话,我承认这算不上什么革命性的东西,而且它可能不会改变很多工作流程。但对我来说,这并非关键;对我来说,最重要的是了解有哪些可用的工具。说实话,我不知道什么时候会用到像上面这样的客户搜索对象,但我觉得有这个选项真的很酷。既然我知道了,如果真的遇到需要它的情况,我就会把它记在心里。它就像我工具腰带上的另一个蝙蝠镖。
(另外,我认为像这样的元编程真的很酷😎)
尾注
谢谢你读完这篇文章!我知道这很小众,而且我比我认识的大多数开发者都对这种小事感到兴奋。如果你对这篇文章感兴趣,请在评论区留言,或者私信我!
文章来源:https://dev.to/kenbellows/string-prototype-search-a-method-i-wish-i-knew-about-a-long-time-ago-45n6