JS基础2 | 青训营笔记

93 阅读7分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 6 天

3.2 事件基础

事件

定义:编程时系统内发生的动作或者发生的事情,比如用户在网页上单击一个按钮(鼠标点击/经过/离开/拖拽,键盘回车)

事件监听(绑定事件/注册事件):让程序检测是否有事件发生,一旦有事件触发,就立即调用一个函数做出响应

DOM L0 事件源.on事件类型 = function(){}

元素.addEventListener('事件', 要执行的函数)

  • 事件监听三要素

    • 事件源:dom元素:文本框,button, <div>标签
    • 事件:用什么方式触发:鼠标单击click, 鼠标经过mouseover
    • 事件调用的函数
 //1. 获取元素
 let btn = document.querySelector('button')
 btn.addEventListnener('click', function() {
     alert('被点击了')
 })
例子:点击后关闭
 <body>
     <div class="qrCode">
         <img src="" alt>
         <i class="close_btn"x</i>
     </div>
     <script>
         //1. 获取元素 事件源i 关闭qrCode
         let closeBtn = document.querySelector('.close_btn')
         let qrCode = closeBtn = document.querySelector('.qrCode')
         //2. 事件监听
         closeBtn.addEventListner('click', function() {
             qrCode.sytle.display = 'none'
         })
     </script>
 </body>
 ​
 ​

事件源:btn

关闭:css修改display = 'none'

  • 事件类型
小米搜索框:光标
 .result-list {
     display: none;//一开始隐藏,得到光标后显示
 }
 ​
 .mi .search {
     border: 1px solid orange;
 }
 ​
 <body>
     <div class="mi">
         <input type="search" placeholder="笔记本">
         <ul class="result-list">
             <li><a href="#">全部商品</a></li>
             <li><a href="#">小米11</a></li>
             <li><a href="#">小米10</a></li>
             <li><a href="#">空调</a></li>
         </ul>
     </div>
     <script>
         let search = document.querySelector('input[type=search]')
         let list = document.querySelector('.result-list')
         search.addEventLisner('focus', function() {
             // 'focus' -> 'mouseenter'鼠标经过
             // 显示下拉菜单
             list.style.display = 'block'
             //文本框边框变色
             search.classList.add('search')
         })
         // 失去焦点
         search.addEventLisner('blur', function() {
             // 'blur' -> mouseleave 鼠标离开
             // 隐藏下拉菜单
             list.style.display = 'none'
             //文本框边框去色
             search.classList.remove('search')
         })
     </script>
     
 </body>
全选框例子
 <table>
     <tr>
         <th class="checkAll">
             <input type="checkbox" name="" 全选>
         </th>
         <th>表头...</th>
     </tr>
     <tr>
         <td>
             <input type="checkbox" name="check" class="ck">
         </td>
     </tr>
 </table>
 ​
 <script>
     // 1. 获取元素 全选和小复选框
     let all = document.querySelector('#checkAll')
     let cks = document.querySelectorAll('.ck')
     // 2. 事件监听 全选按钮 获得状态
     all.addEventListener('click', function() {
         for (let i = 0; i < cks.length; i++) {
             cks[i].checked = all.checked;//this.checked this指向all
         }
     })
 </script>
绑定多个事件--循环

只有所有单选框都全选,全选框勾选上

 <script>
     for (let i = 0; i < cks.length; i++) {
         cks[i].addEventListener('click', function() {
             //每次点击,都有遍历所有的小按钮
             for (let j = 0; j < cks.length; j++) {
                 if (cks[j].checked === false) {
                     all.checked = false
                     return;
                 }
             }
             all.checked = true;
         })
     }
 </script>
购物车加减
 <body>
     <div>
         // 都是字符串型,加减要类型转换
         <input type="text" id="total" value="1" readonly>
         <input type="button" value="+" id="add">
         <input type="button" value="-" id="reduce" disabled>//默认禁用状态
         <script>
             let total = document.querySelector('#total')
             let add = document.querySelector('#add')
             let reduce = document.querySelector('#reduce')
             
             add.addEventListener('click', function() {
                 total.value++;//有隐式转换
                 reduce.disabled = false
             })
             
             reduce.addEventListener('click', function() {
                 total.value--
                 if (total.value <= 1) { //比较运算符也有隐式转换
                     reduce.disabled = true
                 }
             })
         </script>
         
     </div>
     
 </body>

高阶函数

函数表达式:把函数(非调用,而是定义)赋值给变量

let fn = function() {...}

btn.onclick = function(){}

回调函数:将函数A作为参数传递给函数B,函数A成为回调函数

function fn() {}
setInterval(fn, 1000)
// fn为回调函数,在1000ms后才调用

环境对象

什么是环境对象:函数内部特殊的变量this,代表着当前函数运行时所处的环境

 function fn() {
     console.log(this) // window
 }
 fn() // 相当于window.fn()
 ​
 let btn = document.querySelector('button')
 btn.addEventListener('click', function() {
     console.log(typeof this) // this指向btn
 })

谁调用,this就是谁

编程思想

tab栏切换:点到哪个按钮亮显哪个

 <body>
     <button class="pink">第1个</button> * 5
     <script>
     let btns = document.querySelectorAll('button')
     for (let i = 0; i < btns.length; i++) {
         btns[i].addEventListener('click', function() {
             // solution 1
             for (let j = 0; j < btns.length; j++) {
                 btns[j].classList.remove('pink')
             }
             // solution 2, better
             document.querySelector('.pink').classList.remove('pink')//要有初始的pink style
             this.classList.add('pink')
         })
     }
     </script>
 </body>

todo

综合案例

 <script>
   let tabs = document.querySelectorAll('.tab-item');
   let products = document.querySelectorAll('.products .main');
   console.log(products.length)
   for (let i = 0; i < tabs.length; i++) {
     tabs[i].addEventListener('click', function() {
       document.querySelector('.tab .active').classList.remove('active')
       document.querySelector('.products .active').classList.remove('active')
       tabs[i].classList.add('active')
       products[i].classList.add('active')
     })
   }
 </script>

3.3 节点操作

节点操作:增删改查

DOM节点是什么:DOM树里每一个内容

节点分类:元素节点 div、属性节点 class属性、文本节点

查找父节点:子元素.parentNode 返回最近一级的父节点,找不到返回null

例子:关闭二维码,获得子节点"close_btn",关闭父节点erweima

 <div class="erweima">
     <img src-"" alt>
     <i class="close_btn">x</i>
 </div>
 ​
 <script>
     let son = document.querySelector('.close_btn')
     son.parentNode.style.display = 'none'
 </script>

查找子节点

  • childNodes 获得所有子节点
  • children 仅获得所有元素节点,返回一个伪数组 父元素.children
 <body>
     <butto>点击</butto>
     <ul>
         <li>1</li>
         <li>1</li>
         <li>1</li>
         <li>1</li>
         <li>1</li>
         <li>1</li>
     </ul>
 </body>
 ​
 <script>
     let btn = document.querySelector('button')
     let ul = document.querySelector('ul')
     btn.addEventListener('click' function() {
         for (let i = 0; i < ul.children.length; i++) {
             ul.children[i].style.color="red"
         }
     })
 </script>

查找兄弟节点 元素.nextElementSibling 下一个兄弟元素

元素.previousElementSibling 上一个兄弟元素

增加节点

  • Step1:创建一个新的节点 document.createElement('标签名')

    • 写内容元素.innerHTML = "some content/div标签"
  • Step2:把新节点放入到指定的元素内部

    • 插入到父元素的最后一个子元素父元素.appendChild(要插入的元素) document.body.appendChild(newElement)
    • 插入到父元素中某个子元素的前面 父元素.insertBefore(要插入的元素,在哪个元素前面) 新元素永远放在第一个ul.insertBefore(newLi, ul.children[0])

克隆节点:克隆出一个跟原标签一样的元素

(区别于克隆对象 let cloneObj = Object.assign(new Date(), oldDate);

元素.cloneNode(布尔值)

  • true: 包含后代节点一起克隆
  • false:克隆时不包含后代节点,默认
 <body>
     <ul>
         <li>11111</li>
     </ul>
 </body>
 ​
 <script>
     let ul = document.querySelector('ul')
     let newUl = ul.cloneNode(true) // 
     document.body.appendChild(newUl)
 </script>

删除节点

JS的原生DOM操作中,要删除元素必须通过父元素删除

父元素.removeChild(要删除的元素)

与隐藏节点display:none有区别,隐藏节点还是存在的

数组里删除元素,区别

 <body>
     <ul>
         <li>11111</li>
     </ul>
 </body>
 <script>
     let btn = document.querySelector('button')
     let ul = document.querySelector('ul')
     btn.addEventListener('click', function() {
         ul.removeChild(ul.children[0])
     })
 </script>

时间对象

  • 实例化 当前时间:let date = new Date() 指定时间 let date = new Date('1949-10-01')

  • 时间对象方法:得到number的年月日时分秒与星期 getFullYear() getMonth() getDate() getDay() getHours getMinutes() getSeconds()

  • 例子:实现一个页面时间数秒更新的功能

     let div = ;
     ​
     getTime() // 避免第0-1s会有一个空白
     setInterval(getTime, 1000)
     ​
     function getTimie() {
         // 获取年月日,时分秒;展示
         div.innerHTML = ``
     }
    
  • 时间戳:用于倒计时,从1970年...起至现在的毫秒数

    获取时间戳的方法

    • let date = new Date(); date.getTime()
    • console.log(+new Date(''2021-8-30 12:00:00)) 指定了时间
    • console.log(Date.now())

例子:有一个下班倒计时的例子

综合案例:微博评论发布:点击后增加标签

获取[0, length - 1]

Math.floor(Math.random()*(max+1));

重绘和回流--面试题

类似面试题:节流、防抖

浏览器如何进行渲染

HTML+样式的解析

重点:回流(也叫重排):给盒子layout(布局)

重绘和回流:重绘不一定引起回流,但回流一定会引起重绘

任何一个盒子的尺寸变化了,要回流并且重绘

尺寸不变,只是color, background-color这些属性变化,要重绘

回流的操作:

  • 页面的首次刷新
  • 尺寸变化(浏览器窗口变化、字体小、元素大小位置、内容变化、DOM增删、激活CSS伪类)

3.4 事件高级

事件对象

这个对象有事件触发时的相关信息,比如鼠标点击对象,存储了鼠标点在哪个位置等信息,

如何获取:事件对象在回调函数里

 元素.addEventListener('click', function (e) {
     console.log(e)
 })

常用属性

补充:pageX和pageY 跟文档坐标有关系

 <body>
     <button>点击</button>
     <script>
         let btn = document.querySelector('button')
         btn.addEventListener('click', function(e) {
             console.log(e);//e是一个鼠标事件对象
         })
     </script>
 </body>

例子:一张图片一直跟着鼠标移动

mousemove事件

不断把鼠标在页面中的坐标位置给图片lefttop

例子:按下回车,自动发布

事件流

事件流与两个阶段

事件流定义:事件完整执行过程中的流动路径

两个阶段:捕获、冒泡;往下查找,找到后往上返回

image-20221124104612511

事件捕获和事件冒泡
  • 事件冒泡

例子:向上调用同名事件

  • 事件捕获
阻止事件流动(传播)

组织冒泡/捕获,需求:不想自动触发父元素的事件

事件对象.stopPropagation()

 son.addEventListener('click', function(e) {
     alert('我是儿子')
     e.stopPropagation() //加这一句,只会弹“我是儿子”
 })

鼠标经过事件,对比mouseovermouseentermouseover鼠标经过则触发,经过 是有冒泡效果的;mouseenter不会往上冒泡,从子到父,就不会触发(推荐使用)

  • 阻止默认行为,比如链接点击不跳转,表单域不提交

e.preventDefault()

 <a href="www.baidu.com">跳转到百度</a>
 <script>
     let a = document.querySelector('a')
     a.addEventListener('click', function(e) {
         e.preventDefault() //加了这一句后,就不跳转了
     })
 </script>
  • 两种注册事件的区别

    • L0:传统on注册
    • L2:事件监听注册
 <body>
     <button>点击</button>
     <script>
         let btn = document.querySelector('button')
         // 1.l0 on
         // 多次相同的事件,只执行最后一次
          btn.onclick = function () {
              alert('第一次')
          }
          btn.onclick = function () {
              alert('第二次')
          } // 覆盖,只弹第一次
         // 解绑事件
         btn.onclick = null //不会有弹出
         
 ​
 <body>
     <button>点击</button>
     <script>
         let btn = document.querySelector('button')
         // 2. addEventListener 会弹两次
         btn.addEventListener('click', function() {
             alert('第一次')
         }
         btn.addEventListener('click', function () {
             alert('第二次')
         })
     </script>
 </body>
 <body>
     <button>点击</button>
     <script>
         let btn = document.querySelector('button')
         // 2. addEventListener 会弹两次
         btn.addEventListener('click', add)
         function add() {
             alert('第一次')
         }
         btn.removeEventListener('click', add) //移除了,不弹了,无法对匿名函数解绑,因此不可以写出btn.addEventListener('click', funciton() {...})
     </script>
 </body>
事件委托(不是很懂 )

为什么要事件委托

  • 减少事件注册
  • 新增后代元素的事件绑定,不用每次新增再绑定事件

综合案例——渲染学生信息

与节点操作(微博)不同,数据驱动视图(Vue/React)

传统的jquery开发是通过操作dom来实现页面的渲染和交互,而React采用的是数据驱动视图的方式:view是基于数据来渲染的,数据一旦变化,view就会自动更新。因此我们在开发时,只需要关注数据即可,不用直接操作dom。

在原生DOM里性能低

  • 渲染已有数据

  • 点击录入:往数组arr里追加 push()方法,重新渲染

  • 删除:事件委托,委托给父亲tbody,那就只用给一个元素注册事件。删除也是数组里面的数据,然后重新渲染

    只能点击元素a,才会执行删除操作e.target返回点击的标签,判断标签名是否为a e.target.tagName === a

    删除操作:删除数组里的数据,arr.splice(从哪里开始删,删多少个) <a>添加属性,把索引号记下来<a href="" id="",获取ide.target.id

     splice(index, number); 
     index:表示从第几个元素开始;
     number: 表示从此元素开始,向后删除几个元素
    

    打印对象console.dir(e.target)