【Web APIs-Day6&7】正则表达式与综合实战

4 阅读4分钟

【Web APIs-Day6&7】正则表达式与综合实战

📺 对应视频:P138-P151 | 🎯 核心目标:掌握正则语法、表单验证、综合项目(登录页/首页/放大镜)


一、正则表达式

1.1 什么是正则表达式?

正则表达式(Regular Expression)是用来匹配字符串的模式,常用于表单验证、字符串搜索替换。

// 创建正则
const reg1 = /hello/          // 字面量(推荐)
const reg2 = new RegExp('hello')  // 构造函数(动态模式时用)
const reg3 = /hello/i         // i:忽略大小写
const reg4 = /hello/g         // g:全局匹配(查找所有)
const reg5 = /hello/m         // m:多行匹配
const reg6 = /hello/gi        // 多个修饰符组合

1.2 test() 和 exec()

const reg = /\d+/

// test():测试是否匹配,返回布尔值(最常用)
reg.test('abc123')   // true
reg.test('abc')      // false

// exec():返回匹配结果数组,无匹配返回 null
reg.exec('abc123def')
// ['123', index: 3, input: 'abc123def', groups: undefined]

1.3 字符串的正则方法

const str = 'Hello World hello'

// match():返回匹配的内容
str.match(/hello/i)    // ['Hello', index: 0, ...](只找第一个)
str.match(/hello/ig)   // ['Hello', 'hello'](加 g 找所有)

// matchAll():返回所有匹配的迭代器(ES2020)
const matches = [...str.matchAll(/hello/ig)]
matches.forEach(m => console.log(m[0], m.index))

// replace():替换
str.replace(/hello/i, 'Hi')     // 'Hi World hello'(只替换第一个)
str.replace(/hello/ig, 'Hi')    // 'Hi World Hi'(替换所有)
'2024-01-15'.replace(/(\d{4})-(\d{2})-(\d{2})/, '$2/$3/$1')  // '01/15/2024'

// replaceAll()(ES2021)
str.replaceAll('hello', 'Hi')   // 等价于 /hello/ig

// split():用正则分割
'a1b2c3'.split(/\d/)   // ['a', 'b', 'c', '']

// search():返回第一个匹配的索引
str.search(/world/i)   // 6(找到),-1(未找到)

二、正则语法详解

2.1 元字符

.     任意字符(除换行符)
\d    数字 [0-9]
\D    非数字
\w    单词字符 [a-zA-Z0-9_]
\W    非单词字符
\s    空白字符(空格、Tab、换行)
\S    非空白字符
\b    单词边界
\B    非单词边界
^     行首(在字符类中表示非)
$     行尾

2.2 字符类

[abc]     ab 或 c
[^abc]    不是 ab、c
[a-z]     a 到 z(小写字母)
[A-Z]     A 到 Z(大写字母)
[0-9]     数字
[a-zA-Z0-9]  字母或数字

2.3 量词

*       0次或多次({0,})
+       1次或多次({1,})
?       0次或1次({0,1})
{n}     恰好 n 次
{n,}    至少 n 次
{n,m}   n 到 m 次

// 默认贪婪匹配(尽量多匹配)
'aabab'.match(/a.+b/)   // ['aabab'](贪婪)
// 加 ? 变懒惰匹配(尽量少匹配)
'aabab'.match(/a.+?b/)  // ['aab'](懒惰)

2.4 分组与捕获

// 分组 ()
/(\d{4})-(\d{2})-(\d{2})/.exec('2024-01-15')
// ['2024-01-15', '2024', '01', '15', index: 0, ...]
// 索引1开始是捕获组的内容

// 命名捕获组(ES2018)
/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/.exec('2024-01-15').groups
// { year: '2024', month: '01', day: '15' }

// 非捕获组 (?:...)
/(?:foo)+/.test('foofoo')  // 只匹配不捕获

// 或 |
/cat|dog/.test('I have a cat')  // true
/(red|blue|green) apple/.test('red apple')  // true

2.5 断言(零宽断言)

// 正向前瞻:x(?=y) → x 后面跟着 y
'100px'.match(/\d+(?=px)/)  // ['100'](只匹配数字,px不包含在结果中)

// 负向前瞻:x(?!y) → x 后面不跟 y
'100em 200px'.match(/\d+(?!px)/g)  // ['100']

// 正向后顾:(?<=y)x → x 前面是 y
'$100'.match(/(?<=$)\d+/)   // ['100']

// 负向后顾:(?<!y)x

三、常用正则模式(速查)

// 手机号(11位,1开头)
/^1[3-9]\d{9}$/

// 邮箱
/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$/

// 身份证(18位)
/^\d{17}[\dXx]$/

// 密码(6-16位,字母+数字)
/^(?=.*[a-zA-Z])(?=.*\d)[a-zA-Z\d]{6,16}$/

// 中文字符
/[\u4e00-\u9fa5]/

// URL
/^https?://[\w.-]+(:\d+)?(/[\w-._~:/?#[]@!$&'()*+,;=]*)?$/

// 去除首尾空格
/^\s+|\s+$/g
str.replace(/^\s+|\s+$/g, '')  // 等价于 str.trim()

// 去除所有空格
str.replace(/\s/g, '')

// 连续空格替换为单个空格
str.replace(/\s+/g, ' ')

四、表单验证实战

4.1 实时验证框架

// 通用验证函数
function validate(input, rules) {
  const value = input.value.trim()
  const errorEl = input.nextElementSibling  // 错误提示元素
  
  for (const rule of rules) {
    if (!rule.test(value)) {
      errorEl.textContent = rule.message
      errorEl.style.display = 'block'
      input.classList.add('error')
      return false
    }
  }
  
  errorEl.style.display = 'none'
  input.classList.remove('error')
  return true
}

// 使用示例
const usernameInput = document.querySelector('#username')
usernameInput.addEventListener('blur', () => {
  validate(usernameInput, [
    { test: v => v.length >= 4, message: '用户名至少4个字符' },
    { test: v => /^[a-zA-Z\d_]+$/.test(v), message: '只能包含字母、数字、下划线' }
  ])
})

4.2 完整登录页验证

const form = document.querySelector('#login-form')
const usernameEl = document.querySelector('#username')
const passwordEl = document.querySelector('#password')

const validators = {
  username: [
    { test: v => v !== '', message: '请输入用户名' },
    { test: v => v.length >= 4, message: '用户名至少4个字符' }
  ],
  password: [
    { test: v => v !== '', message: '请输入密码' },
    { test: v => v.length >= 6, message: '密码至少6个字符' },
    { test: v => /^(?=.*[a-zA-Z])(?=.*\d)/.test(v), message: '密码需包含字母和数字' }
  ]
}

// 失焦验证
usernameEl.addEventListener('blur', () => validate(usernameEl, validators.username))
passwordEl.addEventListener('blur', () => validate(passwordEl, validators.password))

// 提交验证
form.addEventListener('submit', e => {
  e.preventDefault()
  const isValidUser = validate(usernameEl, validators.username)
  const isValidPass = validate(passwordEl, validators.password)
  
  if (isValidUser && isValidPass) {
    // 提交登录请求
    console.log('提交登录:', {
      username: usernameEl.value,
      password: passwordEl.value
    })
  }
})

五、图片放大镜效果

5.1 原理

1. 小图区域:鼠标移入显示遮罩层
2. 遮罩层跟随鼠标移动(限制在小图范围内)
3. 大图根据遮罩层位置等比例移动(放大显示对应区域)

遮罩移动比例 = 大图移动距离 / (大图尺寸 - 大图展示区尺寸)

5.2 核心代码

const smallBox = document.querySelector('.small-box')
const mask = document.querySelector('.mask')
const bigBox = document.querySelector('.big-box')
const bigImg = bigBox.querySelector('img')

smallBox.addEventListener('mouseenter', () => {
  mask.style.display = 'block'
  bigBox.style.display = 'block'
})

smallBox.addEventListener('mouseleave', () => {
  mask.style.display = 'none'
  bigBox.style.display = 'none'
})

smallBox.addEventListener('mousemove', e => {
  const { left, top } = smallBox.getBoundingClientRect()
  
  // 鼠标在小图内的位置
  let x = e.clientX - left - mask.offsetWidth / 2
  let y = e.clientY - top - mask.offsetHeight / 2
  
  // 限制遮罩在小图范围内
  x = Math.max(0, Math.min(x, smallBox.offsetWidth - mask.offsetWidth))
  y = Math.max(0, Math.min(y, smallBox.offsetHeight - mask.offsetHeight))
  
  mask.style.left = x + 'px'
  mask.style.top = y + 'px'
  
  // 大图移动(等比例,方向相反)
  const ratioX = x / (smallBox.offsetWidth - mask.offsetWidth)
  const ratioY = y / (smallBox.offsetHeight - mask.offsetHeight)
  
  bigImg.style.left = -ratioX * (bigImg.offsetWidth - bigBox.offsetWidth) + 'px'
  bigImg.style.top = -ratioY * (bigImg.offsetHeight - bigBox.offsetHeight) + 'px'
})

六、知识图谱

正则表达式与综合实战
├── 正则基础
│   ├── 创建:/pattern/flags 或 new RegExp()
│   ├── 测试:test()(布尔)/ exec()(结果数组)
│   └── 字符串方法:match/replace/split/search
├── 正则语法
│   ├── 元字符:. \d \w \s ^ $
│   ├── 字符类:[abc] [a-z] [^abc]
│   ├── 量词:* + ? {n} {n,m}(贪婪/懒惰)
│   └── 分组:() 捕获 (?:) 非捕获 (?<name>) 命名
├── 常用模式:手机号、邮箱、密码强度
├── 表单验证
│   ├── 失焦(blur)验证 + 实时(input)反馈
│   └── 提交前全量验证
└── 综合实战
    └── 放大镜:getBoundingClientRect + 比例计算

七、高频面试题

Q1:正则的贪婪和懒惰模式是什么?

贪婪(默认):尽量多匹配。/a.+b/ 匹配 'aabab' 得到 'aabab'(最长匹配)。 懒惰(加?):尽量少匹配。/a.+?b/ 匹配 'aabab' 得到 'aab'(最短匹配)。

Q2:如何替换字符串中所有的某个字符?

// 方式一:正则加 g 标志
'hello world'.replace(/o/g, '0')  // 'hell0 w0rld'
// 方式二:replaceAll(ES2021)
'hello world'.replaceAll('o', '0')

Q3:表单验证一般在什么时机触发?

blur(失焦):用户完成输入后立即反馈;② input(实时):实时显示字符计数等;③ submit(提交):最终全量校验,防止绕过前两步。三者配合使用体验最好。


⬅️ 上一篇Web APIs Day5 - BOM与本地存储 ➡️ 下一篇JS进阶Day1 - 作用域、闭包与ES6