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
这个对象是一个更新队列。-
当遇到
this.setState()
与this.forceUpdate()
,会把修改状态以及让视图更新(包含callback回调函数)的操作,放在updater队列中。并且设置一个异步任务,等待同步代码执行完毕,通知updater队列中的方法执行。this.updater.enqueueSetState(this, partialState, callback, 'setState') this.updater.enqueueForceUpdate(this, callback, 'forceUpdate')
-
这样设计的目的是?
- 如果设计为同步操作:
- 好处: 可以在
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(传递的函数)里的第一个入参函数中。
- this.setState()的第一个参数可以是一个对象,也可以是一个函数对象。
-
updater()队列分批次处理
- …
- …
- flushSync(()=>{… })
- flushSync是用来刷新updater队列的。所谓刷新队列,就是让队列中的任务立即更新一次。
- 代码执行中,如果遇到flushSync,则把之前加入updater队列中的,以及flushSync回调函数中,加入updater队列中的,统一更新一次!更新完毕后,才会继续向下执行!
- 真实项目中,我们习惯性的把:所有需要立即刷新一次的任务,都放在flushSync的回调函数中。
-
想基于异步的
this.setState()
与this.forceUpdate
,实现出类似于同步的效果。- 或者说,想拿到之前基于setState修改的状态值,在这些值的基础上,继续做一些事情,我们如何处理?
- 基于flushSync刷新updater队列。
- 本质上更新多次。
- 好处是可以控制更新几次。
- 基于setState第一个参数设置为函数的方式,这样能够在统一刷新的时候,可以在函数中,获取之前已经处理好的状态信息。
- 本质上更新一次。
- 不用引入flushSync。
- 基于定时器等其它异步操作,把setState等操作,分隔成多次处理。
- 本质上更新多次。
- 直接基于this.state.xxx=xxx更新状态。
- 状态的更新是同步的,只不过视图不会更新。让this.setState与forceUpdate只充当为通知视图更新的工具!
- 基于setState的第二个参数来调用,想要更新的东西。
- 在React16中,更狠的方式就是:把所有的setState都放在定时器中,那么所有的setState都变为同步操作了。
- 不过React18中不能用。
- 性能也不太好,更新的次数太多了。
- …
- 基于flushSync刷新updater队列。
- 或者说,想拿到之前基于setState修改的状态值,在这些值的基础上,继续做一些事情,我们如何处理?
事件进阶
合成事件绑定
-
在React中用onClick来绑定的事件,其实是合成事件绑定。
<div onClick={()=>{console.log(111)}}>新增</div>
-
什么是事件?
- 事件是浏览器赋予DOM元素的默认行为。即便什么都不处理,元素该具备的事件都有!
- 即便什么都不绑定函数,事件也在那。
- 就是当鼠标或键盘在元素上触发时,对应的鼠标事件就已经被触发了。
- 跟写与不写事件处理函数、绑定不绑定事件处理函数没有关系。
- 浏览器标准事件
- 事件是浏览器赋予DOM元素的默认行为。即便什么都不处理,元素该具备的事件都有!
-
什么是事件绑定?
- 给元素的事件行为绑定方法,当事件行为触发,会执行对应的方法,完成指定的需求。
- 给元素绑定事件有两种方式:
-
DOM0级事件绑定:
let body = document.body body.onclick = function(){ ... }
- 原理:给DOM元素对象的
onxxx事件私有属性
赋值。- 只能给元素的
对应事件行为
绑定一个方法。 要绑定事件处理函数的DOM元素
必须拥有对应事件私有属性
才可以,某些标准事件是没有对应的事件私有属性的。- 比如:
DOMContentLoaded
- …
- 比如:
- 只能给元素的
- 原理:给DOM元素对象的
-
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」
- 捕获阶段
CAPTURING_PHASE
:从window开始查找,一直查找到事件源。- 其目的是为
冒泡阶段
分析好传播路径。 - 不过也可以在一些DOM元素是提前做事件处理。
- 其目的是为
- 目标阶段
AT_TARGET
:把事件源的相关事件行为触发。 - 冒泡阶段
BUBBLING_PHASE
:让事件源对应DOM元素对象的祖先元素
的相关事件行为也会被触发,而且是从事件源
-->Window
。
- 捕获阶段
-
有一些事件行为是不支持事件传播的。
- 例如:
mouseenter
与mouseleave
load
- …
- 例如:
-
-
事件委托:
- 是用事件传播的冒泡阶段来完成的。
-
拖拽事件:
- 是用鼠标事件对象以及DOM元素属性来合成的一个效果。