20230520----重返学习-React.Component与React.PureComponent与this.setState异步性-事件进阶

day-074-seventy-four-20230520-React.Component与React.PureComponent与this.setState异步性-事件进阶

React

浅比较两个值

  • 浅比较两个值
import _ from "@/assets/utils";//这个是之前写的工具函数类,里面的isObject就是下方的isObject函数。
// 检测是否为对象
const isObject = function isObject(obj) {
    return obj !== null && typeReg.test(typeof obj)
}
// 两个值的浅比较。
const shallowEqual = function shallowEqual(objA, objB) {
  // 两个是否都相等。首先判断两个值是否相等,如果直接就相等了,则没有必要比较。如果传递的是两个对象,并且相等,则说明两个对象共用相同的堆内存地址。
  if (Object.is(objA, objB)) {
    return true;
  }

  //   是否是都是对象。如果objA与objB中有任意一个不是对象,则没有办法进行后续的比较,直接返回不相等即可。
  if (!isObject(objA) || !isObject(objB)) {
    return false;
  }

  //   如果objA与objB的成员数量都不一致,则肯定不相等。
  let objAKeys = Reflect.ownKeys(objA);
  let objBKeys = Reflect.ownKeys(objB);
  if (objAKeys.length !== objBKeys.length) {
    return false;
  }

  //  用其中一个对象key遍历另一个对象key对比。
  for (let i = 0; i < objAKeys.length; i++) {
    let key = objAKeys[i];
    // objA中有的成员,但是在objB中没有,说明objA与objB是不相等的。
    // undefined取值有问题。{a:undefined}与{b:undefined}。
    if (!objB.hasOwnProperty(key)) {
      return false;
    }

    // objA与objB都有这个成员,但是成员值不同,则说明objA和objB也是不相等的。
    if ( !Object.is(objA[key], objB[key])) {
      return false;
    }
  }

  //   如果全部比较完,没有发现不一样的地方,则objA与objB是相等的。
  return true;
};

React.Component与React.PureComponent

  • React.Component父类

    • 如果继承的是React.Component父类。

      export default class Demo extends React.Component {
        state = {
          arr: [10, 20, 30],
        };
      
        handler = () => {
          let { arr } = this.state;
          arr.push(arr[arr.length - 1] + 10);
          this.setState({}); //依旧会更新视图,因为this.state.arr虽然数组地址没改变,但内部的值已经被改变了。而React.Component组件默认只要调用this.setState({}),都会执行this.render(),进而生成虚拟DOM进行对比,而新旧虚拟DOM不一致,就必然会更新视图。
      
          //   this.setState({ arr: [...arr] });//更新视图,因为新旧状态是的arr属性地址是不一样的。
        };
        render() {
          console.log("render"); //React.Component组件默认只要调用this.setState({}),都会执行this.render(),进而打印。
          let { arr } = this.state;
          return (
            <div className="demo">
              {arr.map((item, index) => {
                return <Tag key={index}>{item}</Tag>;
              })}
              <Button type="primary" size="small" onClick={this.handler}>
                新增
              </Button>
            </div>
          );
        }
      }
      
      • 在没有设置shouldCompoentUpdate()钩子的情况下,默认都是允许更新的。

        export default class Demo extends React.Component {
          state = {
            arr: [10, 20, 30],
          };
          handler = () => {
            let { arr } = this.state;
            arr.push(arr[arr.length - 1] + 10);
            this.setState({});
          };
          shouldComponentUpdate(nextProps, nextState) {
            return true;
          }
          render() {
            console.log("render");
            let { arr } = this.state;
            return (
              <div className="demo">
                {arr.map((item, index) => {
                  return <Tag key={index}>{item}</Tag>;
                })}
                <Button type="primary" size="small" onClick={this.handler}>
                  新增
                </Button>
              </div>
            );
          }
        }
        
        • 这样只要执行this.setState({}),即便没有修改任何的状态,视图也会更新一次。
          • 重新走一遍更新的逻辑,生成新的VirtualDOM虚拟DOM,只不过如果状态和之前一样,经过DOM-diff对比,没有渲染任何东西而已。
        • 这样明显是不好的,很浪费性能,不管数据是否一样,都生成虚拟DOM进行对比。
          • 所以我们最好自己设置shouldCompoentUpdate,对新老属性与新老状态进行对比,如果发现一模一样,则不允许继续更新。

          • 这个就是优化处理,也就是说那怕进行了this.setState({}),还要对比属性与状态各自新旧对比。

            export default class Demo extends React.Component {
              shouldComponentUpdate(nextProps, nextState) {
                let { props, state } = this;
                // 如果新老属性和状态,经过浅比较之后,发现一模一样,则直接返回false,停止继续更新。
                if(shallowEqual(props,nextProps)&&shallowEqual(state,nextState)){
                    return false
                }
            
                // 但凡有不一样的地方,则允许更新。
                return true
              }
            }
            
            import _ from "@/assets/utils";//这个是之前写的工具函数类。
            // 检测是否为对象
            const isObject = function isObject(obj) {
                return obj !== null && typeReg.test(typeof obj)
            }
            // 两个对象的浅比较。
            const shallowEqual = function shallowEqual(objA, objB) {
              // 两个是否都相等。首先判断两个值是否相等,如果直接就相等了,则没有必要比较。如果传递的是两个对象,并且相等,则说明两个对象共用相同的堆内存地址。
              if (Object.is(objA, objB)) {
                return true;
              }
            
              //   是否是都是对象。如果objA与objB中有任意一个不是对象,则没有办法进行后续的比较,直接返回不相等即可。
              if (!isObject(objA) || !isObject(objB)) {
                return false;
              }
            
              //   如果objA与objB的成员数量都不一致,则肯定不相等。
              let objAKeys = Reflect.ownKeys(objA);
              let objBKeys = Reflect.ownKeys(objB);
              if (objAKeys.length !== objBKeys.length) {
                return false;
              }
            
              //  用其中一个对象key遍历另一个对象key对比。
              for (let i = 0; i < objAKeys.length; i++) {
                let key = objAKeys[i];
                // objA中有的成员,但是在objB中没有,说明objA与objB是不相等的。
                // undefined取值有问题。{a:undefined}与{b:undefined}。
                if (!objB.hasOwnProperty(key)) {
                  return false;
                }
            
                // objA与objB都有这个成员,但是成员值不同,则说明objA和objB也是不相等的。
                if (!Object.is(objA[key], objB[key])) {
                  return false;
                }
              }
            
              //   如果全部比较完,没有发现不一样的地方,则objA与objB是相等的。
              return true;
            };
            
            export default class Demo extends React.Component {
              state = {
                arr: [10, 20, 30],
              };
            
              handler = () => {
                let { arr } = this.state;
                arr.push(arr[arr.length - 1] + 10);
                // console.log(arr);
                //this.setState({ arr: [...arr] });//更新视图,因为新旧状态是的arr属性的地址是不一样的。
                this.setState({ arr }); //不更新视图,因为新旧状态是的arr属性的地址是一样,在shouldComponentUpdate()钩子函数那会返回false,进而不更新视图。
                this.setState({}); //不更新视图,因为新旧状态是的arr属性在浅对比时是一样,在shouldComponentUpdate()钩子函数那会返回false,进而不更新视图。
                // this.forceUpdate() //强制更新视图,因为this.forceUpdate()跳过shouldComponentUpdate()钩子函数,而this.state.arr虽然数组地址没改变,但内部的值已经被改变了。执行this.render()时,进而生成虚拟DOM进行对比,而新旧虚拟DOM不一致,就必然会更新视图。
              };
              render() {
                console.log("render");
                let { arr } = this.state;
                return (
                  <div className="demo">
                    {arr.map((item, index) => {
                      return <Tag key={index}>{item}</Tag>;
                    })}
                    <Button type="primary" size="small" onClick={this.handler}>
                      新增
                    </Button>
                  </div>
                );
              }
              shouldComponentUpdate(nextProps, nextState) {
                let { props, state } = this;
                // 如果新老属性和状态,经过浅比较之后,发现一模一样,则直接返回false,停止继续更新。
                if(shallowEqual(props,nextProps)&&shallowEqual(state,nextState)){
                    return false
                }
            
                // 但凡有不一样的地方,则允许更新。
                return true
              }
            }
            
            • shallowEqual函数就是上方的浅比较函数。

            • 这个优化处理完,会存在一个小的问题:

              handler = () => {
                let { arr } = this.state;
                arr.push(arr[arr.length - 1] + 10);
                this.setState({ arr }); //不更新视图,因为新旧状态是的arr属性的地址是一样,在shouldComponentUpdate()钩子函数那会返回false,进而不更新视图。
              };
              
              • 如果this.state中的某个状态值是一个对象,我们基于这个对象直接修改了状态值,最后基于this.setState()处理的时候,新修改的状态之前的状态还是相同的堆内存地址,这样在经过shouldComponentUpdate()校验的时候,会认为两次状态是一致的,不予以更新。
                • 例如:

                  let { arr } = this.state;//获取原有状态值 地址0x002.
                  arr.push(arr[arr.length - 1] + 10);//基于对象直接修改了状态值。
                  this.setState({
                    arr//新修改的状态值,依然是之前的地址0x002。
                  });
                  
                • 如何解决这个问题?

                  • 因为我们用的是浅比较,所以只要把状态值改为一个新的堆内存地址,这样在shouldComponentUpdate()检测的时候,就是认为两次的状态是不同的,允许进一步更新!

                    this.setState({
                      arr:[...arr]
                    })
                    
                    import _ from "@/assets/utils";//这个是之前写的工具函数类。
                    // 检测是否为对象
                    const isObject = function isObject(obj) {
                        return obj !== null && typeReg.test(typeof obj)
                    }
                    // 两个对象的浅比较
                    const shallowEqual = function shallowEqual(objA, objB) {
                      // 首先判断两个值是否相等,如果直接就相等了,则没有必要比较「如果传递的是两个对象,并且相等,则说明链各个对象共用相同的堆内存地址」
                      if (Object.is(objA, objB)) return true;
                      // 如果A/B中有任意一个不是对象,则没有办法进行后续的比较,直接返回不相等即可
                      if (!isObject(objA) || !isObject(objB)) return false;
                      let objAKeys = Reflect.ownKeys(objA),
                        objBKeys = Reflect.ownKeys(objB);
                      // A/B对象的成员数量都不一致,则肯定不相等
                      if (objAKeys.length !== objBKeys.length) return false;
                      for (let i = 0; i < objAKeys.length; i++) {
                        let key = objAKeys[i];
                        // A中有的成员,但是在B中没有,说明A和B是不相等的
                        if (!objB.hasOwnProperty(key)) return false;
                        // A/B都有这个成员,但是成员值不同,则说明A和B也是不相等的
                        if (!Object.is(objA[key], objB[key])) return false;
                      }
                      // 如果全部比较完,没有发现不一样的地方,则A和B是相等的
                      return true;
                    };
                    export default class Demo extends React.Component {
                      state = {
                        arr: [10, 20, 30],
                      };
                      handler = () => {
                        let { arr } = this.state;
                        arr.push(arr[arr.length - 1] + 10);
                        this.setState({
                          arr: [...arr],
                        });
                      };
                      shouldComponentUpdate(nextProps, nextState) {
                        let { props, state } = this;
                        // 如果新老属性和状态,经过浅比较之后,发现一模一样,则直接返回false,停止继续更新
                        if (shallowEqual(props, nextProps) && shallowEqual(state, nextState))
                          return false;
                        // 但凡有不一样的地方,则允许更新
                        return true;
                      }
                      render() {
                        console.log("render");
                        let { arr } = this.state;
                        return (
                          <div className="demo">
                            {arr.map((item, index) => {
                              return <Tag key={index}>{item}</Tag>;
                            })}
                            <Button type="primary" size="small" onClick={this.handler}>
                              新增
                            </Button>
                          </div>
                        );
                      }
                    }
                    
                  • 还可以基于this.forceUpdate()强制更新,因为执行这个方法,会跳过shouldComponentUpdate()检测,忽略之前所做的优化项。

  • React.PureComponent父类

    • 如果继承的是React.PureComponent父类,它默认给每个类设置一个shouldComponentUpdate()钩子函数,并且对新老属性props/新老状态state进行了浅比较…

      export default class Demo extends React.PureComponent {
        state = {
          arr: [10, 20, 30],
        };
        handler = () => {
          let { arr } = this.state;
          arr.push(arr[arr.length - 1] + 10);
          this.setState({
            arr,//地址一样,发现视图并不会更新。
          });
        };
        render() {
          let { arr } = this.state;
          return (
            <div className="demo">
              {arr.map((item, index) => {
                return <Tag key={index}>{item}</Tag>;
              })}
              <Button type="primary" size="small" onClick={this.handler}>
                新增
              </Button>
            </div>
          );
        }
      }
      
      export default class Demo extends React.PureComponent {
        state = {
          arr: [10, 20, 30],
        };
        handler = () => {
          let { arr } = this.state;
          arr.push(arr[arr.length - 1] + 10);
          this.setState({
            arr: [...arr],//得手动浅拷贝一下arr对象,也可以正常更新了。
          });
        };
        render() {
          let { arr } = this.state;
          return (
            <div className="demo">
              {arr.map((item, index) => {
                return <Tag key={index}>{item}</Tag>;
              })}
              <Button type="primary" size="small" onClick={this.handler}>
                新增
              </Button>
            </div>
          );
        }
      }
      
      • 类似于对React.Component类组件,加了一个上方的浅比较优化项shouldComponentUpdate()
    • 如果非要在React.PureComponent类组件自己手动加一个shouldComponentUpdate()钩子函数,则自己写的会覆盖React.PureComponent默认加的,只不过控制台会出现红色的警告错误。

      • 不推荐使用React.PureComponent类组件:
        • 因为真正开发时,没多少人会在状态不改变时使用this.setState()
        • 使用必定更新时,使用this.forceUpdate(),反而导致太暴力更新。

this.setState异步性

  • 查看this.setState是同步还是异步。

    • 看render()执行多少次。

      import React from "react";
      import { Button } from "antd";
      
      export default class Demo extends React.Component {
        state = {
          x: 10,
          y: 20,
          z: 30,
        };
      
        handler=()=>{
          // console.log(this);
      
          this.setState({x:100})
          this.setState({y:200})
          this.setState({z:300})
        }
      
        render(){
          console.log(`render()`);
          let {x,y,z}=this.state
          return <div className="box">
            <p>{x} - {y} - {x}</p>
            <Button type="primary" size="small" onClick={this.handler}>按钮</Button>
            
          </div>
        }
      }
      
    • 在执行this.setState修改原始值属性后打印出该原始值属性。

      import React from "react";
      import { Button } from "antd";
      
      export default class Demo extends React.Component {
        state = {
          x: 10,
          y: 20,
          z: 30,
        };
      
        handler=()=>{
          // console.log(this);
      
          this.setState({x:100})
          console.log(this.state.x);
          this.setState({y:200})
          console.log(this.state.y);
          this.setState({z:300})
          console.log(this.state.z);
        }
      
        render(){
          console.log(`render()`);
          let {x,y,z}=this.state
          return <div className="box">
            <p>{x} - {y} - {x}</p>
            <Button type="primary" size="small" onClick={this.handler}>按钮</Button>
            
          </div>
        }
      }
      
    • 在React18框架中,this.setState()this.forceUpdate()操作都是异步的。

      export default class Demo extends React.Component {
        state = {
          x: 10,
          y: 20,
          z: 30,
        };
      
        handler = () => {
          this.setState({ x: 100 });
          console.log(this.state.x); //10
          this.setState({ y: 200 });
          console.log(this.state.y); //20
          this.setState({ z: 300 });
          console.log(this.state.z); //30
        };
      
        render() {
          console.log("render");
          let { x, y, z } = this.state;
          return (
            <div className="box">
              <p>
                {" "}
                {x} - {y} - {z}{" "}
              </p>
              <Button type="primary" size="small" onClick={this.handler}>
                按钮
              </Button>
            </div>
          );
        }
      }
      
      export default class Demo extends React.Component {
        state = {
          x: 10,
          y: 20,
          z: 30,
        };
      
        handler = () => {
          setTimeout(() => {
            this.setState({ x: 100 });
            console.log(this.state.x); //10
            this.setState({ y: 200 });
            console.log(this.state.y); //20
            this.setState({ z: 300 });
            console.log(this.state.z); //30
          }, 1000);
        };
      
        render() {
          console.log("render");
          let { x, y, z } = this.state;
          return (
            <div className="box">
              <p>
                {" "}
                {x} - {y} - {z}{" "}
              </p>
              <Button type="primary" size="small" onClick={this.handler}>
                按钮
              </Button>
            </div>
          );
        }
      }
      
      • this.updater这个对象是一个更新队列。
        1. 当遇到this.setState()this.forceUpdate(),会把修改状态以及让视图更新(包含callback回调函数)的操作,放在updater队列中。并且设置一个异步任务,等待同步代码执行完毕,通知updater队列中的方法执行。

          this.updater.enqueueSetState(this, partialState, callback, 'setState')
          this.updater.enqueueForceUpdate(this, callback, 'forceUpdate')
          
        2. 这样设计的目的是?

          • 如果设计为同步操作:
            • 好处: 可以在this.setState()this.forceUpdate()之后,立即获取到最新修改的状态值。
            • 坏处:
              • 连续出现多次修改状态的操作,视图也会连续更新多次,浪费性能。
              • 每一次执行this.setState(),会立即跳转到更新的一系列操作中如生命周期函数中,等待外部的这些操作执行完毕,再跳转回此同步函数中,继续向下执行其它的代码,逻辑混乱,不方便维护和管理。
            • 所以设计为异步操作,主要就是想复用统一处理批处理的机制,优化视图更新所消耗的性能,并且让代码执行的顺序更加清晰且便于管理!
              • 但是这样,无法在执行this.setState()之后,立即获取最新的状态值。
                • 但我们可以在更新完毕,触发执行callback函数,在callback函数内部中获取最新的状态

                  this.setState(
                      { x: 100 }, 
                      () => {
                          console.log(this.state.x) //100「第二个」
                      }
                  )
                  console.log(this.state.x) //10「第一个」
                  
    • this.setState()this.forceUpdate()操作是异步操作的好处与坏处

      • 坏处
        • 不能后续就直接拿到新的状态值。
      • 好处:
        • 性能高。
        • 好管理代码,不会在该同步函数中被迫跳去其它函数,比如执行其它生命周期,再跳回来执行下一行同步代码。
        • 可以做批量更新,同步任务执行完之后,可以对一群异步任务做处理。
    • 在React16框架中,this.setState()this.forceUpdate()操作有时候是异步的,有时候是同步的。

      • 合成事件绑定/钩子函数等地方,this.setState()操作都是异步的!处理机制和React18版本类似。

        • <button onClick={()=>{ this.setState({})//异步; }}> 合成事件。

          export default class Demo extends React.Component {
            state = {
              x: 10,
              y: 20,
              z: 30,
            };
          
            handler = () => {
              this.setState({ x: 100 });
              console.log(this.state.x); //10
              this.setState({ y: 200 });
              console.log(this.state.y); //20
              this.setState({ z: 300 });
              console.log(this.state.z); //30
            };
          
            render() {
              console.log("render");
              let { x, y, z } = this.state;
              return (
                <div className="box">
                  <p>
                    {" "}
                    {x} - {y} - {z}{" "}
                  </p>
                  <Button type="primary" size="small" onClick={this.handler}>
                    按钮
                  </Button>
                </div>
              );
            }
          }
          
      • 但是在其它的异步操作中,this.setState()操作被设计为同步的!

        export default class Demo extends React.Component {
          state = {
            x: 10,
            y: 20,
            z: 30,
          };
        
          handler = () => {
            
            setTimeout(() => {
              this.setState({ x: 100 });
              console.log(this.state.x); //100
              this.setState({ y: 200 });
              console.log(this.state.y); //200
              this.setState({ z: 300 });
              console.log(this.state.z); //300
            }, 1000);
          };
        
          render() {
            console.log("render");
            let { x, y, z } = this.state;
            return (
              <div className="box">
                <p>
                  {" "}
                  {x} - {y} - {z}{" "}
                </p>
                <Button type="primary" size="small" onClick={this.handler}>
                  按钮
                </Button>
              </div>
            );
          }
        }
        
        • 例如:
          • 定时器的回调函数中。
          • 基于addEventListener()手动做的事件绑定等操作等。
  • 基于异步的this.setState()/this.forceUpdate(),实现出类似于同步的效果:

    import React from "react";
    import { Button } from "antd";
    
    export default class Demo extends React.Component {
      state = {
        x: 10,
        y: 20,
        z: 30,
      };
    
      handler = () => {
        // 
        for (let i = 0; i < 10; i++) {
          // 在循环中,我们只是把修改this.state中的x属性的任务放在updater队列中。但是状态并没有改,所以每一轮循环,我们获取的this.state.x都是0;不过this.setState执行10次,回调函数也回调10次,但render()只执行一次。
          this.setState({ x: this.state.x + 1 });
        }
      };
    
      render() {
        console.log(`render()`);
        let { x } = this.state;
        return (
          <div className="box">
            <p>
              {x}
            </p>
            <Button type="primary" size="small" onClick={this.handler}>
              按钮
            </Button>
          </div>
        );
      }
    }
    
  • this.setState()中传一个函数。

    • this.setState()的第一个参数可以是一个对象,也可以是一个函数对象。
      • 如果是个对象:把x状态值改为1,异步。
      • 如果是个函数:则把函数加入到updater队列中,当后期通知队列中的任务执行时,会依次把存放的函数执行,把当前处理好的状态传递给prev,接收函数的返回值,作为要修改的状态信息。
        • 个人建议,对于this.state的数据直接操作的方式都放在this.state(传递的函数)里的第一个入参函数中。
  • updater()队列分批次处理

    1. flushSync(()=>{… })
      • flushSync是用来刷新updater队列的。所谓刷新队列,就是让队列中的任务立即更新一次。
      • 代码执行中,如果遇到flushSync,则把之前加入updater队列中的,以及flushSync回调函数中,加入updater队列中的,统一更新一次!更新完毕后,才会继续向下执行!
        • 真实项目中,我们习惯性的把:所有需要立即刷新一次的任务,都放在flushSync的回调函数中。
  • 想基于异步的this.setState()this.forceUpdate,实现出类似于同步的效果。

    • 或者说,想拿到之前基于setState修改的状态值,在这些值的基础上,继续做一些事情,我们如何处理?
      1. 基于flushSync刷新updater队列。
        • 本质上更新多次。
        • 好处是可以控制更新几次。
      2. 基于setState第一个参数设置为函数的方式,这样能够在统一刷新的时候,可以在函数中,获取之前已经处理好的状态信息。
        • 本质上更新一次。
        • 不用引入flushSync。
      3. 基于定时器等其它异步操作,把setState等操作,分隔成多次处理。
        • 本质上更新多次。
      4. 直接基于this.state.xxx=xxx更新状态。
        • 状态的更新是同步的,只不过视图不会更新。让this.setState与forceUpdate只充当为通知视图更新的工具!
      5. 基于setState的第二个参数来调用,想要更新的东西。
      6. 在React16中,更狠的方式就是:把所有的setState都放在定时器中,那么所有的setState都变为同步操作了。
        • 不过React18中不能用。
        • 性能也不太好,更新的次数太多了。

事件进阶

合成事件绑定

  • 在React中用onClick来绑定的事件,其实是合成事件绑定。

    <div onClick={()=>{console.log(111)}}>新增</div>
    
  • 什么是事件?

    • 事件是浏览器赋予DOM元素的默认行为。即便什么都不处理,元素该具备的事件都有!
      • 即便什么都不绑定函数,事件也在那。
      • 就是当鼠标或键盘在元素上触发时,对应的鼠标事件就已经被触发了。
        • 跟写与不写事件处理函数、绑定不绑定事件处理函数没有关系。
      • 浏览器标准事件
  • 什么是事件绑定?

    • 给元素的事件行为绑定方法,当事件行为触发,会执行对应的方法,完成指定的需求。
    • 给元素绑定事件有两种方式:
      • DOM0级事件绑定:

        let body = document.body
        body.onclick = function(){ ... }
        
        • 原理:给DOM元素对象的onxxx事件私有属性赋值。
          • 只能给元素的对应事件行为绑定一个方法。
          • 要绑定事件处理函数的DOM元素必须拥有对应事件私有属性才可以,某些标准事件是没有对应的事件私有属性的。
            • 比如:
              • DOMContentLoaded
      • DOM2级事件绑定:

        let body = document.body;
        body.addEventListener("click",function(){ ... },布尔值/对象)
        //DOM元素对象.addEventListener(事件名,事件处理函数,布尔值/对象)
        
        • 参数:
          • 第一个参数:类型类型,表示监听事件类型的大小写敏感的字符串。
          • 第二个参数:事件监听回调函数,事件触发后要执行的函数。
          • 最后一个参数:可选参数对象,可以是布尔值或对象。
            • 最后参数是布尔类型值,是控制触发的阶段。
              • true:捕获。
              • false:冒泡。
            • 最后参数是对象,则是设置相关的配置项。
              • capture 控制触发阶段。
              • once 只能触发一次,触发完毕后,会自动基于removeEventListener()移除事件绑定。
              • passive 设置为true之后,则禁止阻止默认行为
        • 原理:利用浏览器内置的事件池机制,完成事件绑定及触发管理的。
          • 只要是浏览器支持的标准事件,都可以用addEventListener()做事件绑定。
            • 比DOM0级的功能强大。
          • 可以给同一个元素的同一个事件类型,绑定多个不同的方法。
            • 当事件触发的时候,所有绑定的方法会依次被触发执行。
        • EventTarget.addEventListener()
          • DOM元素对象.addEventListener()是对于DOM元素对象来说,基于原型链上的查找,会比DOM0级事件属性要来得慢一点。
  • 浏览器的标准事件:

  • 什么是事件对象?

    • 所谓事件对象,就是一个普通对象,只不过记录了本次触发事件的相关信息。

      • 当事件触发,绑定的方法执行,会把分析好的事件对象作为实参传递给每个绑定了该事件的执行函数

        let body = document.body
        body.addEventListener("click",function(event){ 
          // event:获取的事件对象信息。这个名称可以随意起,但就是它的第一个形参。
        })
        
        • 事件对象在事件函数执行时,是事件函数的第一个形参。
          • 事件函数的第一个形参名称可以随意起,但第一个形参就代表事件对象。
    • 常用的事件对象:

      • MouseEvent(PointerEvent/TouchEvent):鼠标事件对象。

        • MouseEvent表示鼠标事件对象。

          document.body.addEventListener("dblclick",function(event){ 
            // event:获取的事件对象信息。这个名称可以随意起,但就是它的第一个形参。
            console.log(event)//MouseEvent
          })
          
          • 对于明确知道是由鼠标引起的事件,就是MouseEvent事件对象。
        • PointerEvent表示点击事件对象,对于不明确知道是那些事件的,一般就是这个了。

          document.body.addEventListener("click",function(event){ 
            // event:获取的事件对象信息。这个名称可以随意起,但就是它的第一个形参。
            console.log(event)//PointerEvent
          })
          
          • 对于不明确知道是由手指触摸还是鼠标引起的事件,就是PointerEvent事件对象。
        • TouchEvent表示触摸事件。

          document.body.addEventListener("touchstart",function(event){ 
            // event:获取的事件对象信息。这个名称可以随意起,但就是它的第一个形参。
            console.log(event)//TouchEvent
          })
          
          • 对于明确知道是由手指触摸引起的事件,就是TouchEvent事件对象。
      • KeyboardEvent键盘事件对象。

        document.body.addEventListener("keyup",function(event){ 
          // event:获取的事件对象信息。这个名称可以随意起,但就是它的第一个形参。
          console.log(event)//KeyboardEvent
        })
        
      • Event

        • 普通的事件对象
    • 事件对象中常用的信息:

      • 鼠标事件:
        • altKey 是否已经按下了alt键。
        • ctrlKey 是否已经按下了ctrl键。
        • shiftKey 是否已经按下了shift键。
        • clientX/clientY 操作点距离可视窗口的坐标。
        • pageX/pageY 操作点距离BODY的坐标。
        • srcElement/target 事件源。
          • 事件触发的源头是谁,该属性就是谁。
          • 与是谁绑定的事件没有关系。
        • type 事件类型。
      • 键盘事件:
        • keyCode/which 键盘的按键码。
      • 常用事件:
        • preventDefault() 阻止浏览器默认行为。

          • 如右键点击在浏览器上就会出现菜单。
        • stopPropagation()/stopImmediatePropagation() 阻止冒泡传播。

        • composedPath() 获取传播的路径。

          document.body.οnclick=(event)=>{
            console.log(event.composedPath())//[p, div.box, div#root, body, html, document, Window]
          }
          
          • 事件源 -> ... -> Window
  • 事件的传播机制:

    • 对于支持事件传播的事件行为来讲,当事件行为触发的时候,会经历三个阶段:

      `CAPTURING_PHASE: 1` 捕获阶段「从window开始查找,一直找到事件源,其目的是为冒泡阶段分析好传播路径」
      `AT_TARGET: 2` 目标阶段「把事件源的相关事件行为触发」
      `BUBBLING_PHASE: 3` 冒泡阶段「并让其祖先元素的相关事件行为也被触发,而且是从事件源 -> Window」
      
      1. 捕获阶段CAPTURING_PHASE:从window开始查找,一直查找到事件源。
        • 其目的是为冒泡阶段分析好传播路径。
        • 不过也可以在一些DOM元素是提前做事件处理。
      2. 目标阶段AT_TARGET:把事件源的相关事件行为触发。
      3. 冒泡阶段BUBBLING_PHASE:让事件源对应DOM元素对象的祖先元素的相关事件行为也会被触发,而且是从事件源 --> Window
    • 有一些事件行为是不支持事件传播的。

      • 例如:
        • mouseentermouseleave
        • load
  • 事件委托:

    • 是用事件传播的冒泡阶段来完成的。
  • 拖拽事件:

    • 是用鼠标事件对象以及DOM元素属性来合成的一个效果。

进阶参考

  1. 浏览器的标准事件
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值