Ожидал, что выражение /(.)(?<!\1.*)/g будет выбирать все символы, перед которыми не было таких же. Но почему-то результат всегда null.
console.log("12".match(/(.)(?<!\1.*)/g))
Это происходит по той причине, что .* может найти пустую строку, а значит выражение (.)(?<!\1.*) равноценно (.)(?<!\1) и означает "найди любой символ, не равный самому себе".
Используйте .+:
console.log("12".match(/(.)(?<!\1.+)/g))
console.log("12".match(/(.)(?<!\1.+)/gs)) // Если нужна поддержка строк с символами перехода на новую строку
Блок предварительного просмотра вперёд срабатывает в том месте, где находится текущая позиция. (.) находит 1, сразу после этого происходит проверка, есть ли перед текущей позицией такой же символ, перед которым может быть 1 и более символов, отличных от знаков перехода на новую строку. Т.е. можно представить это так: после того, как найдена 1, срабатывает .+, как в блоке предварительного просмотора вперёд (только тут этот шаблон находит всю подстроку до её начала), а потом движок ищет ближайший к началу строки символ, идентичный захваченному в группе №1. Так как текущая позиция находится сразу после 1, .+ не разрешает \1 найти захваченный символ (так как перед этим идентичным символом должен быть как минимум один символ).
Апостиль в Лос-Анджелесе без лишних нервов и бумажной волокиты
Основные этапы разработки сайта для стоматологической клиники
Продвижение своими сайтами как стратегия роста и независимости