Day1—DOM

属性

自定义属性

HTML5新增了一项自定义属性,如data-abc=""

我们学习的style=""、name=""等都是原有属性,在JS中操作他们只需要.style、.name就行,但自定义属性不行

自定义属性分为合法属性和不合法属性,

合法的自定义属性的书写方式是data-属性名="",这是W3C的标准,是HTML5推出的

不合法的自定义属性的书写方式是属性名="",前面不加data也就是不合法的自定义属性,虽然也可以使用,但并不符合规范

需要注意,JS中想要操作属性必须先获取标签才可以

自定义属性的方法

不合法属性的获取方法为oDiv.getAttribute('属性名'),前面的oDiv是获取到的标签,.getAttribute()是获取标签的方法

添加方法为oDiv.setAttribute('属性名', '属性值'),必须同时填入两个参数

删除方法为oDiv.removeAttribute('属性名')

当然这三种方法也可以操作合法的自定义属性,不过合法属性也有自己的获取方式

获取:oDiv.dataset.属性名,这里的属性名不需要加上data-,因为dataset会自动解析

设置属性值:oDiv.dataset.属性名 = '属性值',直接赋值即可

自定义属性的应用

自定义属性最多应用在商品详情页、列表页等,因为数据过多,在点击时后台需要获取到一个唯一的标识来确定用户点击的是哪一个商品,进而可以去数据库查找相应的商品详情传回给详情页

也可以配合querySelector使用属性选择器查找来获取相应元素

操作Class

因为class在JS中是关键字,所以使用.className来操作class

添加:oBox.className = 'box',需要注意的是,className会覆盖元素原有的class,因此想保留原有的class就要在赋值时一起再添加一遍

删除:oBox.className = '',利用覆盖特性,只需要赋值空字符串自然就把class全部清除了

ES5新方法

可以看到className实际上并不好使,需要考虑原有类名,ES5提供了类列表的新方法可以使用

添加:oBox.classList.add('class名'),在原有类名基础上添加

删除:oBox.classList.remove('hide'),删除类名

切换:oBox.classList.toggle('class名'),有就删掉,没有就添加

通过操作class来实现页面切换

HTML

  <div class="box">
    <button>按钮1</button>
    <button>按钮2</button>
    <button>按钮3</button>
    <div>元素1</div>
    <div>元素2</div>
    <div>元素3</div>
  </div>

CSS

    .box {
      width: 200px;
      display: flex;
      justify-content: space-between;
      flex-wrap: wrap;
      position: relative;
    }

    .box div {
      height: 200px;
      width: 200px;
      background: #ccc;
      line-height: 200px;
      text-align: center;
      position: absolute;
      top: 19px;
      left: 0;
    }

    .box button {
      border: 0;
      background: #ccc;
    }

    .box .on {
      background: pink;
    }

    .box .active {
      display: block;
      background: pink;
      z-index: 99;
    }

JavaScript

        var aBtn = document.querySelectorAll('.box>button') // 获取所有按钮
        var aDiv = document.querySelectorAll('.box>div')    // 获取box里面的所有div        
        aBtn.forEach(function (item, index) {
            item.onclick = function () {    // 每个按钮都添加一个点击事件
                // console.log(index)
                var iNow = index
                for (var i = 0; i < aBtn.length; i++) { // 循环所有按钮
                    aBtn[i].classList.remove('on')      // 删除所有按钮的class
                    aDiv[i].classList.remove('active')  // 删除所有div的class
                }
                aBtn[iNow].classList.add('on')
                aDiv[iNow].classList.add('active')
            }
        })

节点

属性 作用 返回值
element.parentNode 元素的父节点 一个父元素集合
element.children 元素的子节点 一个子元素的集合
element.firstElementChild 元素的第一个子节点 一个子元素,没有则返回null
element.lastElementChild 元素的最后一个子节点 一个子元素,没有则返回null
element.previousElementSibling 元素的上一个兄弟节点 一个兄弟元素,没有则返回null
element.nextElementSibling 元素的下一个兄弟节点 一个兄弟元素,没有则返回null

操纵DOM

创建:let 变量名 = document.createElement('标签名')

添加:element.appendChild(变量名),将创建好的标签插入到元素的子元素后面

element.insertBefore(变量名, element.children[下标]),将创建好的标签插入到元素的指定子元素前面

删除:element.remove(),删除元素,不需要参数,父元素.removeChild(子节点),删除子节点

修改:element.replaceChild(变量名, element.children[下标]),将创建好的标签替换掉元素指定位置的子元素

克隆:let 变量名 = element.cloneNode(true),复制元素,填写参数true代表不仅复制元素还复制元素内的后代元素,如果不填则只复制元素本身

练习

书写佳能官网的登录注册页面


Day2—DOM

获取元素信息

使用元素.style.样式可以获取到元素的行内样式

使用元素.offsetWidth元素.offsetHeight可以获取到元素的宽高,需要注意的是这是不包含margin的盒子大小,获取到的是数值,且获取的是可视宽高,如果display:none则为0

使用元素.clientWidth元素.clientHeight可以获取到元素的宽高,只包含padding和内容的宽高,且获取的是可视宽高,获取到的是数值型

使用元素.scrollWidth元素.scrollHeight可以获取到滚动内容的宽高,当没有滚动内容的时候就是元素的宽高

使用getComputedStyle(元素).样式可以获取到行外的元素样式,获取到的是带单位的字符串型

获取元素的位置

使用元素.offsetLeft元素.offsetTop可以获取到元素的水平位置和垂直位置,返回值为数值型

但注意这里获取到的相对位置是相对于具有定位属性的祖元素的,当所有的祖元素都没有定位属性就是相对于body的

使用元素.offsetParent可以获取到具有定位属性的祖元素

元素.getBoundingClientRect()返回的是一个对象,包含了元素大小和元素相对于视口的距离等

如果我们想在父元素设置定位的情况下获得元素相对于body的位置可以使用下列函数

        function getPos(obj) {
            var l = 0;
            var t = 0;

            while (obj) {
                l += obj.offsetLeft;
                t += obj.offsetTop;

                obj = obj.offsetParent;  //指向自己有定位的祖元素
            }

            return { left: l, top: t };
        }
        getPos(oBox)

事件

事件有很多,但最需要背的只有九个

事件需要绑定,在前面加on,绑定好的事件在触发后会开始执行相对应的操作

浏览器事件

事件 触发条件
load 加载完成
scroll 滚动
resize 改变窗口大小

鼠标事件

事件 触发条件
click 点击
mouseover 移入
mouseout 移出
mouseenter 移入
mouseleave 移出
mousedown 摁下
mousemove 移动
mouseup 抬起
mousewheel 滚轮
contextmenu 右键
dbclick 双击

overenter的区别是,前者有冒泡,后者没有冒泡,前者移到子元素相当于短暂移出自身,后者移到子元素不会移出自身,前者功能更多,后者更省性能

键盘事件

事件 触发条件
keydown 按下
keyup 抬起

表单事件

事件 触发条件
input 输入
change 改变
submit 提交

焦点事件

事件 触发条件
focus 得到焦点
blur 失去焦点

移动端事件

事件 触发条件
touchstart 按下
otouchmove 移动
touchend 抬起

动画事件

animationend ,动画结束

过渡事件

transitionend,过渡结束

鼠标信息

每个事件里都有一个独特的event事件对象,比如鼠标事件的event就是光标,我们可以获得他的位置,点击的按键等等

我们可以使用event.offsetXevent.offsetY获得光标针对目标元素左上角的相对位置

也可以使用event.pageXevent.pageY来获取到光标相对于页面的位置

如何实现拖拽元素

        let oBox = document.querySelector('.box')
        oBox.onmousedown = function () {
            // event 是事件里面独有的一个对象
            let disX = event.offsetX // 存鼠标到元素位置X
            let disY = event.offsetY // 存鼠标到元素位置Y
            oBox.onmousemove = function () {
                let x = event.pageX - disX  // 鼠标到页面-鼠标到元素=移动距离
                let y = event.pageY - disY  // 鼠标到页面-鼠标到元素=移动距离
                oBox.style.left = x + 'px'  // 改变left值
                oBox.style.top = y + 'px'   // 改变top值
            }
        }
        oBox.onmouseup = function () {
            oBox.onmousemove = null // 取消移动事件
        }

练习

鼠标拖拽封装

let oBox = document.querySelectorAll('div')

    let move = function (element) {
      element.onmousedown = function () {
        let disX = event.offsetX
        let disY = event.offsetY
        console.log(disX, disY)
        element.onmousemove = function () {
          let x = event.pageX - disX
          let y = event.pageY - disY
          element.style.left = x + 'px'
          element.style.top = y + 'px'
        }
      }
      element.onmouseup = function () {
        element.onmousemove = null
      }
    }

    oBox.forEach(function (item) {
      move(item)
    })

页面放大镜(只实现鼠标移动放大镜跟随效果)

    let oBox = document.querySelector('.box1')
    let oBig = document.querySelector('.big')
    let bigLeft = oBig.offsetLeft
    let bigTop = oBig.offsetTop

    oBig.onmouseover = function () {
      oBox.style.display = 'block'
      oBig.onmousemove = function () {
        //让舞台的左上角作为原点,用鼠标位置 - 舞台位置 = 鼠标相对于舞台的位置
        const x = event.pageX - bigLeft
        const y = event.pageY - bigTop
        //让鼠标始终在移动块的最中心
        let width = x - oBox.offsetWidth / 2
        let height = y - oBox.offsetHeight / 2
        // 限制移动块
        // 如果移动块的位置超出舞台,让他回到舞台
        if (width <= 0) {
          width = 0;
        } else if (width >= oBig.offsetWidth - oBox.offsetWidth) {
          width = oBig.offsetWidth - oBox.offsetWidth;
        }
        if (height <= 0) {
          height = 0;
        } else if (height >= oBig.offsetHeight - oBox.offsetHeight) {
          height = oBig.offsetHeight - oBox.offsetHeight;
        }
        // 实时改变移动块位置
        oBox.style.left = width + 'px';
        oBox.style.top = height + 'px';
      }
    }

    oBig.onmouseout = function () {
      oBox.style.display = 'none'
    }

Day3—事件相关

事件源和事件委托

事件源是当前事件所指对象,获取事件源使用event.target

在之前我们一直是获取一个元素然后给他绑定一个事件,在通过事件源获取后我们可以直接给祖元素设置一个事件,他的后代元素就可以通过事件源获取来达到之前的效果,这样可以大大节省性能

比如导航列表,之前我们是获取一组li,然后循环DOM给每个li绑定一个事件,现在我们可以直接给ul设一个事件,在我们触发这个事件的时候获取事件源,如果是li则执行相关代码

    var aLi = document.querySelectorAll('li')   // 获取10个li
    aLi.forEach(function (item) {       // 循环10个li
        item.onclick = function () {    // 每个li添加事件
        item.style.background = 'cyan'
       }
    })
    //事件源获取
    let oUl = document.querySelector('ul')
    oUl.onclick = function () {
      if(event.target.tagName === 'LI'){
        event.target.style.background = 'cyan'
      }
    }

需要注意,使用tagName获得到的标签名是大写

这被称为事件委托,即孩子的事件让父级加,这个父级可以是任意祖元素,但不建议是document

案例:留言板

HTML

  <main>
    <textarea class="txt" cols="30" rows="10" placeholder="请输入"></textarea>
    <div class="option"><button class="btn">发送</button></div>
    <ul class="list">
      <li><a href="javascript:;">111</a><span>删除</span></li>
      <li><a href="javascript:;">222</a><span>删除</span></li>
      <li><a href="javascript:;">333</a><span>删除</span></li>
      <li><a href="javascript:;">444</a><span>删除</span></li>
    </ul>
    <dialog class="reinput">
      <span>×</span>
      <p>内容修改</p>
      <textarea cols="30" rows="10" class="replace"></textarea>
      <input type="text">
      <div class="option">
        <button class="btn2">应用修改</button>
      </div>

    </dialog>
  </main>

CSS

    * {
      margin: 0;
      padding: 0;
      --bgcolor: greenyellow;
    }

    li,
    ul {
      list-style: none;
    }

    a {
      text-decoration: none;
    }

    main {
      width: 500px;
      margin: 0 auto;
    }

    .txt {
      width: 500px;
      height: 100px;
      resize: none;
      padding-top: 10px;
      text-indent: 10px;
    }

    .option {
      text-align: right;
    }

    .btn {
      width: 50px;
      height: 30px;
      background: var(--bgcolor);
      border: 0;
      border-radius: 3px;
      cursor: pointer;
    }

    .list li {
      display: flex;
      justify-content: space-between;
      align-items: center;
      box-shadow: 0 0 10px #ccc;
      padding: 10px;
      margin-top: 20px;
      border-radius: 5px;
    }

    .list a {
      width: 80%;
    }

    .list span {
      color: red;
      cursor: pointer;
      font-size: 12px;
    }

    .reinput {
      position: relative;
      margin: auto;
      border-radius: 5px;
      width: 200px;
      height: 200px;
      text-align: center;
      padding: 0 25px;
      box-sizing: border-box;
    }

    .reinput textarea {
      width: 150px;
      height: 100px;
      margin-top: 20px;
      resize: none;
      outline: 0;
      text-indent: 10px;
      padding-top: 10px;
    }

    .reinput span {
      position: absolute;
      right: 5px;
      top: 0px;
      cursor: pointer;
    }

    .btn2 {
      background: var(--bgcolor);
      border: 0;
      border-radius: 3px;
      width: 70px;
      height: 30px;
    }

JavaScript

    let oTxt = document.querySelector('.txt')
    let oBtn = document.querySelector('.btn')
    let oList = document.querySelector('.list')
    let oRe = document.querySelector('dialog')
    let oRetxt = document.querySelector('.replace')
    let replace = 0

    oBtn.onclick = function () {
      let li = document.createElement('li')  //创建元素
      if (oTxt.value.trim() === '') { //判断发送的内容是否为空
        oTxt.value = ''
        return
      }
      li.innerHTML = `<a href="javascript:;">${oTxt.value}</a><span>删除</span>`  //插入文本域内容
      oList.insertBefore(li, oList.children[0]) //把元素插入到所有元素得最前面
      oTxt.value = ''  //清空文本域
    }

    oList.onclick = function () {
      if (event.target.tagName === 'SPAN') {  //删除功能
        event.target.parentNode.remove()
      }
      if (event.target.tagName === 'A') {   //修改功能
        replace = event.target.parentNode
        oRe.showModal()
        oRetxt.value = event.target.textContent
      }
    }

    oRe.onclick = function () {   //点击事件
      if (event.target.tagName === 'SPAN') {
        oRe.close()
      }
      if (event.target.tagName === 'BUTTON') {    //替换按钮
        replace.innerHTML = `<a href="javascript:;">${oRetxt.value}</a><span>删除</span>`
        oRe.close()
      }
    }

事件监听

添加监听器

之前我们直接使用on+事件来绑定事件,这种方式有一个问题就是做不到同一个元素想要绑定多个相同事件,后绑定的事件会替换掉前面的事件

为了解决这个问题,可以使用element.addEventListener('事件类型',执行函数,事件流)来添加多个监听器来监听事件

本质上是将执行函数添加到事件对象的监听列表里,因此如果遇到两个相同的非匿名函数,方法不会再次添加相同的函数,但如果书写的是匿名函数则会重复添加,这是因为非匿名函数在调用时都是相同的地址,而匿名函数每次声明后都是不同的地址,因此方法无法分辨

如果有两个相同的函数但是事件流一个是冒泡一个是捕获,则方法会多次添加

移出监听器

监听器添加后不会被替换,因此想要取消监听器就需要专门的方法

element.removeEventListener('事件类型',执行函数,事件流),需要注意的是,这里要求事件类型、执行函数地址、事件流都相同才可以移除,任何参数不同都会导致移除失败

阻止事件默认行为

在想要阻止的事件下书写event.preventDefault()就可以阻止默认行为,比如复制、超链接的跳转等

事件流

事件流分为三个阶段,事件捕获、事件目标、事件冒泡

事件冒泡

事件冒泡是在事件相同元素嵌套的情况下,子元素事件触发后他的父元素也会一层一层从里到外触发一遍,这被称为事件冒泡

事件冒泡不是bug,可以人为阻止

在希望阻止冒泡的元素绑定的事件里加上event.stopPropagation(),这样他之后的祖元素就不会再触发了,这个方法真正的意思是阻止传播,因此在事件捕获中他也一样起作用

前者兼容所有浏览器,但是移动端用不了,后者移动端可以使用,但不兼容IE10以下

事件捕获

事件捕获与事件冒泡正相反,他是从最外层元素开始一路触发到目标元素

可以人为开启事件捕获,开启的方法是在添加监听器的时候设置第三个参数为true,默认为false,也就是事件冒泡

按键获取

事件对象下有event.keyCode,可以获取到键盘按下的按键的ASCII码,通过简单的判断就可以在按下相应的按键后执行操作

keyCode已经被废弃了,现在使用event.key来获取,他获取到的是字符串类型的物理按键名,比如回车就显示为Enter

也可以获取到组合键event.shiftKeyevent.ctrlKeyevent.altKey,再通过与判断就可以判断出组合键

练习

登录页面

html

  <main>
    <button class="open">登录页面</button>
    <dialog class="login">
      <h2 class="title">登录<span class="close">×</span></h2>
      <div class="avatar"><img src="avatar.ee5a837.png" alt=""></div>
      <form action="javascript:;">
        <p class="user"><span>用户名:</span><input type="text" placeholder="请输入用户名"></p>
        <p class="pass"><span>密码:</span><input type="password" placeholder="请输入密码"></p>
        <span class="error"></span>
        <p class="submit"><button class="btn">登录</button></p>
      </form>
    </dialog>

    <dialog class="login">
      <h2 class="title">标题<span class="close">×</span></h2>
      <div class="avatar"><img src="avatar.ee5a837.png" alt=""></div>
      <h3 class="tip">登陆成功</h3>
      <p>本页面 <b class="count">3</b> 秒后自动关闭</p>
    </dialog>
  </main>

CSS

    * {
      margin: 0;
      padding: 0;
    }

    body,
    html {
      height: 100%;
    }

    li,
    ul {
      list-style: none;
    }

    a {
      text-decoration: none;
    }

    main {
      width: 100%;
      height: 100%;
      text-align: center;
    }

    main>button {
      width: 70px;
      height: 30px;
      background: greenyellow;
      margin-top: 30px;
      cursor: pointer;
    }

    .back {
      width: 100%;
      height: 100%;
      background: rgba(0, 0, 0, 0.5);
      position: absolute;
      top: 0;
      left: 0;
    }

    .login,
    .success {
      border-radius: 5px;
      background: #ccc;
      box-sizing: border-box;
      position: absolute;
      top: calc(50% - 90px);
      left: calc(50% - 160px);
      overflow: hidden;
      width: 246px;
      height: 279px;
    }

    .login h2 {
      font-size: 20px;
      display: flex;
      justify-content: space-between;
      padding-left: 10px;
      border-bottom: 1px solid #ccc;
      line-height: 40px;
      position: relative;
      background: pink;
    }

    .login h2 span {
      line-height: 1;
      font-size: 16px;
      position: absolute;
      right: 5px;
      top: 0;
      cursor: pointer;
    }

    .avatar img {
      width: 100%;
      height: 100%;
    }

    .avatar {
      width: 70px;
      height: 70px;
      border-radius: 50%;
      overflow: hidden;
      margin: 0 auto;
      margin-top: 10px;
      /* background: red; */
    }

    .login p {
      font-size: 14px;
      text-align: center;
      line-height: 30px;
      margin-top: 10px;
    }

    .login p span {
      width: 60px;
      height: 30px;
      text-align: left;
      display: inline-block;
    }

    .login form {
      width: 100%;
      padding: 10px;
      box-sizing: border-box;
    }

    .login input {
      box-shadow: 0 0 10px #ddd;
      border: 0;
      border-radius: 2px;
      outline: 0;
      text-indent: 5px;
      height: 25px;
      /* margin-right: 20px; */
    }

    .login .submit {
      margin: 0;
    }

    .btn {
      display: inline-block;
      width: 100%;
      height: 30px;
      background-color: orangered;
      border: 0;
      border-radius: 2px;
      cursor: pointer;
    }

    .error {
      height: 20px;
      width: 100%;
      font-size: 12px;
      color: red;
      display: inline-block;
    }

    h3 {
      margin-top: 20px;
      margin-bottom: 10px;
      color: red;
    }

JavaScript

    let oOpen = document.querySelector('.open')
    let oLogin = document.querySelectorAll('.login')
    let oTitle = document.querySelector('.title')
    let oUser = document.querySelector('.user>input')
    let oPass = document.querySelector('.pass>input')
    let oError = document.querySelector('.error')
    let oCount = document.querySelector('.count')


    //点击显示登录页面
    oOpen.onclick = function () {
      oLogin[0].showModal()
    }

    //移动函数封装
    function move() {
      oLogin.forEach(function (item) {
        let disX = event.offsetX
        let disY = event.offsetY
        document.onmousemove = function (e) {
          let x = e.pageX - disX
          let y = e.pageY - disY
          if (x < 0) {
            x = 0
          } else if (x > innerWidth - item.offsetWidth) {
            x = innerWidth - item.offsetWidth
          }
          if (y < 0) {
            y = 0
          } else if (y > innerHeight - item.offsetHeight) {
            y = innerHeight - item.offsetHeight
          }
          item.style.left = x + 'px'
          item.style.top = y + 'px'
        }
        oTitle.addEventListener('mouseup', function () {
          document.onmousemove = ''
          console.log(1)
        })
        event.preventDefault()
      })

    }


    //鼠标按下执行移动函数
    oTitle.addEventListener('mousedown', move)


    // 验证账户密码是否正确
    function ver() {
      if (oUser.value === 'admin' && oPass.value === '123456') {
        oLogin[0].close()
        oLogin[1].showModal()
        let s = 3
        let timer = setInterval(function () {
          s--
          oCount.innerHTML = s
          if (s === 0) {
            oLogin[1].close()
            s = 3
            oCount.innerHTML = s
            clearInterval(timer)
          }
        }, 1000)

      } else {
        alert('账户或密码输入错误')
        oError.innerHTML = '密码或用户名错误'
        let timer2 = setTimeout(function () {
          oError.innerHTML = ''
          clearTimeout(timer2)
        }, 3000)
      }
    }

    // 回车时调用验证函数
    oLogin[0].onkeydown = function (e) {
      if (e.keyCode === 13) {
        ver()
      }
    }

    oLogin.forEach(function (item) {
      item.onclick = function (e) {
        if (e.target.className === 'close') { //点击×时关闭页面
          item.close()
        } else if (e.target.className === 'btn') {//点击按钮时调用验证函数
          ver()
        }
      }
    })

Day4—正则

正则格式

正则又被称为规则表达式,它的构造方式有两种

使用构造函数const reg = new RegExp('规则', '匹配')

使用字面量const reg = /规则/匹配符,(推荐)

正则一般使用在输入框的匹配上、屏蔽词的替换等

规则

普通字符

规则可以是任何字符,但这样的话就只能匹配对应的字符,可以用在屏蔽词上,但如果想要更进一步的效果就需要使用元字符来匹配

元字符

正则的字符规则叫做元字符

\d表示数字0-9\D表示非数字0-9

\w表示a-z、A-Z、0-9、__的字符,\W表示非a-z、A-Z、0-9、__

\s表示空白字,也就是空格,\S表示非空白字

一个元字符只能匹配一个字符,如果想要匹配多位就需要写多个元字符,比如想要匹配两位数字就需要写成/\d\d/这种格式,此时字符串中必须有两位连续数字才能匹配成功,比如3356这种,而如果是19t等都会失败,后面会讲简便写法,但思想都是需要多个元字符

正则提供了验证方法/规则/匹配.test('字符串')或者reg.test('字符串'),返回值是布尔值

        console.log(/\d/.test('1')) // true
        console.log(/\d/.test('a')) // false

        console.log(/\w/.test('1')) // true
        console.log(/\w/.test('a')) // true
        console.log(/\w/.test('&')) // false

        console.log(/\s/.test(' ')) // true
        console.log(/\s/.test(''))  // false
        console.log(/\s/.test('a')) // false

边界符

边界符分为首尾限定,其中首限定为^,他表示字符串必须以正则规则内容开头,否则为false,尾限定为$,他表示字符串必须以正则规则内容结尾,否则为false

当两者连用时为精确匹配,严格限定字符串的内容和个数

比如/\d/的写法事实并不严谨,如果字符串是asdfgges27dasfaf,验证返回结果是true,因为此时只要字符串里有一位是数字就判断为真

如果想要严格匹配就需要在首尾加上^$,写成这样的格式/^\d$/,此时匹配的字符串要求必须是纯数字(因为\d是匹配0-9的数字),且因为只有一个元字符因此字符串长度要求为1,始终要牢记,一个元字符只能匹配一个字符,因此当他要求以这个元字符开头这个元字符结尾的时候就限定了字符串的内容和长度

如果是/^\d\d\d$/就要求字符串必须是纯数字,且必须是三位数(因为有三个元字符)

console.log(/^\d$/.test('1'))   // true
console.log(/^\d$/.test('1a'))  // false
console.log(/^\d$/.test('11'))  // false
//想要匹配多长的字符串就需要多少个元字符
console.log(/^\d\d\d\d\d\d\d\d\d\d\d$/.test('19195118885'))  // true

限定符

限定符就是允许特定字符或字符集合自身重复出现的次数,他就是用来使正则式书写简便的

+,匹配的字符最少出现一次,最多出现不限次数

/^\d+$/举例,他匹配的是只由0-9组成的字符串(^\d$的限制)最少可以是一位数,最多可以有无数位,这样我们想要匹配多位数字就不需要多次书写元字符了

console.log(/^\d$/.test('11'))  // false  //不加限定符
//加限定符
console.log(/^\d+$/.test('1123'))// true
console.log(/^\d+$/.test(''))    // false
console.log(/^\d+$/.test('a1'))  // false
console.log(/^\d+$/.test('1'))   // true

*,匹配的字符最少出现零次,最多出现不限

/^\d*$/举例,他匹配的是只由0-9组成的字符串(^\d$的限制),最少可以是空字符串,最多可以有无数位

console.log(/^\d*$/.test(''))    // true
console.log(/^\d*$/.test('1'))   // true
console.log(/^\d*$/.test('a'))   // false

?,匹配的字符最少零次,最多一次

/^\d?$/举例,他匹配的是只由0-9组成的字符串(^\d$的限制),最少可以是空字符串,最多是一位数

console.log(/^\d?$/.test(''))    // true
console.log(/^\d?$/.test('1'))   // true
console.log(/^\d?$/.test('a'))   // false

{数字},匹配的字符出现固定次数

/^\d{3}$/举例,他匹配的是只由0-9组成的字符串(^\d$的限制),且只能是三位数

  • {数字,}的格式,表示最少多少次,最多不限,比如{1,},此时表示匹配的字符最少出现一次,最多不限,等价于+{3,},表示最少出现三次,最多不限
  • {数字,数字},表示最少多少次,最多多少次,比如{0,1},表示最少零次,最多不限,等价于?
console.log(/^\d{4}$/.test('1234'))     // true
console.log(/^\d{4}$/.test('123'))      // false
console.log(/^\d{4,}$/.test('123123'))  // true     最少4次,多了不限
console.log(/^\d{4,6}$/.test('123123')) // true     最少4次,最多6次
console.log(/^\d{4,6}$/.test('12312356')) // false

特殊字符

.,代表所有,匹配任何字符,只要有字符就是真,不匹配空字符串

\,表示转义,可以使有作用的关键字变成普通字符,比如.com里的.就需要转义成普通字符

(|),表示任选一组,书写格式为(规则一|规则二|规则三|……),只要满足任一规则就可以匹配

console.log(/^(com|cn|net|org|vip|live|在线|视频|直播)$/.test('live'))    // true
console.log(/^(com|cn|net|org|vip|live|在线|视频|直播)$/.test('com.cn'))  // false
console.log(/^(com|cn|com\.cn|vip|live|在线|视频|直播)$/.test('com.cn'))  // true

[],表示任选一个,书写格式为[一个字符串],满足字符串里的有且仅有一个字符就是真

仔细体会下面的案例

console.log(/^[abc]$/.test('aabc'))     // false
console.log(/^[abc]$/.test('abcc'))     // false
console.log(/^[abc]$/.test('abbbbc'))   // false
console.log(/^[abc]$/.test('a'))        // true
console.log(/^[abc]$/.test('b'))        // true
console.log(/^[abc]$/.test('c'))        // true
console.log(/^[abc]$/.test('ab'))       // false
console.log(/^[abc]$/.test('aa'))       // false

[]里面可以书写范围,比如[a-z]表示的是字母a到z,[0-9]表示的是数字0到9,[a-z0-9]是前两者合起来,需要注意[13-79]表示的不是13到79,而是1、3到7、9,里面不会出现两位数,不要将相邻数字关联起来

//表示 1 3 4 5 6 7 9
console.log(/^[13-79]$/.test('21'))     // false
console.log(/^[13-79]$/.test('99'))     // false
console.log(/^[13-79]$/.test('79'))     // false
console.log(/^[13-79]$/.test('66'))     // false
console.log(/^[13-79]$/.test('5'))      // true
console.log(/^[13-79]$/.test('16'))     // false

[]每次也是只能匹配一个字符,因此他也可以通过限定符来多次使用

[]支持十六进制字符编码,比如汉字的编码范围就是\u4e00-\u9fa5,因此我们可以通过[\u4e00-\u9fa5]来匹配汉字字符

比如下面的十六进制匹配

console.log(/^([0-9a-f]{3}|[0-9a-f]{6})$/.test('ccc'))      // true
console.log(/^([0-9a-f]{3}|[0-9a-f]{6})$/.test('f69201'))   // true
console.log(/^([0-9a-f]{3}|[0-9a-f]{6})$/.test('cccc'))     // false

^,除了在首尾中使用,当他出现在[]内的时候表示取反

// 中文
console.log(/^[\u4e00-\u9fa5]$/.test('一'))        // 4e00 9fa5是16进制写法    \u代表正则的标签
console.log(/^[\u4e00-\u9fa5]$/.test('哈'))        // 4e00 9fa5是16进制写法    \u代表正则的标签
console.log(/^[\u4e00-\u9fa5]$/.test('9'))        // 4e00 9fa5是16进制写法    \u代表正则的标签
// 非中文 [^\u4e00-\u9fa5]
console.log(/^[^\u4e00-\u9fa5]$/.test('9'))        // [^abc] 上箭头是取反的意思,只要出现在中括号里面的都取反

完整规则式的书写

以一个案例来讲解

邮箱匹配

邮箱KaiouseiXMS@gmail.com,其中KaiouseiXMS是用户名,@是标识符,gmail是域名,.com是域名后缀

因此我们逐个分析

首先是用户名,一般网站要求不能少于五位字符且不能多于十六位字符,数字英文下划线都可以,因此规则式可以写成\w{5,16}

然后是标识符,就一个@

之后是域名,一般都是不少于两位,最长不限,而且域名里不会出现下划线,所以不能用\w,我们可以使用上面学到的特殊字符,写成[0-9a-zA-Z]{2,}

最后是域名后缀,可以英文可以中文不能数字,最少两位最多不限,所以可以写成\.[a-zA-Z\u4e00-\u9fa5]{2,}

然后我们只需要将以上拼起来放在//里就可以了

最终完整表达式为/^\w{5,16}@[a-zA-Z0-9]{2,}\.[a-zA-Z\u4e00-\u9fa5]{2,}$/

0-255数字匹配

/*
     0-255
     一位0-9         \d
     两位10-99       [1-9]\d
     三位    
         100-199     1\d{2}
         200-249     2[0-4]\d
         250-255     25[0-5]
*/
console.log(/^(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])$/.test('255'))

匹配符

/规则/匹配,之前讲的都是规则,这里讲匹配符

i,忽略大小写

m,多行,多用在小说网站

g,全局,不能和首位一起出现,会冲突

console.log(/[a-z]/img.test('A'))   // true
console.log(/[a-z]/ig.test('A'))   // true
console.log(/[a-z]/ig.test('18722878a6183D28'))   // true

Day5—ES6

变量的解构赋值

数组的解构赋值

我们之前给变量赋值都是直接给定值,但我们可以按照一定格式直接从数组或对象中提取值并赋予变量,这被称为解构

let a = 1
let b = 2
let c = 3

可以写成下面形式

let [a, b, c] = [1, 2, 3]

上述代码表示按照顺序从数组提取数据

事实上,只要结构相同就可以读取到数值,但如果不同就会报错,比如直接赋值

let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3

let [ , , third] = ["foo", "bar", "baz"];
third // "baz"

let [x, , y] = [1, 2, 3];
x // 1
y // 3

let [a] = 1  //报错

但如果解构不成功就会得到undefined,也就是变量比数值多

let [a] = []  //a = undefined
let [a , b] = [1] //a = 1  b = undefined

另一种情况是不完全解构,也就是数值比变量多,此时不会报错,变量也能获取到值

let [a] = [1,2]

默认值

解构可以设定默认值

let [a, b = 1] = [2]  //a = 2 , b = 1
let [a, b = 1] = [2 , 3]  //a = 2 , b = 3

只有在数组成员严格等于undefined的时候默认值才会触发,否则不会触发

let [x = 1] = [undefined];
x // 1

let [x = 1] = [null];
x // null

对象的解构赋值

对象和数组最大的不同在于,对象是无序的,因此需要变量名必须和对象的关键字对应,而顺序则无所谓

let { bar, foo } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"

let { baz } = { foo: 'aaa', bar: 'bbb' };
baz // undefined

与数组一样可以嵌套,嵌套的时候必须声明内层属于哪个关键字

const node = {
  loc: {
    start: {
      line: 1,
      column: 5
    }
  }
};

let { loc, loc: { start }, loc: { start: { line }} } = node;
line // 1
loc  // Object {start: Object}
start // Object {line: 1, column: 5}

当我们需要匹配的关键字已经被声明和使用了怎么办,这时候可以将解构的变量改名,需要写成下面这种格式

let {foo:a ,bar:b} = {foo:1 ,bar:2} //a = 1 b = 2

我们可以发现,实际上真正被赋值的不是关键字,相同的关键字只是用来匹配而已,真正存储的还是关键字后的变量ab,前面的foobar都只是用来匹配的

字符串

字符串也可以解构赋值

因为这相当于将字符串看做了一个数组

const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"

对象简写

首先我们知道对象可以存储变量

let name = 'jack'
let  id = 9527
let obj = {
    name:name,
    id:id,
}

在关键字和变量名相同的情况下,我们可以直接合并

let name = 'jack'
let  id = 9527
let obj = {
    name,
    id,
}

箭头函数

一个普通的赋值函数是

let fn = function (参数){
    语句
}

箭头函数可以将function去掉,然后在参数和语句块之间加一个箭头

let fn = (参数) => { 语句 }

还可以更进一步的简化,当参数只有一个的时候,可以不写(),当语句只有一条语句的时候,可以省略{}return,于是函数就可以简写成

const res = arr.filter(function (item) { return item > 50 })
// 函数简写
const res = arr.filter(item => item > 50)

当返回的是对象字面量时,需要使用小括号包起来,因为对象是大括号,函数体也是大括号

箭头函数和函数最重要的一点区别就是,箭头函数没有this,他可以使用父级的this,箭头函数也没有arguments,如果需要取值则需要自定义一个数组保存参数**

浅拷贝

...,展开运算符,只要是复杂数据类型都能用

const arr = [12, 5, 7]
console.log(...arr) // 12,5,7
const arr2 = [...arr]
console.log(arr, arr2) // arr = [12, 5, 7] arr2 = [12, 5, 7]

浅拷贝只有单层数据可以使用,多层数据会导致内层数据变成引用

可以用在函数形参内来保存剩余参数(箭头函数也适用),function get(...arr){}

可以用在求数组的最大最小值,Math.max(...arr)

可以用来合并数组,arr[...arr1,...arr2]

深拷贝

浅拷贝不好用的时候就需要深拷贝JSON.parse(JSON.stringify(拷贝对象))

序列化

我们前端使用对象时使用的格式不是后端想要的,后端想要的是一个字符串'{"name": "Jack", "age": 18, "id": 9527, "flag": true}'被称为JOSN串

所以我们需要一个方法直接将格式转化为后端需要的JOSN串,这就是JSON.stringify(obj)

反序列化

我们要传后端需要转格式,那么后端传给我们也需要转格式,而这就需要反序列化JSON.parse(json串)

这两者合在一起组成了深拷贝

新增数据

Set、Map、Symbol

新增数据类型在实际开发中很少甚至几乎不会用到

Symbol,基本数据类型,主要用来给对象当属性名使用的,可以使属性名独一无二不产生冲突

Set,复杂数据类型,使用方法是let s = new Set(数组),参数是一个单层数组,可以自动去重,而如果是嵌套数组则不能使用

Map,复杂数据类型,使用方法是let m = new Map(),对象只能把字符串当关键字,但是Map可以用任何数据类型当关键字,实际开发中作用是用来生成链表

let m = new Map()   // 链表, 有个初始key,值就是一下个属性的key
m.set('a', '1').set('2', 'c').set('c', '3').set('1', 'b').set('b', '2')  //这里的set不是数据类型的Set
console.log(m)   // {"a" => "1","2" => "c","c" => "3","1" => "b","b" => "2"}

for of

for of循环,可以用来循环可迭代数据类型(具有iterator接口),比如数组、字符串等,也可以用来循环MapSet,他遍历取到的是对象的值,而非键名,而且是有序的,他不能用来遍历对象

他和for in有区别,决不能混用,for in主要遍历对象,输出的是字符串类型的键名而非值,而且是无序的,虽然也能遍历数组但是有可能会有问题

很重要一点,不要将数据类型Map和数组循环的map混淆,也不要将数据类型Set和Map的方法set混淆

昨天练习简化

昨天的练习是,当手机号、邮箱号、电话号、0~255的整数都符合要求时,让按钮可以点击,任何一项不符合要求都会导致按钮变得不可使用

    <div class="form">
        <div class="group">
            <span>手机号:</span>
            <input type="text">
        </div>
        <div class="group">
            <span>邮箱号:</span>
            <input type="text">
        </div>
        <div class="group">
            <span>电话号:</span>
            <input type="text">
        </div>
        <div class="group">
            <span>0-255:</span>
            <input type="text">
        </div>
        <div class="group">
            <button disabled class="sub">提交</button>
        </div>
    </div>
    <script>
        // 使用数据
        var flagArr = [
            {
                re: /^1[3-9]\d{9}$/,
                flag: false
            },
            {
                re: /^\w{5,16}@[0-9a-zA-Z]{2,}\.[a-zA-Z\u4e00-\u9fa5]{2,}(\.cn)?$/,
                flag: false
            },
            {
                re: /^0[1-9]\d{1,2}[1-9]\d{6,7}$/,
                flag: false
            },
            {
                re: /^(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])$/,
                flag: false
            },
        ]

        var oSub = document.querySelector('.sub')   // 提交按钮
        var aInp = document.querySelectorAll('.form input')// 获取所有输入框
        aInp.forEach(function (item, index) { // 循环所有输入框
            item.onblur = function () { // 每个输入框添加一个失去焦点事件
                flagArr[index].flag = flagArr[index].re.test(item.value)    // 改变数组其中一个对象的属性值 flag
                oSub.disabled = !(flagArr.every(item => item.flag)) // 把简单的代码逻辑,变成了表达式
            }
        })
    </script>

程序的思想很重要