1. react 的更新元素

  • React 元素都是immutable不可变的,当元素被创建之后,你是无法改变其内容或属性的
  • 更新界面的唯一办法是创建一个新的元素,然后将它传入ReactDOM.render()方法
  • react 只会更新必要的部分

2. react 状态 更新

**setState()** 你应该了解三件事

  1. 不要直接修改state

构造函数是唯一可以给 this.state 赋值的地方

更新数据并触发渲染 只能使用 setState()方法

  1. state的更新可能是异步的

出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用。

因为 this.propsthis.state 可能会异步更新,所以你不要依赖他们的值来更新下一个状态

要解决这个问题,可以让 setState() 接收一个函数而不是一个对象

在事件处理函数中,setState的调用会被批量执行, setState 并不会修改this.state, 而是在事件处理函数结束后,再进行更新,

如何判断setState是同步还是 批量更新?

react 能管控的地方(事件处理函数, 生命周期函数)就是异步的(批量更新)

不能管控的地方就是同步的(非批量): setTimeout, setInterval, 原生dom 事件

  1. // 同步
  2. setTimeout(()=>{
  3. this.setState({a:10})
  4. })
  1. state的更新会被合并

当你调用 setState() 的时候,React 会把你提供的对象合并到当前的 state

合并是浅合并

  1. 数据是向下流动的

组件可以选择把它的 state 作为 props 向下传递到它的子组件中

实现原理(同步更新)

每个类组件 都有一个 Updater 更新器(类似观察者模式)

  1. class Component {
  2. constructor(props) {
  3. this.props = props;
  4. this.state = {};
  5. // 每个类组件的实例都有一个更新器
  6. this.updater = new Updater(this);
  7. }
  8. // 模拟setState 方法
  9. setState(partialState, callback) {
  10. this.updater.addState(partialState, callback);
  11. }
  12. }
  13. // 更新器
  14. class Updater {
  15. constructor(classInstance) {
  16. this.classInstance = classInstance;
  17. this.pendingState = []; // 保存将要更新的队列
  18. this.callbacks = []; // 保存将要执行的 回调函数
  19. }
  20. addState(partialState, callback) {
  21. this.pendingState.push(partialState);
  22. if (typeof callback === "function") {
  23. this.callbacks.push(callback);
  24. }
  25. // 触发更新逻辑
  26. this.emitUpdate();
  27. }
  28. emitUpdate() {
  29. this.updateComponent();
  30. }
  31. updateComponent() {
  32. const { classInstance, pendingState } = this;
  33. if (pendingState.length > 0) {
  34. shouldUpdate(classInstance, this.getState());
  35. }
  36. }
  37. // 拿到状态
  38. getState() {
  39. const { classInstance, pendingState } = this;
  40. let { state } = classInstance;
  41. pendingState.forEach((nextState) => {
  42. if (typeof nextState === "function") {
  43. nextState = nextState(state);
  44. }
  45. state = { ...state, ...nextState };
  46. });
  47. // 清空 存储的 状态函数
  48. this.pendingState = [];
  49. return state;
  50. }
  51. }
  52. function shouldUpdate(classInstance, nextState) {
  53. classInstance.state = nextState; // 真正修改状态
  54. // 调用组件的 更新
  55. classInstance.updateComponent();
  56. }

3. Hook 规则

useEffect , 每次执行,都会产生一个新的闭包

  • 只在最顶层使用 Hook
  • 只在 React 函数组件中调用 Hook
  • 不要在循环,条件或嵌套函数,普通的 js 函数中调用 Hook

4. 为什么不能在条件和循环里使用Hooks

确保 Hook 在每一次渲染中都按照同样的顺序被调用,能够在多次的 useStateuseEffect 调用之间保持 hook 状态的正确

5. useEffect 和 useLayoutEffect不同?

它俩不一样,

可以认为useLayoutEffect 是微任务, 会在渲染前执行

useEffect 是宏任务, 会在渲染后执行(下一个事件环执行)

useLayoutEffect 比 useEffect 早执行逻辑

6. useRef 和 createRef

useRef 是一个hooks, 只能用在函数组件中, 可以多次渲染的保持不变,即使用的都是同一个对象

createRef 是一个普通方法, 只能用在类组件中, 每次调用都会返回一个新的对象

7. 生命周期钩子函数

react老秦 - 图1

8. 函数

函数运行会产生 执行上下文, 它是一个对象, 称为 VO, 里边放置的有,变量 以及参数

作用域 就是 一个个的 上下文对象

作用域链 是在 函数创建时 就 确定了

编译阶段:

  1. 创建 VO
  1. 1. 处理参数,放入vo的属性上
  2. 2. 扫描所有代码,找出function声明,从上往下依次执行,有重复时,后边的会覆盖掉前边的
  3. 3. 扫描var 关键字声明的变量, 只声明, 不赋值,也就是undefined,如果function 和变量的命名重复,会忽略掉 var 的命名变量
  4. 4. 编译阶段 不会处理let 声明的变量,也不会放入vo

9. useState 实现原理

  1. let hookState = [];
  2. let hookIndex = 0;
  3. function useState(initState) {
  4. hookState[hookIndex] = hookState[hookIndex] || initState;
  5. let currentIndex = hookIndex;
  6. function setState(param) {
  7. hookState[currentIndex] = param;
  8. render();
  9. }
  10. return [hookState[hookIndex++], setState];
  11. }

10 前端请求状态码

413 : (请求实体过大)服务器无法处理请求,因为请求实体过大,超出服务器的处理能力

403: 表示资源不可用。服务器理解客户的请求,但拒绝处理它

405: 不支持请求中指定的方法

400: 前端传入的参数 不匹配 后台接口参数需要的格式

11. Fiber 是什么?

Fiber 是一个执行单元, 也是一种数据机构

浏览器任务调度过程:

react老秦 - 图2

requestIdleCallback 使开发者能够在主事件循环上执行后台 和 低优先级 任务 (即在时间切片外)

react老秦 - 图3

这是一种合作式的调度, 需要程序和浏览器互相信任。 浏览器会分配时间片(requestIdelCallback) 给程序去选择调用, 程序需要按照约定在这个时间内执行完毕,并将控制权交换给浏览器

Fiber 是一个执行单元, 每次执行完一个执行单元,React 就会检查现在还剩下多少时间, 如果没有时间就将控制权交还给浏览器,然后继续进行下一帧的渲染。

Fiber 也是一种数据结构

react老秦 - 图4

Fiber 使用链表 将 虚拟Dom 链接起来,每个节点代表一个fiber

  1. class FiberNode {
  2. constructor(type, payload) {
  3. this.type = type // 节点类型
  4. this.key = payload.key // key
  5. this.payload = payload // 挂载的数据
  6. this.return = null // 父Fiber
  7. this.child = null // 长子Fiber
  8. this.sibling = null // 相邻兄弟Fiber
  9. }
  10. }

通过Fiber 架构,可以让自己的 调度 过程 变的 可被中断, 适时让出 CPU 控制权, 让浏览器及时的响应用户的交互

Fiber 执行阶段

每次渲染有两个阶段:Reconciliation(协调/render) 阶段 和 commit (提交)阶段

  • 协调/render阶段: 可以认为是 DIFF 阶段, 这个阶段可以被中断,在这个阶段会找出所有节点变更,如(节点的增删改)操作,此变更在 react 中被称为 副作用
  • 提交阶段: 将上一个阶段计算出来的需要处理的副作用一次性执行。此阶段不能中断,必须同步一次性执行完。

12. react 渲染流程

  1. 选择高优先级的任务 进入调度
  2. 计算变更的内容
  3. 把变更的内容render 到页面上

13 . setState 实际做了什么?

setState() 会对一个组件的state 对象 安排 一次更新, 当 state 的值改变了,该组件就会重新渲染

14 . state 和 props 之间的区别是什么?

  1. propsstate 都是普通的 js 对象, 都是用来保存信息的,控制组件的渲染输出 和 更新
  2. props 是传递给组件的, 而 state 是在组件内 被组件自己管理的

15. 为什么 setState 给了我一个错误的值?

  1. React 中, `this.state` `this.props` 都代表已经被渲染的值, 即当前屏幕上展示的值

调用 setState 其实是异步的, 不要指望在 调用 setState 之后, this.state 会立即映射为新 的值。

16. 我应该如何更新那些依赖于当前的 state 的 state 呢

  1. 如果需要基于当前的 state 来计算出新的值, 那么应该给 `setState` 传递一个 函数, 而不是一个对象, 就可以确保每次的调用 都是使用的 最新的 state
  1. this.setState((state) => {
  2. // 重要:在更新的时候读取 `state`,而不是 `this.state`。
  3. return {count: state.count + 1}
  4. });

17 . setState 什么时候是异步的(批量更新)?

目前, 在事件 处理函数 内部的 setState 是异步的。(16及以前), (17版本)React 将默认批量更新所有更新

无论setState()您在 React 事件处理程序中对多少个组件进行了多少次调用,它们都只会在事件结束时产生一次重新渲染

  1. react 能管控的地方 就是 批量更新的 模式 (事件处理函数, 钩子函数)
  2. react 不能管控的地方 就是 同步更新 的模式, setTimeOut, setInterval, 原生dom 事件等)

18. 为什么 React 不同步地更新 this.state

在开始重新渲染之前, React 会有意的 进行 等待, 直到 所有在组件的 事件处理 函数 内调用的 setState() 完成之后。 可以通过避免不必要的更新渲染来提高性能。

19. 深入理解 setState()?

  1. setState(updater, [callback])
  • setState() 方法 将 对 组件 state 的更改排入队列, 并通知 React 需要使用 更新后的 state 更新 渲染此组件及其子组件
  • setState() 视为请求 而不是立即更新组件的命令, 为了更好的性能, React 会延迟调用它 ,而是通过一次传递更新多个组件。 所以React 并不会保证 state 的变更 会立即生效。
  • setState() 并不总是立即更新组件。 它会 批量推迟更新。这使得 在调用 setState 后,立即调用 this.state 成为了隐患。 为了解决这个隐患, 请使用 componentDidUpdate 或者 setState 的回调函数setState(updater, callback)`, 这两种方式都可以保证 应用更新后触发
  • 除非 shouldComponentUpdate() 返回 false , 否则 setState 将始终执行重新渲染操作

参数一为带有形式参数的 **updater** 函数:updater 的返回值会与 state 进行浅合并。

  1. this.setState((state, props) => {
  2. return {counter: state.counter + props.step};
  3. }, callback);

setState() 的第二个参数为可选的回调函数, 它将在 setState 完成合并并重新渲染组件后执行。通常,我们建议使用 componentDidUpdate() 来代替此方式。 此钩子函数中不允许 在改变 state 状态。

20. 合成事件?

在 React 中, 想阻止默认行为 , 需要 调用 preventDefault 来阻止。

事件的 参数 e 是一个 合成事件, 即 `SyntheticEvent, 它是 浏览器的原生事件 的跨浏览器的包装器,除兼容所有浏览器外,它还拥有和浏览器原生事件相同的接口,包括stopPropagation()preventDefault()

从 v17 开始,**e.persist()** 将不再生效,因为 **SyntheticEvent** 不再放入事件池

21. 错误边界 问题

错误边界是一种 React 组件,这种组件 可以捕获并打印发生在其子组件树任何位置的 JavaScript 错误,并且,它会渲染出备用 UI, 而不是渲染那些崩溃了的子组件树,

  1. 在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误,
  2. 错误边界仅可以捕获其子组件的错误,它无法捕获其自身的错误
  3. 自 React 16 起,任何未被错误边界捕获的错误将会导致整个 React 组件树被卸载。

错误边界 无法 捕获 以下场景的错误:

  • 事件处理 (在事件处理器内部捕获错误,使用 try / catch 语句:)
  • 异步代码
  • 服务端渲染
  • 它自身跑出来的错误

如果一个 类组件 定义了 static getDerivedStateFromError()componentDidCatch 方法中的一个,那么它就 变成一个错误边界, 当抛出错误后 使用第一个方法 渲染备用UI, 使用 第二个方法去打印错误信息

  1. class ErrorBoundary extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.state = { hasError: false };
  5. }
  6. static getDerivedStateFromError(error) {
  7. // 更新 state 使下一次渲染能够显示降级后的 UI
  8. return { hasError: true };
  9. }
  10. componentDidCatch(error, errorInfo) {
  11. // 你同样可以将错误日志上报给服务器
  12. logErrorToMyService(error, errorInfo);
  13. }
  14. render() {
  15. if (this.state.hasError) {
  16. // 你可以自定义降级后的 UI 并渲染
  17. return <h1>Something went wrong.</h1>;
  18. }
  19. return this.props.children;
  20. }
  21. }

22 . React15的DomDiff 算法

算法原理:

DOM节点跨层级的移动操作特别少, 可以忽略不计

拥有相同类的 两个组件,会生成相似的树形结构,不同的类生成不同的 树形结构

对于同一层级的一组子节点, 它们可以通过唯一的key 进行区分

DIFF 算法在执行时有三个维度, 分别是 Tree DIFF, Component DIFF 和 Element DiFF

1. Tree DIFF

  • 对树的 每一层 进行遍历, 如果某组件不存在了, 则会直接销毁
  • 当出现节点跨级移动时, 并不会出现移动操作,而是以当前为根节点的树进行重建

2. Component DIFF

如果是同一类型的组件, 会向下继续比较子节点 如果类型不同,则直接替换掉

3. Element DIFF

当节点处于同一级时, diff 提供了 三种节点操作, 分别为, insert(插入), move(移动),和remove(删除)

  • insert ,新的 component 类型不在老集合里, 即都是全新的节点, 需要对新节点执行插入操作
  • move: 在老集合有新的 component 类型, 就需要做更新和移动操作,可以复用以前的dom节点
  • remove:老的component 类型不在新的集合里, 需要执行删除操作

24. 虚拟dom 实现

  1. // 文本元素
  2. const TEXT = Symbol.for('TEXT');
  3. // dom 元素
  4. const ELEMENT = Symbol.for('ELEMENT');
  5. /**
  6. * @description 创建虚拟dom 元素
  7. * @param {*} type
  8. * @param {*} config
  9. * @param {...any} children
  10. */
  11. function createElement(type, config = {}, ...children) {
  12. delete config.__self;
  13. delete config.__source;
  14. // 元素类型
  15. let $$typeof = null;
  16. // 解构 key , ref , 其它属性放到 props 里
  17. let { key, ref, ...props } = config;
  18. // 原生元素
  19. if (typeof type === 'string') {
  20. $$typeof = ELEMENT;
  21. }
  22. // 简化代码
  23. props.children = children.map((item) =>
  24. typeof item === 'object'
  25. ? item
  26. : { $$typeof: TEXT, type: TEXT, content: item },
  27. );
  28. return ReactElement($$typeof, type, key, ref, props);
  29. }
  30. // 创建react 元素
  31. function ReactElement($$typeof, type, key, ref, props) {
  32. let element = {
  33. $$typeof,
  34. type,
  35. props,
  36. key,
  37. ref,
  38. };
  39. return element;
  40. }
  41. // 导出 React 对象
  42. const React = {
  43. createElement,
  44. };
  45. export default React;

25. 合成事件实现

  1. /**
  2. * 在 ract 中, 并不是把事件绑定到 dom 节点上, 而是绑定到 document 上,类似于事件委托
  3. * 原因:
  4. * 1. 屏蔽浏览器之间的差异()
  5. * 2. 可以实现事件对象复用,重用, 减少垃圾回收, 提高性能
  6. * 3. 要实现默认的批量更新,比如两次seState 合并成一次更新,也是在合成事件中处理
  7. *
  8. * @description 合成事件
  9. * @param {*} dom 要绑定的dom 节点
  10. * @param {*} eventType 事件的类型 click change hover
  11. * @param {*} listener 事件处理函数
  12. */
  13. export function addEvent(dom, eventType, listener) {
  14. eventType = eventType.toLowerCase(); // onClick => onclick
  15. // 在要绑定的dom节点上挂载一个对象, 准备存放监听函数(有就用你的, 没有就创建个空对象, 第一次的时候肯定是没有的)
  16. let eventStore = dom.eventStore || (dom.eventStore = {});
  17. eventStore[eventType] = listener;
  18. // onclick => eventType.slice(2)之后 => click
  19. // 统一冒泡到 document 上
  20. // 阶段1: 捕获, 阶段2: 冒泡 , false 就是冒泡
  21. document.addEventListener(eventType.slice(2), dispatchEvent, false);
  22. }
  23. /**
  24. * @description 真正事件触发的是这个函数(所有的事件监听函数都会进入dispatchEvent)
  25. * @param {*} event 原生的事件对象,但传给监听函数并不是它, 而是处理过的
  26. */
  27. // 合成事件对象变量
  28. let syntheticEvent = null;
  29. function dispatchEvent(event) {
  30. // type:事件类型 , target: 元素
  31. let { type, target } = event;
  32. let eventType = 'on' + type; // onclick
  33. // 给 syntheticEvent 赋值
  34. syntheticEvent = getSyntheticEvent(event);
  35. // 在给它重新符值一个当前的元素
  36. getSyntheticEvent.currentTarget = target;
  37. // 模拟事件冒泡
  38. while (target) {
  39. let { eventStore } = target;
  40. // 拿到事件监听函数
  41. let listener = eventStore && eventStore[eventType];
  42. // 如果有,就执行监听函数
  43. if (listener) {
  44. listener.call(target, syntheticEvent);
  45. }
  46. // 没有的话,找父级元素
  47. target = target.parentNode;
  48. }
  49. // 监听函数执行完毕, 清空掉所有属性,以供下次复用
  50. // for (const key in syntheticEvent) {
  51. // if (key !== 'persist') syntheticEvent[key] = null;
  52. // }
  53. }
  54. // 持久化事件
  55. function persist() {
  56. syntheticEvent = {
  57. persist,
  58. };
  59. }
  60. /**
  61. * @description 得到合成事件对象
  62. * @param {*} nativeEvent 原生的事件源
  63. */
  64. function getSyntheticEvent(nativeEvent) {
  65. // 如果 syntheticEvent 为空,就创建一个新对象
  66. if (!syntheticEvent) {
  67. syntheticEvent = {
  68. persist,
  69. };
  70. }
  71. // 把原生事件源赋值给它
  72. syntheticEvent.nativeEvent = nativeEvent;
  73. // 当前作用的元素
  74. getSyntheticEvent.currentTarget = nativeEvent.target;
  75. // 拷贝原生事件上的方法及属性到合成事件对象上
  76. for (const key in nativeEvent) {
  77. if (typeof nativeEvent[key] === 'function') {
  78. // 把 this 指针还作用到原生事件源上
  79. syntheticEvent[key] = nativeEvent[key].bind(nativeEvent);
  80. } else {
  81. syntheticEvent[key] = nativeEvent[key];
  82. }
  83. }
  84. return syntheticEvent;
  85. }

26. setState 实现原理

  • react 中进行事件处理函数执行的时候, 会先进去批量更新模式
  • 在执行此函数的时候,可能会引起多个组件的更新,
  • 但是因为当前是处于 批量更新的模式的,
  • 不会立即更新 state, 而是先把状态缓存起来,在事件函数执行完成之后,再全部更新这个脏组件
  • 事件和生命周期函数都会触发批量更新

react老秦 - 图5

  1. // 创建类组件
  2. export class Component {
  3. constructor(props) {
  4. this.props = props;
  5. this.$updater = new Updater(this);
  6. this.state = {}; //当前状态
  7. this.nextProps = null; // 下个属性对象
  8. }
  9. // 批量更新, 状态可能会合并
  10. setState(partialState) {
  11. this.$updater.addState(partialState);
  12. }
  13. }
  14. // 创建任务更新队列
  15. export let updateQueue = {
  16. updaters: [], // 存放将要执行的更新器对象
  17. isPending: false, //批量更新的模式标志| false = 批量更新模式 | true= 非批量更新模式, 也就是立即更新
  18. add(updater) {
  19. this.updaters.push(updater); // 先放到 更新器数组里, 添加更新器,放进去之后就完事,不进行真正的更新
  20. },
  21. // 当 只有调用这个方法时,才会执行真正的更新
  22. batchUpdate() {
  23. // 取出 updaters 数组
  24. let { updaters } = this;
  25. // 先进入非批量更新模式, 此时是立即更新
  26. this.isPending = true;
  27. // 保存当前的 更新器实例对象
  28. let updater;
  29. // 取出来每一个,调用更新器的 更新组件的方法
  30. while ((updater = updaters.pop())) {
  31. updater.updateComponent();
  32. }
  33. // 重置为非批量更新模式
  34. this.isPending = false;
  35. },
  36. };
  37. // 更新器 对象
  38. class Updater {
  39. constructor(componentInstance) {
  40. this.componentInstance = componentInstance; // 一个类组件对应一个更新器
  41. this.pendingStates = []; // 更新可能是批量的, 如果是批量的话, 把分状态先存到这个数组中
  42. this.nextProps = null; // 新的属性对象
  43. }
  44. addState(partialState) {
  45. this.pendingStates.push(partialState); // 先把分状态放到数组里
  46. this.emitUpdate(); // 开始视图更新
  47. }
  48. // 可能会出入一个新的属性对象过来
  49. emitUpdate(nextProps) {
  50. this.nextProps = nextProps;
  51. // 如果传入了新的属性对象,或者当前是非批量更新模式的话,那就直接更新, 否则先不更新
  52. if (nextProps || updateQueue.isPending) {
  53. this.updateComponent();
  54. } else {
  55. updateQueue.add(this); // 如果当前是批量更新的模式, 则把自己这个 updater 实例放到更新器数组中
  56. }
  57. }
  58. // 组件更新
  59. updateComponent() {
  60. let { componentInstance, pendingStates, nextProps } = this;
  61. // 说明有等待执行合并的更新状态
  62. if (nextProps || pendingStates.length > 0) {
  63. shouldUpdate(componentInstance, nextProps, this.getState());
  64. }
  65. }
  66. // 获取组件的状态
  67. getState() {
  68. let { componentInstance, pendingStates } = this;
  69. // 拿到老状态
  70. let { state } = componentInstance;
  71. // 合并新老state
  72. if (pendingStates.length > 0) {
  73. pendingStates.forEach(
  74. (nextState) =>
  75. (state =
  76. typeof nextState === 'function'
  77. ? nextState.call(componentInstance, state)
  78. : { ...state, ...nextState }),
  79. );
  80. }
  81. // 用完清空掉
  82. pendingStates.length = 0;
  83. return state;
  84. }
  85. }

27. Redux设计理念

Redux是将整个应用状态存储到一个地方上称为store,里面保存着一个状态树store tree,组件可以派发(dispatch)行为(action)给store,而不是直接通知其他组件,组件内部通过订阅store中的状态state来刷新自己的视图。

react老秦 - 图6

28. Redux三大原则

  • 1 唯一数据源

整个应用的state都被存储到一个状态树里面,并且这个状态树,只存在于唯一的store中

  • 2 保持只读状态

state是只读的,唯一改变state的方法就是触发action,action是一个用于描述以发生时间的普通对象

  • 3 数据改变只能通过纯函数来执行

使用纯函数来执行修改,为了描述action如何改变state的,你需要编写reducers

  1. let createStore = (reducer) => {
  2. let state;
  3. //获取状态对象
  4. //存放所有的监听函数
  5. let listeners = [];
  6. let getState = () => state;
  7. //提供一个方法供外部调用派发action
  8. let dispath = (action) => {
  9. //调用管理员reducer得到新的state
  10. state = reducer(state, action);
  11. //执行所有的监听函数
  12. listeners.forEach((l) => l())
  13. }
  14. //订阅状态变化事件,当状态改变发生之后执行监听函数
  15. let subscribe = (listener) => {
  16. listeners.push(listener);
  17. }
  18. dispath();
  19. return {
  20. getState,
  21. dispath,
  22. subscribe
  23. }
  24. }
  25. export default createStore;

29. 路由实现原理

HashRouter:利用 hash 实现路由切换
BrowserRouter:实现 h5 Api 实现路由的切换

1.hash

  1. <html lang="en">
  2. <head>
  3. <meta charset="utf-8" />
  4. <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
  5. <meta name="viewport" content="width=device-width, initial-scale=1" />
  6. <meta name="theme-color" content="#000000" />
  7. <title>React App</title>
  8. </head>
  9. <body>
  10. <div id="root"></div>
  11. <a href="#/a">去a</a>
  12. <a href="#/b">去b</a>
  13. <script>
  14. window.addEventListener('hashchange', () => {
  15. console.log(window.location.hash)
  16. })
  17. </script>
  18. </body>
  19. </html>

2. history

  • history 对象提供了操作浏览器会话历史的接口。
  • historylength 属性声明了浏览器历史列表中的元素数量
  • pushState HTML5 引入了 history.pushState()history.replaceState() 方法,它们分别可以添加和修改历史记录条目。这些方法通常与 window.onpopstate 配合使用
  • onpopstate window.onpopstatepopstate 事件在 window 对象上的事件处理程序