Skip to content

1 正则

1.1 正则表达式

javascript
  // 正则表达式 Regular Expression   Regular 有规律的
  // 定义:是一种字符串匹配的模式(pattern)   模式 理解为 规则 

  // 作用,应用场景:
  // 1. 表单验证,比如验证手机号 看是否满足条件 : 匹配
  // 2. 过滤敏感词:替换
  // 3. 从字符串中提取一些我们想要的字符 : 提取 


  // 使用,分为两个步骤
  // 1. 定义规则
  // 2. 使用正则 ,看是否匹配

  const reg = /前端/
  // 字面量: 字面意思,我们看到它就知道它是正则  [] ,  {} 

  console.log(typeof reg)  // object
  console.log(reg instanceof Object) // true
  console.log(Object.prototype.toString.call(reg)) // [object RegExp]


  // ==> 使用

  const str = '我们正在学习正则'
  
  // 1. 定义正则表达式
  const reg2 = /正则/
  // reg.test(str)
  // 2. 看是否匹配 符合返回true, 不符合返回false
  console.log(reg2.test(str))  // true 


  console.log(reg.test('正则哈哈哈')) // false 注意,正则表达式写在test的前面

1.2 边界符

DANGER

^ 以谁开头 $ 以谁结尾 /^xxx$/ 精确匹配

javascript
// 元字符之边界符
console.log(//.test('哈')) // true 
console.log(//.test('二哈')) // true 
console.log(/啊哈/.test('哈啊哈')) // true 
console.log(/啊哈/.test('啊哈啊')) // true 
console.log(/哈啊哈/.test('啊哈啊')) // false
// 注意,这里的正则,不能拆分,它是一个整体

// 边界符
console.log('--------------')
// 1. ^ 脱字符, 表示以谁开头    6号键位
console.log(/^/.test('哈'))  // true 
console.log(/^/.test('哈哈')) // true 
console.log(/^/.test('二哈'))  // false 

console.log('---------------------')
// 2. $ 以谁结尾  $ 是4号键位
console.log(/web$/.test('web前端')) // false 
console.log(/web$/.test('前端web')) // true 
console.log(/web$/.test('前端web学习')) // false
console.log(/web$/.test('前端we')) // false

console.log('---------------')
// 3. 精确匹配 ^ $ 一起在正则表达式的前后写    ==> 必须一模一样
console.log(/^$/.test('哈'))  // true 
console.log(/^$/.test('二哈'))  // false  
console.log(/^$/.test('哈哈'))  //  false  ==> 因为精确匹配,多一个都不行 
console.log(/^哈二哈$/.test('哈二哈哈'))  // false

1.3 量词

javascript
// 元字符之量词
// 1. * 重复次数 >= 0 次
const reg1 = /^*$/  
console.log(reg1.test('')) // true 
console.log(reg1.test('哈哈哈')) // true 
console.log(reg1.test('哈哈哈哈哈哈')) // true 

console.log('------------')
// 2. + 重复次数 >= 1 次
const reg2 = /^w+$/    //   w ww www wwww 
console.log(reg2.test(''))  // false
console.log(reg2.test('w'))
console.log(reg2.test('ww'))
console.log(reg2.test('www'))

console.log('---------------------')
// 3. ? 重复次数  0 || 1 
const reg3 = /^w?$/ 
console.log(reg3.test('')) // true 
console.log(reg3.test('w')) // true 
console.log(reg3.test('ww')) // false

console.log('------------')
// 4. {n} 重复 n 次
const reg4 = /^w{3}$/
console.log(reg4.test('ww'))
console.log(reg4.test('www'))
console.log(reg4.test('wwww'))

console.log('------------')
// 5. {n,} 重复次数 >= n 
const reg5 = /^w{3,}$/
console.log(reg5.test('ww')) 
console.log(reg5.test('www'))
console.log(reg5.test('wwww'))

// 6. {n,m}   n =< 重复次数 <= m , 注意,逗号左右不要加空格
console.log('----------------')

const reg6 = /^w{3,6}$/
console.log(reg6.test('ww')) // false 
console.log(reg6.test('www')) // true 
console.log(reg6.test('wwww'))
console.log(reg6.test('wwwww'))
console.log(reg6.test('wwwwww'))
console.log(reg6.test('wwwwwww')) // false


// eg. 量词作用的是它相邻的前面那一个字符
const reg7 = /^ab{2,5}c$/    
const res = reg7.test('abc') 

// abbc  abbbc abbbbc abbbbbc
console.log(res)

1.4 字符组

javascript
// 字符组: 同一个位置上可能出现各种字符,也叫做纵向模糊匹配
// 写法:[]方括号中列出所有可能出现的字符,但是,最后只选一个!

// 联想 :抽奖的老虎机,只能选一个
// /[abc]/   ==>   a || b || c

console.log(/^[abc]$/.test('a'))
console.log(/^[abc]$/.test('b'))
console.log(/^[abc]$/.test('c'))

console.log(/^[abc]$/.test('ab')) // false  因为只能选一个 
console.log(/^[abc]$/.test('bc'))


console.log('-------------')
// 2. 如果不加边界符 , 右边的字符串如果包含a 或 b 或 c ,则为true
console.log(/[abc]/.test('bc'))  

// 3. 字符组+量词 
console.log(/^[abc][abc]$/.test('ab')) // true 
console.log(/^[abc]{3}$/.test('abc'))  // true 

console.log(/^[ef][abc]{2}$/.test('eac')) // true 

// [abc][abc]  ==>  a b c ;  a b c ;  aa ab ac ba bb bc ca cb cc

1.5 取反

javascript
// 取反 
// 在方括号的开头写上 ^ , 表示取反 

// 1. /^[^0-9]$/  除了0-9以外的任意单个字符
// 1. /^[^a-z]$/  除了a-z以外的任意单个字符
// 1. /^[^abc]$/  除了abc以外的任意单个字符

// [^a-zA-Z0-9]

console.log(/^[^0-9]$/.test('a'))
console.log(/^[^0-9]$/.test('9'))

1.6 字符组简写

javascript
// 字符组 简写   

// \d   [0-9]   记忆:digit  数字字符  小写
// \w   [a-zA-Z0-9_]  记忆: word 单词字符
// \s   [ \t\r\n\v\f]  记忆:空白字符 space

// 大写的 ==>   \D  \W  \S  是刚才的分别取反得到

// \D   [^0-9] 除了数字以外的任意单个字符
// \W   [^0-9a-zA-Z_] 除了这些字符以外的
// \S   [^ \t\r\n\v\f] 非空白字符

console.log(/\d/.test('abc')) // false  [0-9]
console.log(/\D/.test('abc')) // true   [^0-9]
console.log(/\w/.test('9999')) // true  单词包含了数字


// 匹配 2023-4-1 日期 的正则?
const reg = /^\d{4}-\d{1,2}-\d{1,2}$/

1.7 替换和修饰符

javascript
// 1. 字符串的方法  替换 
// str.replace(/正则/, 要替换成什么)
// 返回值:替换之后的字符串

const str = '练练老师和果果老师有基情~'
const resStr = str.replace(/基情/, '**')
console.log(resStr)

// 2. 修饰符
// 约束正则的某些细节行为,比如是否区分大小写,是否匹配所有情况
//   /正则/修饰符

// 2.1 掌握两个修饰符 

// 1. i  ignore 忽略  忽略大小写
// 2. g  global 全局匹配,找到所有匹配的

// eg2.
const str2 = 'Java是一门编程语言,学完Java之后找到工作,薪资还不错~'
const res = str2.replace(/java/ig, 'Go')
console.log(res)

1.8 正则练习替换

javascript
// 隐藏手机号中间四位案例
const tel = '13611112222'

// 1. 利用正则,把手机号化为3份
// 正则用小括号分组了 
const reg = /^(\d{3})\d{4}(\d{4})$/

// 2. 利用replace替换
// $1 可以得到正则第一个小括号中的内容 136
// $2 可以取到正则第二个小括号中的内容 2222
const resStr = tel.replace(reg, '$1****$2')
console.log(resStr)
document.querySelector('.phone').innerHTML = resStr


// const res = tel.replace(reg, '$1')
// console.log(res)

// 3. 新需求:只显示后面四位手机号  *******2222

const reg2 = /^\d{7}(\d{4})$/
const resStr2 = tel.replace(reg2, `${'*'.repeat(7)}$1`)
// const resStr2 = tel.replace(reg2, `*******$1`)

// 字符串重复  repeat方法
// console.log('ab'.repeat(7))
console.log(resStr2)

2.扩展

2.1 函数返回值

javascript
const fn = function(){
    console.log('今天大家学废了吗?')
    console.log('今天又废了')
    console.log('今天站起来了')
    return 666
}

const res = fn()
console.log(res)

// 1. 函数内部,如果不写return,那么调用函数之后的表达式的值就是undefined
// 2. 如果函数内部写了return,fn() 的值 就是 return后面的值

2.2 flag状态切换

html
<button>click</button>
<script>
  const btn = document.querySelector('button')
  let flag = true  // == true表示 白色
  // 当我们需要做一种状态切换的时候,就可以使用flag变量 status
  // 比如开关的时候,一个开,一个关
  btn.addEventListener('click', function(){
    if (flag){
      document.body.style.backgroundColor = 'black'
      // 样式变了,状态我们也一起改变
      flag = false // 表示现在是关着的状态
    } else {
      document.body.style.backgroundColor = 'white'
      flag = true 
    }

  })
</script>

2.3 change事件

change 当输入的内容发生改变的时候,并且按下回车或者失去焦点时触发 input:只要内容发生改变就触发

javascript
const input = document.querySelector('input')
// input 当我们用户输入的内容发生改变的时候,实时触发
input.addEventListener('input', function(){
    console.log(input.value)
})

// 针对表单  change 事件
// change : 1. 当我们输入的内容发生改变的时候
//          2. 并且失去焦点或者按下回车的时候触发
input.addEventListener('change', function(){
    console.log(input.value)
})

3. 综合案例

todo01 发送验证码

javascript
  // 1. 发送短信5秒倒计时业务

  // 获取元素,注册事件,开启定时器 setInterval() 关定时器
  const btn = document.querySelector('.code')
  console.log(btn)
  // 注册点击事件
  btn.addEventListener('click', function(){
      let i = 5
      // 点完之后文字内容立马就切换了
      btn.innerHTML = `${i}秒后重新获取`
      let timerId = setInterval(function(){
          i--
          btn.innerHTML = `${i}秒后重新获取`
          if (i === 0) {
            // 清除定时器
            clearInterval(timerId)
            btn.innerHTML = `重新获取`
          }
  
      }, 1000)
  })

验证码多次点击问题解决 - flag

javascript
// 获取元素,注册事件,开启定时器 setInterval() 关定时器
const btn = document.querySelector('.code')
console.log(btn)
    // 注册点击事件
let flag = true // ===> 表示可以点击的状态
  btn.addEventListener('click', function(){
  // 第一次点击的时候 flag 为true, 进入if条件中,开启了一个定时器,同时将状态切换为false
  // 第二次点击的时候,5秒之后,flag为false,进不了条件,
  // 只有5秒之后,flag变为true,又可以开启定时器
  if (flag) {
      flag = false // 点完之后,状态就应该是不能点击了
      let i = 5
      // 点完之后文字内容立马就切换了
      btn.innerHTML = `${i}秒后重新获取`
      let timerId = setInterval(function(){
          i--
          btn.innerHTML = `${i}秒后重新获取`
          if (i === 0) {
              // 清除定时器
              clearInterval(timerId)
              btn.innerHTML = `重新获取`
              flag = true // 当倒计时为0,让btn又可以重新点击
            }
        }, 1000)
    }
  })

// 要的效果是,1. 点击了一次之后,不能再点击了(不能再开启定时器了)
// 只能让当前环境中,只有一个定时器在运行
// 什么时候又可以重新点击? 2. 当我们倒计时为0的时候,又可以让他重新点击

// ==> 联想到flag,做状态切换,因为分析了一下,可以点,和不可以点两种状态

flag的另一种写法

javascript
let flag = true // ===> 表示可以点击的状态
  btn.addEventListener('click', function(){
  // 第一次点击的时候 flag 为true, 进入if条件中,开启了一个定时器,同时将状态切换为false
  // 第二次点击的时候,5秒之后,flag为false,进不了条件,只有5秒之后,flag变为true,
  // 又可以开启定时器
  if (!flag) return 

  // 一开始flag true, !flag ==> false ,  if(!flag) 不成立,后面的return就不生效,
    // 那么下面的代码就会执行
  // 当flag => true  下面的代码就会执行

  // 当点完一次之后, flag 为false , !flag ==> true , if (!flag) 成立,return生效,
    // 后面的代码不再执行
  // 当flag => false  下面的代码就不执行

  // if (flag) {
  flag = false // 点完之后,状态就应该是不能点击了

  let i = 5
  // 点完之后文字内容立马就切换了
  btn.innerHTML = `${i}秒后重新获取`
  let timerId = setInterval(function(){
    i--
    btn.innerHTML = `${i}秒后重新获取`
    if (i === 0) {
      // 清除定时器
      clearInterval(timerId)
      btn.innerHTML = `重新获取`
      flag = true // 当倒计时为0,让btn又可以重新点击
    }
  }, 1000)
  // }
  })

todo02 验证用户名

javascript
const username = document.querySelector('[name="username"]')

const verifyName = function(){
  const reg = /^[a-zA-Z0-9-_]{6,10}$/
  // if (regName.test(username.value)){
  //   username.nextElementSibling.innerHTML = ''
  //   return true 
  // } else {
  //   username.nextElementSibling.innerHTML = '请输入6-10位'
  //   return false 
  // }
  // 反着逻辑改改  ==> 如果验证不通过 
  if (!regName.test(username.value)){
    // 不通过的逻辑
    username.nextElementSibling.innerHTML = '请输入6-10位'
    return false 
  }
  username.nextElementSibling.innerHTML = ''
  return true 
}
username.addEventListener('change', verifyName)

todo03 其他验证

javascript
// 用户名验证
const reg = /^[a-zA-Z0-9-_]{6,10}$/
// 手机号
const reg = /^1(3\d|4[5-9]|5[0-35-9]|6[567]|7[0-8]|8\d|9[0-35-9])\d{8}$/
// 验证码
const reg = /^\d{6}$/
// 密码验证
const reg = /^[a-zA-Z0-9-_]{6,20}$/
javascript
/* ==================== 手机号验证 ===================== */
const phone = document.querySelector('[name="phone"]')
const verifyPhone = function(){
  const reg = /^1(3\d|4[5-9]|5[0-35-9]|6[567]|7[0-8]|8\d|9[0-35-9])\d{8}$/
  // 反着逻辑改改  ==> 如果验证不通过 
  if (!reg.test(phone.value)){
    // 不通过的逻辑
    phone.nextElementSibling.innerHTML = '请输入正确的手机号'
    return false 
  }
  phone.nextElementSibling.innerHTML = ''
  return true 
}
phone.addEventListener('change', verifyPhone)

/* ==================== 短信验证码验证 ===================== */
const code = document.querySelector('[name="code"]')
const verifyCode = function(){
  const reg = /^\d{6}$/
  if (!reg.test(code.value)){
    code.nextElementSibling.innerHTML = '请输入正确的验证码'
    return false 
  }
  code.nextElementSibling.innerHTML = ''
  return true 
}
code.addEventListener('change', verifyCode)

/* ==================== 密码验证 ===================== */

const pwd = document.querySelector('[name="password"]')
const verifyPwd = function(){
  const reg = /^[a-zA-Z0-9-_]{6,20}$/
  if (!reg.test(pwd.value)){
    pwd.nextElementSibling.innerHTML = '请输入正确的格式,数字字母下划线 6-20位'
    return false 
  }
  pwd.nextElementSibling.innerHTML = ''
  return true 
}
pwd.addEventListener('change', verifyPwd)

/* ==================== 密码再次确认验证 ===================== */
const pwdConfirm = document.querySelector('[name="confirm"]')
const verifyRePwd = function(){
  if (pwdConfirm.value !== pwd.value){
    pwdConfirm.nextElementSibling.innerHTML = '两次密码不一致'
    return false 
  }
  pwdConfirm.nextElementSibling.innerHTML = ''
  return true 
}
pwdConfirm.addEventListener('change', verifyRePwd)

todo04 阅读同意协议

点击的时候切换添加一个类名 icon-queren2

javascript
const agree = document.querySelector('.icon-queren')
agree.addEventListener('click', function(){
  // 切换,一开始没有这个类,添加
  // 再次点击,有了这个类,移除
  this.classList.toggle('icon-queren2')
})

todo05 提交整个表单的验证

  1. 先判断是否勾选了同意协议, 如果没有勾选,不提交 ,阻止默认提交 e.preventDefault()
  2. 校验每一个表单是否满足校验条件,如果没有通过校验,阻止默认提交 e.preventDefault()
javascript
const form = document.querySelector('.xtx-form')
form.addEventListener('submit', function(e){
  // 1. 如果同意协议没有勾选,我们就阻止默认提交的行为
  if (!agree.classList.contains('icon-queren2')){
    e.preventDefault() // 阻止默认提交
    return alert('请勾选同意协议') // 加上return 阻止后面的代码运行
  }
  // 判断某个元素是否包含某个类名,包含返回true,否则,返回false
  // console.log(agree.classList.contains('icon-queren2'))

  // 2. 之前的那些输入的表单,如果验证不通过,也要阻止默认提交
  // 连着写,逻辑中断,后面的不会执行
  if (!verifyName()) e.preventDefault()
  if (!verifyPhone()) e.preventDefault()
  if (!verifyCode()) e.preventDefault()
  if (!verifyPwd()) e.preventDefault()
  if (!verifyRePwd()) e.preventDefault()
  // if (!verifyName() || !verifyPhone() || !verifyCode() 
  // || !verifyPwd() || !verifyRePwd()){
  //   e.preventDefault()
  // }
})

todo06 登录页tab切换 login.html

javascript
const tab = document.querySelector('.tab-nav')
const tabPanes = document.querySelectorAll('.tab-pane')
tab.addEventListener('click', function(e){
  if (e.target.tagName === 'A'){
    // 上面的tab高亮
    tab.querySelector('.active').classList.remove('active')
    e.target.classList.add('active')
    // 下面对应的盒子显示
    console.log(e.target.dataset.id)
    // 1. 先让其他的隐藏掉, 干掉所有人
    // for(let i = 0; i < tabPanes.length; i++){
    //   tabPanes[i].style.display = 'none'
    // }
    // 注意:querySelectorAll获取的伪数组 有forEach!
    // 但是children子元素伪数组没有forEach方法
    console.dir(tabPanes)
    tabPanes.forEach(function(el){
      el.style.display = 'none'
    })
    // 2. 让对应的盒子显示
    tabPanes[e.target.dataset.id].style.display = 'block'
  } // end if 
})

todo07 登录页login.html 用户名密码输入登录去首页

javascript
// 点击登录 , 要把用户名存到本地,给首页index使用
const form = document.querySelector('form')
const agree = document.querySelector('.remember')
const username = document.querySelector('[name="username"]')

form.addEventListener('submit', function(e){
  e.preventDefault()
  if (!agree.checked){
    return alert('请勾选同意')
  }

  // 同意了,要把用户名存到本地
  localStorage.setItem('xtx-username', username.value)
  location.href = './index.html' // 跳转
})

通过action跳转

javascript
  form.addEventListener('submit', function(e){
    // e.preventDefault() // 一开始就阻止了跳转到action的地址上
    if (!agree.checked){
      e.preventDefault()
      return alert('请勾选同意')
    }

    // 同意了,要把用户名存到本地
    localStorage.setItem('xtx-username', username.value)
    // location.href = './index.html'

    // 一个是可以用location.href 
    // 还可以用form表单的action属性设置跳转的地址,但是注意 ==> 会被e.preventDefault()阻止
  })

首页渲染

javascript
;(function(){
  const li1 = document.querySelector('.xtx_navs').children[0]
  const li2 = li1.nextElementSibling
  // 一进入页面,就渲染两个li标签
  // 本地存了username,
  const render = function(){
    // 这里不需要转,因为刚才存的就是字符串
    const username = localStorage.getItem('xtx-username')
    if (username){
      li1.innerHTML = `
      <a href="javascript:;">
         <i class="iconfont icon-user">${username}</i>
      </a>
      `
      li2.innerHTML = `<a href="javascript:;">退出登录</a>`
    } else {
      li1.innerHTML = `<a href="./login.html">请先登录</a>`
      li2.innerHTML = `<a href="./register.html">免费注册</a>`
    }
  }
  // 已进入页面,调用render
  render()

  // 点击退出登录 
  li2.addEventListener('click', function(){
    // 删除本地的username
    localStorage.removeItem('xtx-username')
    render()
  })
})()