DevGang
Авторизоваться

String.prototype.search(): метод, который я хотел бы знать давно 

Я пишу на JavaScript всего около 18 лет. Я начал когда-то в 2002 году, когда IE 6 был королем, Firefox только что вышел, а Chrome не существовало.

Я писал JavaScript почти два десятилетия, и я всегда был тем, кто любит копаться в документах, изучать каждую доступную функцию, каждый метод каждого объекта в браузере. Но иногда ... иногда я все еще, после всего этого времени, нахожу что-то, что было вокруг в течение долгого времени, и я просто не знал об этом.

Сегодня я обнаружил один такой метод: String.prototype.search(). И мужик, я бы хотел знать об этом давно.

Что оно делает

Строковый метод .search() довольно прост: это в основном .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() требуется дополнительная обработка, потому что он имеет три вида возвращаемых значений:

  1. если не найдено совпадение, возвращается null
  2. если он находит совпадение:если у вашего регулярного выражения был глобальный флаг (/.../g как в примере выше MDN), он возвращает массив всех совпадений, и нет никакого способа получить их индексыесли у вашего регулярного выражения не было глобального флага, он возвращает объект со свойством index

Другой вариант, который я иногда использую, это 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(), было довольно забавно. Я просматривал README для фантастической библиотеки полифилла Пола Миллера ES6 Shim и заметил это в разделе «Предостережения» внизу:

  1. Известные Symbol

Если для вас это не имеет смысла, давайте пройдем 30-секундный ускоренный курс по символам. Если это имеет смысл, пропустите следующий раздел.

Кратко о символах

Это будет очень краткий обзор, поэтому, если символы все еще не имеют для вас смысла после этого, я настоятельно рекомендую заняться поиском, потому что они очень важны для повышения уровня в JS (IMHO).

Символы - это новый примитивный тип, введенный в JavaScript в ECMAScript 2015, он же ES6. Основная идея, стоящая за ними, заключается в создании совершенно уникального ключа для использования в качестве имени свойства объекта, чтобы кто-то другой не мог случайно забить ваше свойство позже, используя то же имя, особенно для общих объектов и свойств глобального окна. До Символов было обычным делом видеть ключи на общих объектах с большим количеством начальных подчеркиваний, например ___myThing, или со случайно сгенерированным префиксом, например 142857_myThing. Это может показаться редким крайним случаем, если вы с ним не сталкивались, но поверьте мне, это много раз вызывало разочарование в истории JS.

Для ваших стандартных символов, созданных с помощью Symbol('foo'), никто, кроме вас, не имеет к ним доступа, если вы не передадите их. Тем не менее, существует специальный набор так называемых «известных символов», к которым имеет доступ каждый. Вы можете создать свое собственное, зарегистрировав имя в глобальном реестре Symbol с помощью Symbol.for(), как упомянуто в приведенной выше цитате, но также есть несколько известных символов, определенных браузером как свойства объекта Symbol. Они используются в качестве специальных имен свойств, которые обеспечивают определенные функциональные возможности для объектов.

Возможно, самым известным является Symbol.iterator, что позволяет нам определять пользовательское поведение итерации для наших классов, которое затем используется синтаксисом распространения и [for ... of loop] для итерации по нашему объекту. 

Вернемся к истории

После прочтения заметки в разделе «Предостережения» ES6 Shim мой вопрос был: «Для чего, черт возьми, этот Symbol.search?» Я никогда раньше не сталкивался с этим известным символом, поэтому прочитал страницу MDN Symbol.search, которая, в свою очередь, привела меня к этому String.prototype.search.

Подводя итог, суть заключается в следующем: когда вы вызываете myString.seach(x), движок проверяет, есть ли у переменной, которую вы передали x, метод, определенный под ключом [Symbol.search]. Если нет, он пытается преобразовать ее в RegExp вызвав new RegExp(x), который работает только для строк.

(Примечание: страница MDN вводит в заблуждение. В ней говорится: «Если передано регулярное выражение не-RegExp объекта, оно неявно преобразуется в 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, пытаясь извлечь их имена, каждый раз используя один и тот же объект поиска, даже сохраняя искатель имен и аналогичные искатели для других полей в отдельном модуле и импортируя их. Я думаю, что это может быть аккуратно!

Источник:

#JavaScript
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

Поделитесь своим опытом, расскажите о новом инструменте, библиотеке или фреймворке. Для этого не обязательно становится постоянным автором.

Попробовать

Оплатив хостинг 25$ в подарок вы получите 100$ на счет

Получить