前端综合面试题【看这一篇就够了】

文章目录

说说React生命周期中有哪些坑?为什么要溢出will相关生命周期?

React 的生命周期是组件的重要概念,它描述了 React 组件在运行过程中会经历的一系列阶段。在使用 React 进行开发时,需要注意一些 React 生命周期中存在的坑,以避免出现意外结果。

React 生命周期中常见的坑:

  1. componentWillReceiveProps 在 React 17 版本中已被废弃,如果使用此生命周期函数可能会导致意外情况。建议使用 getDerivedStateFromProps 或 componentDidUpdate 替代。
  2. shouldComponentUpdate 可能会降低性能,因为每次渲染都会被调用。建议使用 PureComponent 或 shouldComponentUpdate 做判断来避免不必要的渲染。
  3. componentWillMount 过早的访问状态可能会导致预期之外的结果,建议在 constructor 中初始化组件状态。
  4. componentWillUnmount 需要及时清理事件、定时器和异步请求等资源,否则可能会导致内存泄漏。

为什么要溢出will相关生命周期?

React 团队在最近的更新中建议开发者尽量减少使用 componentWillReceiveProps、componentWillUpdate、componentWillMount 等生命周期函数,并建议开发者优先考虑使用 getDerivedStateFromProps、shouldComponentUpdate、constructor 等替代方案。这是因为前者可能会造成一些副作用和性能问题,而后者能够更好地支持异步渲染和 React 未来的优化方向。

在 React 16 版本中,React 团队引入了新的生命周期函数 getDerivedStateFromProps 和 componentDidCatch,前者用于替代 componentWillReceiveProps,后者用于处理组件中的错误。这些新的生命周期函数能够更好地与异步渲染和 Fiber 架构进行配合,从而提高应用的性能和稳定性。因此,在开发时,建议尽量使用最新的生命周期函数,避免过多地使用废弃的生命周期函数。

说说Real diff算法是怎么运作的,从tree层到component层到element层分别讲解?

React 的 diff 算法是用来比较新旧虚拟 DOM(Virtual DOM)之间的差异,从而能够尽量减少 DOM 操作,提高渲染性能。

React diff 算法分为三个层次:tree 层、component 层和 element 层。

在 tree 层,React 会比较整棵树的结构是否有变化,如果有,则直接替换整棵树。

在 component 层,React 会通过 shouldComponentUpdate 函数判断哪些组件需要更新,哪些组件不需要更新。如果 shouldComponentUpdate 返回 true,则表示需要更新该组件及其子组件;如果 shouldComponentUpdate 返回 false,则表示该组件及其子组件不需要更新。通过这样的判断,可以优化不必要的更新操作,提高渲染性能。

在 element 层,React 会对同级别的元素进行比较,并尽可能地复用已有的元素。对于新增的元素,React 会直接创建新的元素并添加到页面中;对于删除的元素,React 会将其从页面中删除。如果元素的属性或文本内容发生变化,React 会直接更新元素的属性和内容,从而避免整个元素的重新渲染。

总体来说,React diff 算法的核心思想是尽可能地减少 DOM 操作。通过比较新旧虚拟 DOM 的差异,并仅对发生变化的元素进行更新,可以大大提高应用的渲染性能。

调和阶段setState干了什么?

React 中的 setState 是用来更新组件状态的方法,它会在异步调和阶段才会进行状态的合并(merge),具体的流程如下:

  1. 首先,React 会将调用 setState 的组件标记为“dirty”,表示该组件需要更新。
  2. 然后,React 会将所有标记为“dirty”的组件加入一个队列中,并执行“批量更新”操作。
  3. 在“批量更新”过程中,React 会对同一个组件进行多次 setState 调用时的状态进行合并。如果多个 setState 调用中有相同的状态属性,则取最后一次调用时的值。如果多个 setState 调用中有不同的状态属性,则对它们进行浅合并,也就是只取出最外层的属性进行合并。
  4. 最后,在合并完所有组件的状态之后,React 会根据“dirty”组件的更新策略,逐一执行它们的生命周期函数,并生成新的虚拟 DOM 树。如果新旧虚拟 DOM 树中有差异,React 会更新页面上的 DOM 元素。

总的来说,setState 这个方法是非常重要的,因为它能够触发组件的重新渲染,更新界面内容。而调和阶段则是 React 环节中一个非常重要的流程,它能够优化组件更新的性能,减少不必要的 DOM 操作,提高应用的响应速度。

CSS3的新特性都有哪些?

CSS3 是 CSS 的第三个版本,引入了很多新的特性和功能。下面是一些 CSS3 的新特性:

  1. 选择器:CSS3 引入了新的选择器,如属性选择器、伪类选择器和伪元素选择器等,可以更精确地选中元素。
  2. 盒模型:CSS3 中引入了新的 box-sizing 属性,可以改变盒模型的计算方式,方便开发者实现弹性布局。
  3. 布局:CSS3 中新增了 flexbox 和 grid 布局,可以用于灵活、响应式布局。
  4. 动画:CSS3 中引入了动画属性(animation)和过渡属性(transition),可以实现动态效果,如弹出菜单、淡入淡出等。
  5. 媒体查询:CSS3 中引入了媒体查询,可以根据设备的宽度、高度、屏幕方向等特性,实现响应式布局。
  6. 边框:CSS3 中引入了新的边框样式(border-style),包括点线、双线、波浪线等,增加了页面的视觉效果。
  7. 字体:CSS3 中新增了 @font-face 规则,可以引入自定义字体文件,丰富了网页字体的选择。
  8. 渐变:CSS3 中引入了线性渐变和径向渐变,可以实现更丰富的背景效果。

总体来说,CSS3 的新特性增加了很多样式和功能,可以让开发者更轻松地实现各种复杂的设计需求,并且可以提高网页的视觉效果和用户体验。

说说redux的工作流程?

Redux 是一种用于管理 React 应用状态的 JavaScript 库,它的设计思路是将应用的状态和视图分离,使用单向数据流的方式进行状态管理。Redux 的工作流程大致可以分为以下几步:

  1. 定义 Store:首先,在 Redux 中需要定义一个 Store 来管理应用的状态。Store 可以看作是一个数据仓库,整个应用的状态都存储在这里。
  2. 创建 Action:开发者通过创建 Action 来描述用户的行为,例如点击按钮、提交表单等。Action 通常是一个 JavaScript 对象,包含一个 type 字段指定行为的类型,以及一些其他字段描述行为的具体信息。
  3. 分发 Action:当用户进行某个行为时,开发者需要将对应的 Action 分发到 Store 中,这个过程中可能会使用到 Redux 中的 dispatch 方法。
  4. 处理 Action:当一个 Action 被分发到 Store 后,Store 会将这个 Action 发送到 Reducer 函数。Reducer 函数根据 Action 的类型来更新应用的状态,返回一个新的应用状态。
  5. 更新 Store:最后,Redux 会更新 Store 中的状态,并触发应用重新渲染的过程。开发者需要根据应用状态的变化来更新页面视图,从而实现数据和视图的同步更新。

总体来说,Redux 的工作流程是非常简单和直观的,但是需要开发者习惯使用纯函数来管理状态,并且需要使用一些辅助工具和中间件来简化开发流程。通过 Redux,开发者可以将应用的状态和 UI 完全分离,从而实现更加灵活、可维护的应用架构。

React合成事件的原理,有什么好处和优势?

React 的合成事件是一种封装了浏览器原生事件的一种机制,它用于处理用户与组件之间的交互操作。在 React 中,所有的事件都通过合成事件来处理,而不是直接使用原生 DOM 事件。

React 合成事件的原理是在组件树中捕获所有的事件,并将其转化为一个统一的格式,这个统一的格式包含了所有事件相关的信息。React 会从顶层组件依次向下传递事件,直到找到对应的组件进行处理。这个过程中,React 使用了类似于事件冒泡的机制,但有一些细节和差别。

React 合成事件的好处和优势主要有以下几个方面:

  1. 跨平台兼容:React 合成事件可以跨浏览器和跨平台使用,这是因为 React 封装了浏览器原生事件的差异性,并提供了统一的事件系统。
  2. 性能优化:React 合成事件可以在同一事件处理函数中处理多个事件,从而避免了频繁创建和销毁事件监听器的开销,提高了事件处理的性能。
  3. 方便调试:React 合成事件可以方便地打印出事件的详细信息,包括事件类型、冒泡路径、目标对象等,方便开发者调试代码。
  4. 事件委托:React 合成事件使用了事件委托的机制,也就是说,所有事件都是在顶层组件进行处理,而不需要在每个组件上单独注册监听器。这样可以减少事件绑定和解绑的次数,提高了应用的性能。

总体来说,React 合成事件的原理和优势,是 React 框架重要的一部分。相比于直接使用浏览器原生事件,React 合成事件提供了更加统一和简洁的事件处理方式,可以提高开发效率和应用性能。

为什么react元素有一个$$type属性?

React 元素是 React 应用中的基本组成部分,它是描述组件在 UI 中渲染结果的对象。在 React 中,每个 React 元素都包含一个 $$type 属性,这个属性的作用是指定该元素的类型。

这里的 $$type 属性实际上是 React 内部使用的属性,以区分不同类型的 React 元素。在实际开发中,我们不需要直接操作和访问这个属性。

React 使用 t y p e 属性来区分不同类型的 R e a c t 元素,从而对于不同类型的元素采取不同的处理方式。例如,如果 type 属性来区分不同类型的 React 元素,从而对于不同类型的元素采取不同的处理方式。例如,如果 type属性来区分不同类型的React元素,从而对于不同类型的元素采取不同的处理方式。例如,如果type 为字符串,则表示这个元素是原生 DOM 元素或 React 组件;如果 t y p e 为函数,则表示这个元素是一个自定义函数式组件;如果 type 为函数,则表示这个元素是一个自定义函数式组件;如果 type为函数,则表示这个元素是一个自定义函数式组件;如果type 为类,则表示这个元素是一个自定义类组件。

另外,值得注意的是,React 的内部使用了一些技巧来减小渲染性能的消耗,其中之一就是通过对比 t y p e 属性来判断是否需要重新渲染组件。当 R e a c t 在 d i f f 算法中检测到两个元素的 type 属性来判断是否需要重新渲染组件。当 React 在 diff 算法中检测到两个元素的 type属性来判断是否需要重新渲染组件。当Reactdiff算法中检测到两个元素的type 不同时,会认为这两个元素是不同的,从而会重新渲染组件。

总之,React 元素的 $$type 属性是 React 内部使用的一个属性,用于区分不同类型的 React 元素,并对不同类型的元素采取不同的处理方式。在使用 React 进行开发时,我们不需要直接操作和访问这个属性,它会被 React 自动处理。

react的优化方案?

React 在性能优化方面提供了很多的手段,这里列举一些常用的手段:

  1. 使用虚拟 DOM:React 使用虚拟 DOM 将渲染结果存储在内存中,并只在必要情况下进行更新。这样可以避免常规的 DOM 操作和重排/重绘等开销,提高应用的性能。
  2. 使用 React.memo():React.memo() 是一个高阶组件(Higher-Order Component),用于对函数式组件进行浅比较,从而检测何时进行重新渲染。使用 React.memo() 可以避免不必要的重新渲染,提高组件的性能。
  3. 使用 shouldComponentUpdate() 或 PureComponent:shouldComponentUpdate() 方法或 PureComponent 组件都可以用来检测组件是否需要进行更新。这样可以避免不必要的重新渲染,提高组件的性能。
  4. 使用异步渲染和批量更新:React 支持将组件的更新推迟到下一个事件循环,从而避免阻塞主线程。同时,React 还支持将多个 setState() 调用批量处理,从而减少不必要的重复操作。
  5. 使用生命周期方法:React 的生命周期方法可以用来控制组件的渲染过程。例如,使用 componentDidMount() 方法可以在组件渲染完成后进行一些操作,如添加事件监听器等。
  6. 使用 Webpack 和 Babel 进行代码优化:Webpack 可以对项目进行打包和优化,从而提高应用性能。Babel 可以将 ES6/ES7 的语法转换成 ES5 的语法,从而兼容旧版浏览器。

以上是 React 常用的性能优化手段,当然还有其他的一些方面也可以进行优化,如使用 memoization、lazy loading 等技术。在实际开发中,我们需要根据具体的场景和需求选择合适的优化手段,综合考虑性能和开发效率。

说说你对fiber架构的理解?fiber解决了什么问题?

Fiber 是 React 应用的一种新的渲染机制,它是 React 16 中引入的协调器。Fiber 架构解决了 React 在处理大型组件树时可能会出现的卡顿、掉帧等性能问题,同时还可以使组件的更新达到更精准和可控的程度。

传统的 React 渲染机制是基于递归实现的,在处理大型组件树时,可能会因为递归深度太大而导致浏览器失去响应,从而影响用户体验。而 Fiber 通过将递归转化为一系列任务并对其进行分片处理,使得 React 在更新组件时可以中断当前任务并优先执行其他高优先级任务,进而避免了整个组件树的渲染阻塞。

此外,Fiber 还支持异步渲染,即 React 可以在下一个事件循环开始时,才对组件进行更新,从而避免了在单个事件循环中进行大量的计算和渲染操作,这一特性可使 React 应用更加稳定,同时提升了用户体验。

除了性能上的改进,Fiber 还赋予了 React 更高的可编程性。如可以使用 Scheduler API 控制任务调度的优先级,也可以通过暂停、恢复和放弃任务等操作,实现更精细的任务调度和控制。

因此,Fiber 架构是 React 应用的一种新的渲染机制,在性能、稳定性和可编程性等方面都有所改进,其出现使得 React 应用在处理大型组件树时更加流畅和高效。

如何避免ajax数据请求重新获取?

为了避免重复获取 Ajax 数据,我们可以使用缓存机制。具体来说,可以将获取到的 Ajax 数据存储在内存、本地存储或者浏览器缓存中,下一次需要使用相同数据时,优先从缓存中获取。在缓存中存储数据时,我们可以使用一个唯一的标识符作为键名(如请求 URL),以此来区分不同的数据缓存。

如果采用 localStorage 等本地存储方式保存数据,还需要注意数据的过期问题。可以使用类似于 Redis 的 TTL(Time To Live)机制,使得缓存数据在一定时间后失效,并在再次请求时重新获取数据并更新缓存。这样可以使得缓存数据保持最新性,同时也避免了因为数据长期未更新而导致的缓存过期。

另外,在使用 Ajax 的时候,可以通过设置 HTTP 缓存头信息来实现浏览器缓存机制。例如,设置 “Cache-Control” 和 “Expires” 头字段,来指定缓存的时间和是否允许缓存。有时候,服务器端也可以返回一个特殊的 HTTP 状态码,如 “304 Not Modified” 来告诉浏览器使用缓存中的数据,而不是重新获取数据。

综上所述,我们可以通过使用缓存机制,包括本地存储或浏览器缓存、HTTP 缓存头信息等方式,来避免 Ajax 数据的重复获取。同时在缓存数据时也需要注意数据过期时间和更新机制,以保持数据的最新性。

短轮询、长轮询和 WebSocket 间的区别?

短轮询、长轮询和 WebSocket 都是用于客户端和服务器端进行通信的方式,但它们在实现机制上有很大的区别。

  1. 短轮询

短轮询是指客户端每隔一段时间向服务器端发送一个请求,服务器端返回最新的数据。这种方式简单易用,适用于数据更新频率不高的场景。但是,由于客户端需要不断地发送请求,所以会造成服务器端的不必要的负载和网络带宽浪费。

  1. 长轮询

长轮询是指客户端向服务器端发送一个请求,如果服务器没有新的数据,就会一直保持连接不响应,直到有新的数据时再返回给客户端。客户端在收到响应后再次发起请求,这样可以减少不必要的请求次数。这种方式适用于数据更新频率较高的场景,但是由于需要维护长时间的连接,所以会占用服务器端的资源并增加服务器端的负担。

  1. WebSocket

WebSocket 是 HTML5 中新增的一种协议,它利用了 TCP 连接,实现了客户端与服务器端的双向通信。在建立 WebSocket 连接时,需要经过一个握手阶段,建立连接后就可以双向发送数据。WebSocket 优势在于它能够实时推送数据,而且不会额外增加服务器端的负担。它可以在多个客户端之间实现实时通信,如在线聊天、实时游戏等。

总体来说,短轮询和长轮询都是通过 HTTP 协议进行通信的,而 WebSocket 利用了 TCP 连接,实现了双向通信。三者的适用场景不同,开发者在选择使用哪种方式时应根据实际情况进行选择。

说说你对事件循环event loop的理解?

事件循环(Event Loop)是 运行时中非常重要的一个机制,它可以让我们编写出非阻塞异步代码。事件循环机制中,主线程会不断地从消息队列中取出消息执行,每次只执行一个消息,直到所有消息队列中的消息都已经被执行完毕。

事件循环机制主要由以下几个部分组成:

  1. 消息队列:用来存储所有的任务和事件,例如异步回调函数、用户交互事件等。
  2. 执行栈:用来存储当前正在执行的任务和函数,JavaScript 代码执行时就是从执行栈中取出任务并执行。
  3. 微任务队列:用来存储所有微任务,例如 Promise 的 resolve 回调和 MutationObserver 的回调等。
  4. 宏任务队列:用来存储所有宏任务,例如 setTimeout 和 setInterval 的回调。

事件循环机制的执行过程如下:

  1. 首先执行当前的同步任务,如果存在异步任务则将其加入到消息队列中,然后继续执行同步任务。
  2. 当同步任务执行完毕后,会从消息队列开始处理任务。
  3. 从消息队列中取出第一个任务,如果该任务为一个微任务,则立即执行,如果该任务为一个宏任务,则加入到宏任务队列中等待执行。
  4. 当前微任务执行完成之后,将继续从微任务队列中取出下一个微任务执行,直到微任务队列为空。
  5. 当前宏任务执行完成之后,将继续从宏任务队列中取出下一个宏任务执行,如此反复循环,直到消息队列为空。

总体来说,事件循环机制是一种异步编程实现方式,通过将异步操作加入到消息队列,并在主线程空闲时执行回调函数来实现异步处理。熟悉事件循环机制可以帮助开发者更好地理解 JavaScript 异步处理方式,并编写高性能、高可靠性的异步代码。

前端跨域的解决方案?

前端跨域问题是指在浏览器中由于安全策略的限制,导致浏览器不能直接访问其他域名下的资源。常见的解决方案包括:

  1. JSONP:这是一种基于 script 标签的跨域解决方案,它通过在请求中添加一个回调函数来获取服务器返回的数据。JSONP 只支持 GET 请求,且需要服务端配合返回符合要求的数据。
  2. CORS:CORS(Cross-Origin Resource Sharing)是一种网站解决跨域问题的标准方式,它主要通过在服务器端设置响应头信息来支持跨域请求。CORS 支持所有类型的 HTTP 请求,包括 GET、POST 等,且对于 POST 请求支持 Content-Type 为 application/json 的数据格式。
  3. iframe 跨域:通过在页面中嵌入一个隐藏的 iframe 并将请求发送到该 iframe 中来实现跨域请求。不过由于使用 iframe 可能会对用户体验产生负面影响,因此在实际开发中一般不推荐使用。
  4. 服务端代理:通过在自己的服务器上建立一个代理,将浏览器请求发送到自己的服务器上,在服务器上转发请求到目标服务器,并将响应结果返回给浏览器。这种方案比较灵活,但同时也带来了一些性能问题和安全风险。
  5. WebSocket:WebSocket 是一种基于 TCP 协议的全双工通信协议,它可以在客户端和服务器之间建立持久连接,支持跨域请求。使用 WebSocket 协议可以实现服务端主动向客户端推送消息的功能,从而达到实时通信的目的。
  6. Nginx 反向代理:Nginx 是一款高性能的 Web 服务器,可以用作反向代理服务器来解决跨域问题。通过在 Nginx 配置中添加反向代理配置,将浏览器请求发送到代理服务器上,在代理服务器上修改响应头中的跨域信息,最后将响应结果返回给浏览器。

总结来说,以上是常见的前端跨域解决方案,每个方案都有着自己的特点和适用场景,需要开发者根据具体情况选择最合适的方案。

js数组常用方法及作用,至少15个?

JavaScript 中的数组是一种特殊的对象类型,它可以存储多个值,并且这些值之间是有序的。JavaScript 提供了大量的数组操作方法,以下是常用的15个方法及其作用:

  1. push():向数组末尾添加一个或多个元素,并返回新的数组长度。
  2. pop():删除数组末尾的元素,并返回该元素的值。
  3. unshift():向数组开头添加一个或多个元素,并返回新的数组长度。
  4. shift():删除数组开头的元素,并返回该元素的值。
  5. splice():向/从数组中添加/删除元素,可以用于增加、删除或替换数组中的元素。
  6. slice():截取数组的一部分,返回截取后的新数组,不会影响原数组。
  7. concat():将两个或多个数组合并成一个新数组。
  8. join():使用指定的分隔符将数组转换为字符串。
  9. indexOf():查找数组中某个元素第一次出现的位置,如果没有找到则返回 -1。
  10. lastIndexOf():查找数组中某个元素最后一次出现的位置,如果没有找到则返回 -1。
  11. reverse():将数组中的元素翻转顺序。
  12. sort():将数组按照字母表顺序排序,也可以自定义排序规则。
  13. filter():通过指定函数过滤数组中的元素,并返回符合条件的新数组。
  14. map():通过指定函数对数组中的每个元素进行操作,并返回操作后的新数组。
  15. reduce():对数组中的元素逐个执行指定的操作,并返回一个最终值。

以上这些方法是 JavaScript 中常用的数组方法,掌握它们可以很好地帮助我们在编写 JavaScript 代码时更高效地处理数组数据。当然,除了以上这些方法,JavaScript 中还有更多的数组相关方法,有需要时可以参考官方文档进行学习和使用。

React render方法的原理,在什么时候会触发?

React 中的 render 方法是 React 组件中必不可少的方法之一。它的作用是渲染组件的 UI,并返回一个对应的 React 元素。

当我们在父组件中,使用子组件的标签来渲染子组件时,在 React 底层会自动调用子组件的 render 方法,从而生成对应的 React 元素树。这个过程一般发生在以下情况中:

  1. 当组件初次挂载到页面上时,render 方法会被调用,生成对应的 React 元素,并将其插入到 DOM 中。
  2. 当组件的状态或属性发生变化时,React 会重新调用 render 方法,并生成一个新的 React 元素树,然后与旧的元素树进行比较,找出差异部分进行更新。
  3. 如果使用了 React 的路由功能,当路由切换时,也会重新调用 render 方法,生成新的 React 元素树,并根据路由信息展示对应的组件。

render 方法的具体实现原理,主要是通过使用虚拟 DOM 技术,将组件的 JSX 代码转换成对应的 React 元素树,并且在应用程序中使用 Diff 算法来寻找变化,最终进行更新。简单来说,就是将组件的描述结构转换为对应的 React 元素,并通过真实 DOM 的操作来实现对应的效果。

总结来说,render 方法是 React 组件中非常重要的一个方法,用于将组件的描述转换为真实 DOM 元素,并在需要更新界面时重新调用以重新渲染 DOM。而其具体实现原理,则是通过使用虚拟 DOM 技术,将 JSX 代码转换为对应的 React 元素,并通过 Diff 算法来寻找变化,最终进行更新。

说说你对vue中mixin的理解?

在 Vue 中,Mixin 是一种代码复用的技术,它可以将一个或多个组件中的一些通用的逻辑和函数抽象出来,封装在一个独立的 Mixin 对象中,然后再将这个 Mixin 对象混入到多个组件中使用,以达到代码重用的目的。

具体来说,Mixin 可以包含多个 Vue 组件中共享的属性、方法、生命周期函数等,并且这些共享的部分可以被多个组件所使用。例如,在多个组件中都要用到的某些数据或计算方法,就可以将其定义在 Mixin 中,从而避免了重复编写相同的代码。

在 Vue 中使用 Mixin 也非常简单,只需要创建一个包含需要共享的逻辑和函数的普通对象,然后通过 mixins 选项将其混入到需要使用的组件中即可。值得注意的是,如果混入的对象和组件中已有的属性或方法发生冲突,那么组件中的属性或方法将会覆盖 Mixin 中的属性或方法。

除此之外,Vue 还提供了全局 Mixin,可以将其混入到所有的 Vue 组件中,这样就能够让每个组件都拥有相同的功能或行为。不过需要注意的是,全局 Mixin 的使用应该谨慎,因为它会影响到所有组件,如果使用不当可能会带来很多意外问题。

综上所述,Mixin 是 Vue 中一种非常有用的功能,能够有效地提高代码复用性,避免代码冗余,并且方便维护和管理。但需要注意的是,在使用时要遵循一定的规范和约束,防止出现意外的问题。

for…in循环和for…of循环的区别?

for...in 循环和 for...of 循环是 JavaScript 中两种不同的迭代方式。

  1. for...in 循环:用于遍历对象(Object)中的可枚举属性,包括自有属性和继承属性。它返回的是对象属性名(键名),而非属性值。
  2. for...of 循环:用于遍历可迭代对象(Iterable),例如数组、字符串、Set、Map 等数据结构,它返回的是当前元素的属性值,而非属性名。

需要注意的是,for...of 循环只能遍历具有迭代器(Iterator)接口的数据结构。如果要遍历普通的对象,可以先通过 Object.keys()Object.values()Object.entries() 方法将其转换为数组,再使用 for...of 循环进行遍历

总结来说,for...in 循环主要用于遍历对象中的键名,而 for...of 循环主要用于遍历可迭代对象中的元素值。在实际编程中,需要根据具体的场景选择合适的循环方式。

Js数据类型判断都有哪几种方式?至少说出5种?它们的区别是什么?

JavaScript 中常见的数据类型有以下几种:Number、String、Boolean、Undefined、Null、Object 和 Symbol。判断数据类型的方式主要有以下5种:

  1. typeof 操作符:可以返回一个值的数据类型 — (需要注意的是,typeof 的返回值是一个字符串,在判断对象或数组等引用类型时只能判断出是否是对象或数组,不能具体判断其内部结构。)
  2. instanceof 操作符:可以判断一个对象是否属于某个类或其子类的实例 — (需要注意的是,instanceof 只能判断对象是否是某个类的实例,无法判断基本数据类型)
  3. isNaN() 函数:可以判断一个值是否为 NaN(Not a Number) — (isNaN() 函数对于非数字类型的值,会先进行类型转换,如果转换后的值不是数值(比如字符串、对象、数组等),则会返回 true。)
  4. 正则表达式:可以通过正则表达式判断一个值的类型 — (这种方式比较麻烦,但可以灵活地判断各种类型)
  5. Object.prototype.toString.call() 方法:可以判断一个值的具体类型 — (这种方法可以判断出具体类型,包括对象和数组等引用类型的内部结构。)

说说你对Object.defineProperty()的理解?

Object.defineProperty() 是 JavaScript 中用来定义对象属性的方法,它可以修改或添加对象的属性,并设置属性的特性(属性描述符)。

Object.defineProperty(obj, prop, descriptor)

其中,obj 表示要修改的目标对象,prop 表示要修改或添加的属性名,descriptor 是属性描述符对象。

属性描述符对象可以包含以下属性:

  • value:属性的值,默认为 undefined。
  • writable:是否可写,默认为 false。
  • enumerable:是否可枚举,默认为 false。
  • configurable:是否可配置,默认为 false。
  • get:在读取属性时调用的函数,默认为 undefined。
  • set:在设置属性时调用的函数,默认为 undefined。

Object.defineProperty() 方法可以对属性进行更精细的控制,提高程序的灵活性和安全性。比如,设置 writable 属性为 false,可以避免属性被意外修改;设置 enumerable 属性为 false,可以避免属性被枚举到;设置 configurable 属性为 false,可以避免属性被删除或修改其特性。同时,也可以通过 get 和 set 方法,对属性进行更复杂的处理和计算。

需要注意的是,如果要对一个对象的多个属性进行设置,可以使用 Object.defineProperties() 方法,该方法接收的第二个参数是一个对象,其属性名为要定义或修改的属性名,属性值为属性描述符对象。

介绍一下 Tree Shaking?

Tree Shaking 是一种 JavaScript 打包技术,用于在打包构建应用程序时删除没用到的代码,以减小打包后文件的体积,提高应用程序的性能。它的实现原理是通过静态代码分析,找出不会被执行的代码,并将其从最终生成的代码中移除。

Tree Shaking 技术通常用于处理 ES6 模块中的代码,因为 ES6 模块采用了静态编译的方式,在编译时就能确定哪些代码会被调用。在 JavaScript 应用中,Tree Shaking 技术通常与打包工具结合使用,比如 webpack 4.0 以上版本的 UglifyJsPlugin 和 terser-webpack-plugin 插件。

清除浮动的几种方式,各自的优缺点,推荐使用哪种??

清除浮动是一种常见的前端开发技巧,用于解决父元素无法正确包裹浮动子元素而导致布局混乱的问题。以下是几种清除浮动的方式及其优缺点:

  1. 使用空div清除浮动:

    cssCopy Code.clearfix::after {
      content: "";
      display: table;
      clear: both;
    }
    

    优点:简单易懂,适用于大多数情况。 缺点:需要额外添加一个空的div元素,增加了HTML结构。

  2. 使用overflow属性清除浮动:

    cssCopy Code.clearfix {
      overflow: auto;
    }
    

    优点:不需要添加额外的HTML元素,代码精简。 缺点:可能导致内容溢出被裁剪,对于有固定高度的容器不适用。

  3. 使用clearfix类库: 在项目中使用现成的clearfix类库,例如Bootstrap中的.clearfix类。 优点:方便快捷,已经经过广泛的测试和使用。 缺点:引入整个类库可能造成代码冗余。

推荐使用第一种方式,即使用空div清除浮动。它是一种常见且兼容性较好的解决方案,适用于大多数情况。如果特殊情况下遇到高度固定的容器溢出问题,可以选择使用第二种方式。对于已经使用了类库的项目,可以考虑直接使用该类库提供的清除浮动样式。

需要注意的是,现代CSS布局技术已经发展,很多情况下不再需要手动清除浮动。可以通过使用Flexbox或Grid等来实现更灵活和可靠的布局。

Sass、LESS区别是什么?大家为什么要使用他们?

Sass和LESS都是CSS预处理器,它们在CSS的基础上提供了一些额外的功能和语法来增强样式表的编写效率。它们的区别主要体现在以下几个方面:

  1. 语法风格:Sass使用缩进式的语法,而LESS使用类似于CSS的语法。Sass的缩进风格更加简洁紧凑,而LESS的语法更接近于原生CSS,对于熟悉CSS的开发者更易上手。
  2. 变量声明:Sass使用$符号来声明变量,而LESS使用@符号。例如,在Sass中声明一个颜色变量可以这样写:$color: #f00;,而在LESS中写法为:@color: #f00;
  3. 运算操作:Sass和LESS都支持对数值进行加、减、乘、除等运算操作。但是它们在运算时的单位处理上有所差异。Sass会根据被运算的数值自动识别单位,而LESS需要手动添加单位。
  4. 混合(Mixin)功能:混合是预处理器中非常有用的特性,它允许开发者定义可重复使用的样式块。Sass和LESS都支持混合功能,但在语法上有所不同。Sass使用@mixin关键字来定义混合,通过@include关键字来调用混合;而LESS使用.{classname}()的语法来定义混合,通过.classname();的语法来调用混合。

为什么要使用Sass和LESS呢?

  1. 变量和函数:预处理器允许使用变量和函数,可以减少重复的代码,提高开发效率。可以在样式表中定义一次变量和函数,在多个地方重复使用,便于维护和修改。
  2. 嵌套规则:预处理器可以使用嵌套规则,使得CSS的层级结构更加清晰,易读且易维护。可以将相关的样式规则组织在一起,提高代码的可读性。
  3. 混合功能:混合功能允许开发者定义可重用的样式块,可以减少样式表的代码量,并且当需要修改样式时,只需修改混合部分的样式即可,避免了代码冗余和重复。
  4. 兼容原生CSS:Sass和LESS都是在CSS的基础上扩展而来,因此可以逐步转换现有的CSS代码,无需全部重写。这样可以方便地将现有项目迁移到预处理器上。

总之,Sass和LESS都提供了便捷的语法和功能来简化CSS开发过程,提高开发效率和代码可维护性。它们的选择取决于个人和团队的偏好,以及项目的要求和现有的技术栈。

说说React生命周期有哪些不同的阶段?每个阶段对应的方法是?

在 React 的类组件中,有以下几个生命周期阶段及相应的方法:

  1. 挂载阶段(Mounting Phase):
    • constructor:用于初始化组件的状态和绑定事件处理方法。
    • static getDerivedStateFromProps:在组件实例化之后和接收到新的 props 时被调用,用于根据 props 更新状态。
    • render:返回要渲染的 React 元素。
    • componentDidMount:在组件第一次渲染到 DOM 后调用,可以执行一些副作用操作,如网络请求、订阅等。
  2. 更新阶段(Updating Phase):
    • static getDerivedStateFromProps:在组件接收到新的 props 时被调用,用于根据 props 更新状态。
    • shouldComponentUpdate:决定组件是否需要重新渲染,默认返回 true。可以在此进行性能优化,避免不必要的渲染。
    • render:返回要渲染的 React 元素。
    • componentDidUpdate:在组件更新后被调用,可以执行一些副作用操作,如网络请求、订阅等。
  3. 卸载阶段(Unmounting Phase):
    • componentWillUnmount:在组件被卸载前被调用,可以执行一些清理操作,如取消网络请求、清除定时器等。
  4. 错误处理阶段(Error Handling Phase):
    • static getDerivedStateFromError:在子组件抛出错误时被调用,在渲染过程中捕获错误并更新组件状态。
    • componentDidCatch:在子组件抛出错误后被调用,用于记录错误信息或发送错误报告。

除了以上生命周期方法,还有一些不常用的生命周期方法,如 getSnapshotBeforeUpdate(在更新 DOM 之前获取 DOM 信息)、componentDidCatch(在错误处理阶段捕获错误)等。

需要注意的是,自 React 16.3 版本起,一些生命周期方法被废弃或重命名。建议根据使用的 React 版本查阅官方文档以获取最新的生命周期方法和用法。此外,React 16.8 版本引入的 Hooks 也提供了一种新的函数式组件开发方式,不再依赖于生命周期方法。

说说React中setState执行机制?

在 React 中,setState 是用于更新组件状态的方法,它是异步执行的。以下是 setState 的执行机制:

  1. 异步更新:当调用 setState 时,React 会将传入的状态更新对象合并到组件的待更新队列中,并不会立即触发重新渲染。这是因为 React 希望通过批量处理,将多个 setState 的更新操作合并为一次更新,以提高性能。
  2. 合并更新:在同一个事件循环(Event Loop)内,如果多次调用 setState,则 React 会对这些更新进行合并,只执行一次重新渲染。例如,在连续的事件处理函数中多次调用 setState,React 会将这些更新合并为一个状态更新操作。
  3. 批量更新:React 会在适当的时机触发组件的重新渲染。通常情况下,React 会在当前代码执行完毕、浏览器空闲时或下一个事件循环周期开始时,进行批量更新操作。这样可以避免频繁地进行昂贵的 DOM 操作,提高性能。
  4. 异步回调:可以给 setState 方法传递第二个参数作为回调函数,该回调函数会在状态更新并重新渲染后被调用。这样可以确保在获取最新的 DOM 信息或进行其他操作时使用最新的状态。

需要注意的是,由于 setState 是异步执行的,所以不能在调用 setState 后立即获取更新后的状态。如果需要使用更新后的状态,可以在回调函数中进行操作,或者使用 componentDidUpdate 生命周期方法来监听状态的变化。

如果需要基于先前的状态进行更新操作,应该使用函数形式的 setState,而不是直接传入对象。例如:

javascriptCopy Codethis.setState((prevState) => {
  return { count: prevState.count + 1 };
});

使用函数形式的 setState 可以确保获取到最新的状态,并避免因为异步更新导致计算错误。

总结起来,setState 是 React 中用于更新组件状态的方法,它是异步执行的,通过合并和批量更新机制来提高性能。理解 setState 的执行机制对于正确地管理组件状态和避免一些常见问题非常重要。

React组件之间如何通信?

如果需要手动写动画,最小时间间隔应根据浏览器的刷新率来确定。常见的浏览器刷新率为 60 次/秒(60 Hz),即每秒钟刷新屏幕的次数为 60 次。在这种情况下,最小时间间隔应为约 16.67 毫秒。

这是因为浏览器渲染引擎会以固定的频率进行屏幕刷新,如果设置的动画间隔小于刷新率,会导致动画在屏幕上的更新不被渲染,或者产生不稳定的效果。通过与浏览器的刷新率保持一致,可以确保动画流畅且性能良好。

当动画间隔小于 16.67 毫秒时,可以使用 requestAnimationFrame 方法来实现动画帧的更新。requestAnimationFrame 是浏览器提供的一个用于优化动画效果的 API,它会在浏览器刷新屏幕之前调用指定的回调函数,从而实现动画的流畅更新。

需要注意的是,对于一些精确的计时或高性能的动画,可以考虑使用 Web Animation API 或类似的库,以便更准确地控制动画的时间间隔和帧率,并提供更多的功能和选项。

总结起来,最小的动画时间间隔应与浏览器的刷新率保持一致,约为 16.67 毫秒。这样可以确保手动编写的动画在屏幕上得到流畅更新,并提供良好的用户体验。

说说你对受控组件和非受控组件的理解?应用场景?

在 React 中,组件之间可以通过以下几种方式进行通信:

  1. Props(属性):父组件可以通过 props 将数据传递给子组件。子组件可以通过访问 props 属性来获取父组件传递的数据。这是 React 组件之间最常用和简单的通信方式。
  2. State(状态):组件内部可以通过 state 来管理自己的状态。父组件可以将状态作为 prop 传递给子组件,子组件可以读取并使用该状态来进行相应的操作。如果状态发生变化,子组件会自动更新。
  3. Callbacks(回调函数):父组件可以将回调函数作为 prop 传递给子组件,在子组件中触发回调函数来通知父组件某些事件的发生或者传递数据给父组件。通过回调函数可以实现子组件向父组件的通信。
  4. Context(上下文):Context 是一种在组件树中共享数据的方式。父组件可以通过创建一个 Context,并将需要共享的数据传递给 Context 的 Provider 组件。然后,在子组件中通过 Context 的 Consumer 组件来获取共享的数据。这种方式适用于跨越多个层级的组件之间进行通信。
  5. Redux 或其他状态管理库:当组件之间的通信变得复杂时,可以考虑使用 Redux 或其他类似的状态管理库来集中管理应用程序的状态。这些库提供了一种集中式的状态管理机制,使得组件之间可以共享和更新状态。

需要根据具体需求选择适当的通信方式。对于简单场景,使用 Props 和回调函数就足够了。对于跨层级或复杂场景,可以考虑使用 Context 或状态管理库来实现组件之间的通信。在设计组件之间的通信时,要遵循单向数据流和组件的可复用性原则,以便保持代码的清晰和可维护性。

说说你对fiber架构的理解?解决了什么问题?

Fiber 架构是 React v16 引入的一种新的渲染机制,它重写了 React 的核心算法,主要解决了以下几个问题:

  1. 异步渲染:在旧的 Stack 架构中,React 会通过递归调用来一次性完成整个组件树的渲染,这样在大型或者复杂的组件树中可能会导致界面卡顿,用户体验下降。Fiber 架构通过引入 Fiber 虚拟节点和优先级调度算法,实现了异步渲染。它可以将渲染任务分割成多个小任务,并通过任务优先级调度算法来决定哪些任务先执行,从而提高了应用的响应能力。
  2. 增量更新:Fiber 架构引入了增量更新的概念,即在每个任务之间可以中断和恢复。这意味着 React 可以在每个浏览器帧之间停止当前任务,并将控制权交还给浏览器,从而避免了长时间的阻塞。增量更新使得 React 能够根据不同任务的优先级和当前浏览器空闲情况来灵活地调整工作量,提高了渲染的效率和性能。
  3. 错误边界:在旧的 Stack 架构中,当一个组件发生错误时,整个组件树的渲染会被中断,导致整个应用崩溃。而在 Fiber 架构中,React 引入了错误边界的概念,使得父组件可以捕获和处理子组件的错误,从而提高了应用的稳定性和健壮性。
  4. 高优先级任务的中断:Fiber 架构允许 React 在渲染过程中中断低优先级的任务,以便及时响应更高优先级的任务。这对于实现流畅的用户界面非常重要,因为用户交互事件通常具有更高的优先级,React 可以立即中断当前任务并开始处理用户交互,从而提高了用户体验。

总的来说,Fiber 架构通过引入异步渲染、增量更新、错误边界和任务优先级调度等新特性,解决了 React 旧版本中存在的渲染卡顿、性能问题和错误处理不充分的缺点,提升了渲染的效率、可靠性和用户体验。

说说react diff的原理是什么?

React Diff 算法是用于对比新旧虚拟 DOM 树并计算最小更新操作的算法。其原理如下:

  1. 比较顶层节点:首先,React 会比较新旧虚拟 DOM 树的根节点,并确定是否为同一类型的元素。如果不是同一类型的元素,React 会直接替换整个子树。如果是相同类型的元素,React 会继续进行下一步比较。
  2. 遍历子节点:React 会遍历新旧虚拟 DOM 树的子节点,并按照顺序进行比较。React 会为每个节点分配一个唯一的 key 值(如果提供了 key 属性),这样可以更准确地确定节点的变化状态。
  3. 更新节点属性:React 对比新旧节点的属性值,判断是否需要更新属性。如果新旧节点的某个属性值不同,React 会更新该属性,从而实现属性的变更。
  4. 判断节点移动:如果新旧节点的顺序不同,React 会判断节点的移动情况。React 会使用一种启发式算法来尽量减少节点的移动次数,提高效率。算法会基于节点的 key 值、位置等因素判断节点是否需要移动。
  5. 递归处理子节点:如果当前节点有子节点,React 会递归调用 Diff 算法来比较子节点。React 会对子节点进行深度优先遍历,并使用相同的比较策略对子节点进行更新。

通过上述步骤,React Diff 算法可以高效地计算出新旧虚拟 DOM 树之间的差异,并生成最小的更新操作。这样可以减少渲染的工作量,提高应用的渲染性能。同时,Diff 算法也会尽量保持 UI 的稳定性,避免不必要的变更。

说说你对redux中间件的理解?常用的中间件有哪些?实现原理?

Redux 中间件是一个位于 Redux 核心流程中的拦截器,用于处理 action 的派发和 reducer 的响应过程。它允许开发者在 action 到达 reducer 之前或者之后执行额外的逻辑操作,例如异步请求、日志记录、错误处理等,以扩展 Redux 的功能和增强应用的可维护性。

常用的 Redux 中间件包括:

  1. Redux Thunk:用于处理异步 action 的中间件。它允许 action 创建函数返回一个函数,而不仅仅是一个普通的 action 对象。这个返回的函数可以接收 dispatch 和 getState 参数,从而可以进行异步操作,例如发送网络请求并在请求完成后再派发 action。
  2. Redux Saga:基于 Generator 函数的中间件,用于处理复杂的异步流程。它通过使用特定的语法和控制流程,实现了更易读、可测试、可维护的异步操作。Redux Saga 提供了多种效用函数来管理副作用,例如处理异步请求、定时器、WebSocket 连接等。
  3. Redux Logger:用于在控制台输出 Redux 的 action 日志的中间件。它可以帮助开发者追踪 action 的派发和状态的变化,方便调试和监测 Redux 应用的运行情况。

Redux 中间件的实现原理是基于 Redux 提供的特定接口:middleware API。一个基本的 Redux 中间件是一个函数,接收 store 的 dispatch 和 getState 方法作为参数,并返回一个函数,该函数接收 next 参数,表示接下来的中间件或者最终的 dispatch 方法。

中间件函数在内部可以对传入的 action 进行一些处理,例如修改、延迟派发等操作。然后,在恰当的时机,中间件可以选择调用 next(action) 来将 action 传递给下一个中间件或者最终的 dispatch 方法,也可以选择不调用 next(action) 来阻止 action 继续传递。

这样,多个中间件可以串联起来,形成一个处理 action 的管道。每个中间件都有机会在 action 到达 reducer 之前或者之后执行自己的逻辑。最后,派发的 action 会经过所有中间件的处理,最终到达 reducer 进行状态更新。

总的来说,Redux 中间件通过拦截和处理 action 的过程,扩展了 Redux 的能力,使开发者可以在更灵活的层面上管理应用的副作用和异步操作。

路由原理 history 和 hash 两种路由方式的特点?

路由是指根据 URL 的不同路径,显示不同的内容或执行不同的操作。在前端开发中,常见的路由方式有基于历史记录(history)和基于哈希(hash)两种。

  1. 基于历史记录(history)的路由方式:
    • 特点:基于 HTML5 提供的 history API 实现,通过修改浏览器历史记录来实现路由切换。在这种方式下,URL 不会出现 # 符号。
    • 优点:
      • 美观:URL 更加干净、直观,对用户友好。
      • 无需后端支持:服务器只需将所有路由请求都返回同一个 HTML 页面,前端负责根据 URL 解析路由并进行相应的渲染。
      • 更自由的路由控制:可以通过 pushStatereplaceState 方法动态修改 URL,实现更灵活的路由控制。
    • 缺点:
      • 兼容性:history API 在老版本浏览器中不可用,需要使用 polyfill 进行兼容。
      • 服务器配置:需要服务器配置,以确保在刷新页面时正确响应路由请求。
  2. 基于哈希(hash)的路由方式:
    • 特点:在 URL 中使用 # 符号来模拟路由。当 URL 中的哈希值发生变化时,浏览器不会向服务器发送请求,而是触发 hashchange 事件,前端根据哈希值的变化进行相应的路由处理。
    • 优点:
      • 兼容性:支持所有浏览器,包括老版本浏览器。
      • 简单易用:不需要特别的服务器配置,可以直接在前端进行开发和调试。
    • 缺点:
      • URL 不够美观:URL 中会出现 # 符号,可能不够直观和友好。
      • 有些搜索引擎可能不支持:有些搜索引擎对于哈希路由的内容可能无法正确索引,并且在分享链接时可能不太友好。

需要根据具体的项目需求和目标考虑选择合适的路由方式。一般来说,基于历史记录的路由方式是更为推荐的,因为它提供了更优雅的 URL 结构和更灵活的路由控制。但如果需要兼容老版本浏览器或者简单的项目,基于哈希的路由方式也是一种可行的选择。

什么是强缓存和协商缓存?

强缓存和协商缓存是浏览器在处理请求时使用的两种不同的缓存策略。

  1. 强缓存:
    • 强缓存是基于过期时间(Expires)或者缓存标识(Cache-Control)来确定是否使用缓存的策略。
    • 当资源的缓存过期时间未到或者缓存标识有效时,浏览器直接从本地缓存中读取资源,并且不会向服务器发起请求。
    • 强缓存可以减少请求的数量,加快页面加载速度,因为浏览器无需与服务器进行通信。
    • 常见的缓存标识包括:max-age、no-cache、no-store 等。
  2. 协商缓存:
    • 协商缓存是通过与服务器进行通信来确定是否使用缓存的策略。
    • 浏览器首先发送一个带有缓存标识的请求(如 If-Modified-Since 或 If-None-Match)到服务器。
    • 服务器会根据这些缓存标识判断资源是否有更新。
    • 如果服务器返回 304 Not Modified 状态码,则表示资源未发生变化,浏览器可以从本地缓存中读取资源。
    • 如果服务器返回新的资源或者其他状态码,浏览器需要从服务器重新获取资源。
    • 常见的缓存标识包括:Last-Modified、ETag 等。

强缓存和协商缓存可以结合使用。浏览器首先会检查强缓存,如果强缓存命中,则直接使用缓存资源;如果强缓存未命中,则浏览器发送带有缓存标识的请求进行协商缓存,服务器根据缓存标识判断是否返回新的资源。

使用强缓存和协商缓存可以有效地减少网络请求,加快页面加载速度,并且减轻服务器的负载。需要根据具体的资源特点和业务需求进行合理的缓存策略配置。

iframe有那些缺点?

尽管 `` 元素在某些情况下可以提供便利和灵活性,但它也有一些缺点,包括:

  1. 安全性问题:由于 允许在页面中嵌入其他网页内容,存在一些安全风险。例如,恶意网站可通过 将恶意内容嵌入到其他网站中,从而进行钓鱼攻击或注入恶意代码。
  2. SEO 问题:搜索引擎爬虫对 中的内容索引的能力有限。如果重要的内容被嵌套在 中,可能会导致搜索引擎无法正确索引该内容,从而影响网页的排名和可搜索性。
  3. 页面加载性能:每个 都需要发送独立的请求,这可能增加页面的加载时间。特别是当嵌套的网页比较大或者存在多个 时,会增加网络负载并降低用户体验。
  4. 无法跨域访问内容:由于浏览器的同源策略限制, `` 中的内容无法直接访问跨域的内容。这会导致一些交互和通信限制,除非使用额外的技术手段(如 postMessage)来进行跨域通信。
  5. 页面结构复杂性:使用过多的 可能导致页面结构变得复杂和混乱,增加代码维护的难度。特别是在响应式设计或移动设备上,调整和控制 中的内容尺寸可能会更加困难。

因此,在使用 时需要谨慎考虑,并根据具体的情况进行权衡。在一些情况下,可以考虑使用其他技术手段,如 AJAX 或 元素等来替代 ``。

说说你对@reduxjs/toolkit的理解?和react-redux有什么区别?

@reduxjs/toolkit 是 Redux 官方提供的一个工具包,它旨在简化 Redux 的开发流程,并提供一些常用的工具和约定,帮助开发者更高效地使用 Redux。

@reduxjs/toolkit 提供了以下主要功能:

  1. 简化的 Reducer 写法:createSlice 函数可以自动生成 Reducer 和对应的 Action Creator,减少了手动编写大量模板代码的工作。
  2. 内置的 Immer 支持:createSlice 使用 Immer 库实现了不可变更新的 Reducer,使得 Reducer 代码可以更简洁和易读。
  3. 集成的 Redux DevTools 支持:configureStore 函数自动集成了 Redux DevTools,方便开发者进行状态调试和时间旅行调试。
  4. 默认的中间件配置:configureStore 函数默认集成了 redux-thunk 中间件,可以处理异步的 Action。
  5. 优化的性能特性:createSelector 函数以及 redux-batch 中间件等都提供了一些性能优化的特性,帮助开发者避免不必要的重复渲染和触发多次 Action。

相比之下,react-redux 是一个用于在 React 应用中集成 Redux 的库。它提供了 Provider 组件用于在整个应用中注入 Redux Store,以及 connect 函数用于连接组件和 Redux Store。

react-redux 的主要功能包括:

  1. 提供 Provider 组件:将 Redux 的 Store 注入到 React 应用中,使得所有的组件都可以访问到 Redux 的状态。
  2. 提供 connect 函数:将组件与 Redux 连接起来,让组件可以访问 Redux 的状态,并通过 Action 更新状态。
  3. 提供 useSelectoruseDispatch 这样的 Hooks:方便函数式组件中使用 Redux 的状态和派发 Action。
  4. 性能优化:通过对比前后状态的变化,避免不必要的组件重新渲染。

可以说,@reduxjs/toolkit 更专注于简化 Redux 的开发流程,提供更好的开发体验;而 react-redux 则是为了方便在 React 应用中使用 Redux,提供了与 React 生态的集成。实际上,@reduxjs/toolkit 也是建议与 react-redux 一起使用,以便更好地利用 Redux 在 React 应用中的优势。

window.onload和$(document).ready?

window.onload$(document).ready() 都是用于在网页加载完成后执行 JavaScript 代码的方法。但它们有一些区别:

  1. 触发时机:
    • window.onload:当整个页面及其关联资源(如图片、样式表)都加载完成后触发。这意味着在执行 window.onload 之前,所有 DOM 元素和外部资源都已完全加载。
    • $(document).ready():当 DOM 树构建完成并且所有 DOM 元素都可以操作时触发。此时,可能还有一些外部资源(如图片)尚未加载完成。
  2. 多次触发:
    • window.onload:只能在页面加载完成时触发一次。
    • $(document).ready():可以通过多次调用来注册多个事件处理程序,每次调用都会触发相应的处理程序。
  3. 缩写方式:
    • window.onload 是原生 JavaScript 的方法。
    • $(document).ready() 是 jQuery 提供的快捷方式,需要引入 jQuery 库才能使用。

针对性能建议: 由于 $(document).ready() 在 DOM 树就绪时触发,而不是等待所有资源加载完成,因此它的触发速度较快。推荐在大部分情况下使用 $(document).ready(),因为它能在 DOM 就绪时即可执行相关 JavaScript 代码,提高用户体验。而 window.onload 将等待所有资源加载完成,包括图片等外部资源,触发时间相对较慢,适用于需要确保所有资源加载完毕后才进行操作的场景。

React性能优化的手段有哪些?

  1. 使用 PureComponent 或 shouldComponentUpdate:在组件更新时,通过重写 shouldComponentUpdate 方法或使用 React.PureComponent 来避免不必要的重新渲染。这样可以避免无效的 props 更新和状态更新,提高组件更新的效率。
  2. 使用 memo 包裹函数组件:使用 React.memo 函数包裹函数组件,以避免在相同的 props 下进行重复渲染。
  3. 使用 key 属性优化列表渲染:给列表元素添加唯一的 key 属性,帮助 React 在进行列表 diff 算法时更高效地确定新增、移动和删除的元素。
  4. 避免在 render 方法中创建新对象:避免在 render 方法中创建新的对象或函数,因为每次渲染都会生成新的引用,导致子组件不必要的重新渲染。
  5. 使用 useCallback 和 useMemo:使用 useCallbackuseMemo 钩子来缓存函数和计算结果,以避免在每次渲染时重新创建它们,提高性能。
  6. 异步渲染组件:使用 React.lazySuspense 实现组件的异步加载,使得页面渲染更流畅,减少首屏加载时间。
  7. 避免深层次嵌套的组件:过深的组件层次会增加渲染和协调的成本,尽量保持组件结构扁平化,以提高性能。
  8. 使用虚拟化技术:对于大型列表或表格数据,使用虚拟化技术(如 react-virtualized 或 react-window)只渲染可见区域的部分内容,减少渲染的数量,提高性能。
  9. 合理使用 React DevTools:React DevTools 提供了一些有用的性能分析工具,可以帮助你分析组件重渲染的原因,并找出性能瓶颈。

如何通过原生js实现一个节流函数和防抖函数?

通过原生 JavaScript 可以实现节流函数(throttle)和防抖函数(debounce)。下面是它们的实现方式:

  1. 节流函数(throttle):
javascriptCopy Codefunction throttle(func, delay) {
  let timeoutId;
  return function() {
    if (!timeoutId) {
      timeoutId = setTimeout(() => {
        func.apply(this, arguments);
        timeoutId = null;
      }, delay);
    }
  };
}

使用示例:

javascriptCopy Codefunction handleScroll() {
  console.log('Handling scroll event...');
}

window.addEventListener('scroll', throttle(handleScroll, 200));

上述 throttle 函数会在指定的延迟时间内(delay)只执行一次传入的函数(func),以减少触发频率。

  1. 防抖函数(debounce):
javascriptCopy Codefunction debounce(func, delay) {
  let timeoutId;
  return function() {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => {
      func.apply(this, arguments);
    }, delay);
  };
}

使用示例:

javascriptCopy Codefunction handleInputChange() {
  console.log('Handling input change event...');
}

const inputElement = document.getElementById('myInput');
inputElement.addEventListener('input', debounce(handleInputChange, 300));

上述 debounce 函数会在指定的延迟时间内(delay)没有新的触发时才执行一次传入的函数(func),常用于处理输入框等频繁触发事件。

这些是通过原生 JavaScript 实现的简单节流函数和防抖函数。注意,在实际开发中,也可以通过使用第三方库如 Lodash 来更方便地实现这些函数。

说说webpack中常见的loader?解决了什么问题?

在 Webpack 中,常见的 Loader 用于处理不同类型的文件,将它们转换为模块,并添加到依赖图中。它们解决了在 Webpack 打包过程中处理各种资源的问题。以下是一些常见的 Loader:

  1. Babel Loader:将 ES6+ 的 JavaScript 代码转换为浏览器可识别的 ES5 代码,以实现更好的浏览器兼容性。
  2. CSS Loader:解析 CSS 文件,并处理其中的 import 和 url() 等语法,使其可以作为模块被导入到 JavaScript 中。
  3. Style Loader:将 CSS 代码注入到 HTML 页面中,以使样式生效。
  4. File Loader:处理文件,并将文件内容复制到输出目录,并返回文件的 URL。
  5. Image Loader:加载并处理图片资源,可以进行压缩、优化、转换为 base64 等操作。
  6. URL Loader:类似于 File Loader,但更适合处理小文件,可以将文件转换为 base64 编码,并嵌入到 bundle 中,减少 HTTP 请求次数。
  7. JSON Loader:将 JSON 文件解析为 JavaScript 对象。
  8. XML Loader:将 XML 文件转换为 JavaScript 对象。
  9. CSV Loader:将 CSV 文件转换为 JavaScript 对象或数组。
  10. Font Loader:用于加载和处理字体文件,例如将字体文件转换为 base64 编码并嵌入到样式中。

这些 Loader 在 Webpack 中起到了非常重要的作用。它们可以将不同类型的文件转换为模块,使得这些资源可以与 JavaScript 代码一起打包、压缩和优化。Loader 的使用使得开发者可以更加方便地处理各种资源,并且充分利用 Webpack 的模块化能力。

说说如何借助webpack来优化前端性能?

Webpack 可以通过一系列的优化手段来提升前端性能。下面是一些常见的优化技巧:

  1. 代码压缩:使用 Webpack 的 UglifyJsPlugin 或 TerserPlugin 插件对 JavaScript 代码进行压缩,去除注释、空格和无效代码,减小文件体积,加快加载速度。
  2. Tree Shaking:利用 Webpack 的 Tree Shaking 特性,通过静态代码分析和依赖关系剔除未使用的代码,进一步减小打包后的文件体积。
  3. 懒加载:使用 Webpack 的动态 import(或 import())语法实现按需加载,将页面中不常用的代码以及异步加载的模块延迟加载,减少首次加载时要下载的资源数量,改善页面加载速度。
  4. 资源优化:利用 Webpack 的 loader 和 plugin 处理各种资源,如压缩图片、音频、视频等,减小文件体积,同时优化字体加载和缓存问题。
  5. 文件缓存:通过 Webpack 的 filename 和 chunkhash 等配置,生成带有版本号的文件名,使浏览器能够更有效地缓存文件,并在文件内容发生变化时更新缓存。
  6. 按需合并:通过 Webpack 的 SplitChunksPlugin 插件,将公共代码和第三方库单独打包成独立的文件,实现代码的合理分割和缓存复用,避免重复加载。
  7. 代码分离:使用 Webpack 的 SplitChunksPlugin 或动态 import,将应用拆分成多个模块/块(chunks),并按需加载,提高页面的并行加载性能。
  8. 缩小作用域:通过 Webpack 的 scope hoisting 特性(通过 module concatenation)减少模块函数包装代码,简化作用域链,提高执行效率。
  9. CDN 加速:结合 Webpack 使用外部 CDN 引入常用的第三方库,如 jQuery、React 等,并在 Webpack 配置中设置 externals,以减少打包体积。
  10. 缓存优化:利用 Webpack 插件如 HardSourceWebpackPlugin、DLLPlugin 等,实现缓存加速机制,加快二次构建速度。

这些是常见的借助 Webpack 进行前端性能优化的手段。通过合理的配置和使用相关插件,可以显著提升前端应用的加载速度和用户体验。

重绘和回流(重排)是什么,如何避免?

重绘(Repaint)和回流(Reflow)是浏览器渲染页面时的两个关键概念:

  1. 重绘:当元素的可见样式属性发生改变,如颜色、背景色等,但不影响其布局时,浏览器将会重新绘制这些元素,这个过程称为重绘。重绘不涉及元素的位置和尺寸变化,只需要更新样式。
  2. 回流:当页面的布局发生改变,比如元素的位置、尺寸、内容等发生变化时,浏览器需要重新计算元素的几何属性并重新构建渲染树,然后进行重绘。这个过程称为回流。回流会涉及到更大的开销,因为它需要重新计算和布局多个元素。

由于回流和重绘是非常消耗性能的操作,频繁的回流和重绘会导致页面卡顿、性能下降。为了避免这种情况,可以采取以下措施:

  1. 使用 CSS3 动画:CSS3 动画的效果是在合成线程上运行的,不会引发回流和重绘,性能比 JavaScript 实现的动画更好。
  2. 使用 translate 替代 top/left:对于需要进行动画或位移的元素,使用 CSS 的 translate 属性而不是 top 和 left 属性,因为前者只会引起重绘,而后者会引起回流。
  3. 批量修改样式:避免对样式进行单个单个的修改,而应该尽可能地批量修改样式。可以使用 CSS 类名切换、操作样式表,或者将需要修改的样式属性合并成一个样式规则来减少回流和重绘的次数。
  4. 避免频繁访问布局信息:频繁读取元素的布局信息(比如 offsetTop、offsetLeft 等)会导致浏览器进行回流。如果需要多次使用这些信息,最好将其缓存起来,以减少回流次数。
  5. 使用文档片段或离线 DOM:通过将需要多次操作的 DOM 元素先脱离文档流,进行操作后再插入文档中,可以减少回流的次数。
  6. 避免在布局抖动的元素上触发动画:如果一个元素正在频繁的回流和重绘,最好避免在其上触发动画,因为这会加剧性能问题。
  7. 使用虚拟 DOM 技术:一些框架(如 React、Vue 等)通过使用虚拟 DOM 来批量处理 DOM 的更新,最小化回流和重绘的次数,提高性能。

以上是一些常见的避免回流和重绘的方法,具体应用时可以根据实际情况选择合适的优化策略。

写一个转换数据格式方法,将对象转换为数组?

{
	key1:{
		op1:"value1",
	},
	key2:{
		op2:"value2",
	},
}
写一个转换数据格式方法,将对象转换为数组?
[
	{key:"key1",op:"op1",value:"value1"},
	{key:"key1",op:"op2",value:"value2"},
]

要将给定的对象转换为指定格式的数组,可以使用以下方法:

javascriptCopy Codefunction convertObjectToArray(obj) {
  return Object.entries(obj).flatMap(([key, value]) =>
    Object.entries(value).map(([op, value]) => ({ key, op, value }))
  );
}

const obj = {
  key1: {
    op1: "value1",
  },
  key2: {
    op2: "value2",
  },
};

const arr = convertObjectToArray(obj);
console.log(arr);

这个方法使用了 Object.entries() 方法将对象转换成可迭代的键值对数组,然后使用 flatMap() 方法遍历数组并进行扁平化操作。在内部的 map() 方法中,将每个键值对转换为指定格式的对象。

输出结果为:

javascriptCopy Code[
  { key: 'key1', op: 'op1', value: 'value1' },
  { key: 'key2', op: 'op2', value: 'value2' }
]

每个项都包含了 keyopvalue 属性,分别对应原始对象的键、嵌套对象的键和对应的值。

请注意,由于 JavaScript 中对象的属性顺序是不确定的,转换后的数组中的项也可能是无序的。如果需要保持顺序,可以使用 Map 数据结构来替代普通对象。

useEffect 的第二个参数, 传空数组和传依赖数组有什么区别?

​ 在 React 中,useEffect 是一个用于处理副作用的 Hook。它接受两个参数:一个回调函数和一个依赖数组。

  1. 传递空数组:useEffect(callback, []) 当依赖数组为空时,表示只在组件首次渲染时执行一次回调函数。这意味着 useEffect 不会对任何依赖进行跟踪,因此不会在后续渲染中再次触发回调函数。这种方式常用于执行只需要在组件挂载和卸载时执行的操作,比如监听全局事件、订阅和取消订阅等。通过传递空数组,可以确保副作用只在组件加载和卸载时进行,避免多余的执行。
  2. 传递依赖数组:useEffect(callback, [依赖]) 当依赖数组中包含依赖项时,useEffect 会在每次渲染后,仅当依赖项发生变化时才执行回调函数。这种方式用于处理基于依赖项的副作用,只在特定依赖项发生变化时才执行相应的操作。常见的用例包括发起网络请求、订阅数据更新等。同时,也可以传递多个依赖项,useEffect 会在任一依赖项发生变化时触发回调函数。

总结:

  • 传递空数组作为第二个参数,useEffect 只在组件首次渲染时执行,类似于 componentDidMountcomponentWillUnmount 的合并。
  • 传递依赖数组作为第二个参数,useEffect 会在组件首次渲染以及依赖项发生变化时执行回调函数,类似于 componentDidMountcomponentDidUpdatecomponentWillUnmount 的合并。

需要注意的是,如果省略第二个参数,则 useEffect 每次渲染都会执行回调函数。这可能导致性能问题,因此在使用 useEffect 时,请根据实际需求选择正确的依赖选项。

在 React 中,useEffect 是一个用于处理副作用的 Hook。它接受两个参数:一个回调函数和一个依赖数组。

  1. 传递空数组:useEffect(callback, []) 当依赖数组为空时,表示只在组件首次渲染时执行一次回调函数。这意味着 useEffect 不会对任何依赖进行跟踪,因此不会在后续渲染中再次触发回调函数。这种方式常用于执行只需要在组件挂载和卸载时执行的操作,比如监听全局事件、订阅和取消订阅等。通过传递空数组,可以确保副作用只在组件加载和卸载时进行,避免多余的执行。
  2. 传递依赖数组:useEffect(callback, [依赖]) 当依赖数组中包含依赖项时,useEffect 会在每次渲染后,仅当依赖项发生变化时才执行回调函数。这种方式用于处理基于依赖项的副作用,只在特定依赖项发生变化时才执行相应的操作。常见的用例包括发起网络请求、订阅数据更新等。同时,也可以传递多个依赖项,useEffect 会在任一依赖项发生变化时触发回调函数。

总结:

  • 传递空数组作为第二个参数,useEffect 只在组件首次渲染时执行,类似于 componentDidMountcomponentWillUnmount 的合并。
  • 传递依赖数组作为第二个参数,useEffect 会在组件首次渲染以及依赖项发生变化时执行回调函数,类似于 componentDidMountcomponentDidUpdatecomponentWillUnmount 的合并。

需要注意的是,如果省略第二个参数,则 useEffect 每次渲染都会执行回调函数。这可能导致性能问题,因此在使用 useEffect 时,请根据实际需求选择正确的依赖选项。

TypeScript 的内置数据类型有哪些?

在 TypeScript 中,有以下内置数据类型:

  1. 基本类型(Primitive types):
    • number:表示数字,包括整数和浮点数。
    • string:表示字符串。
    • boolean:表示布尔值,可以是 truefalse
    • null:表示空值。
    • undefined:表示未定义的值。
    • symbol:表示唯一的、不可修改的值。
    • BigInt:表示任意精度的整数。
  2. 复合类型(Composite types):
    • array:表示数组,可以使用泛型指定元素类型。
    • tuple:表示元组,表示一个已知长度和类型的数组。
    • object:表示对象,其中包含属性名和属性值。
    • enum:表示枚举类型,用于定义一组命名的常量。
    • function:表示函数类型。
  3. 其他类型:
    • any:表示任意类型,关闭类型检查。
    • void:表示没有返回值的类型。
    • never:表示永远不存在的值的类型。

此外,TypeScript 还支持自定义类型,可以通过接口(interface)、类(class)、联合类型(union type)、交叉类型(intersection type)等方式创建自己的类型。这些内置和自定义的类型可以帮助我们在开发中更好地使用 TypeScript 的类型系统,提高代码的可靠性和可维护性。

rgba()和opacity的透明效果有什么不同?

rgba()opacity 都可以用于设置元素的透明效果,但它们有一些不同之处。

  1. rgba():是一种 CSS 函数,用于设置颜色的红、绿、蓝和透明度(alpha)通道。它的语法是 rgba(red, green, blue, alpha),其中 redgreenblue 是表示红、绿、蓝通道的整数或百分比值(取值范围为0-255或0%-100%),alpha 是一个浮点数值(取值范围为0-1),表示透明度。通过调整 alpha 值,可以改变元素的透明度。

    示例:

    cssCopy Codebackground-color: rgba(255, 0, 0, 0.5); /* 设置背景颜色为红色,透明度为50% */
    
  2. opacity:是 CSS 属性,用于设置元素的整体透明度。它的取值范围是0-1,其中0表示完全透明,1表示完全不透明。通过调整 opacity 的值,可以改变元素及其内容的透明程度。opacity 属性作用于元素本身以及其所有子元素。

    示例:

    cssCopy Codeopacity: 0.5; /* 设置元素及其内容的透明度为50% */
    

不同之处:

  • rgba() 可以设置元素的背景色、边框色等具体颜色效果,并且可以单独调整透明度,不影响其他样式。
  • opacity 设置的是整个元素及其内容的透明度,会同时影响元素内部的所有子元素,无法单独控制某个元素的透明度。

因此,选择使用 rgba() 还是 opacity 取决于具体需求。如果需要单独控制某个元素的透明度,或者只想改变元素的背景色、边框色等具体颜色效果,应该使用 rgba()。而如果希望整个元素及其内容都具有相同的透明度,可以使用 opacity

TypeScript支持的访问修饰符有哪些?

在 TypeScript 中,有以下几种访问修饰符:

  1. public(默认修饰符):可以被类的实例、子类和外部访问。
  2. private:只能在定义该成员的类内部访问。
  3. protected:可以被定义该成员的类及其子类访问,但不能被外部访问。
  4. readonly:只读修饰符,用于修饰属性或参数,表示该成员只能在声明时或构造函数中被赋值,之后不可修改。
  5. static:静态修饰符,用于修饰类的成员,表示该成员属于类本身,而不是类的实例。静态成员可以通过类名直接访问,无需创建类的实例。
  6. abstract:抽象修饰符,用于修饰类、方法或属性。抽象类不能被实例化,只能被继承,并且抽象方法必须在子类中被实现。

这些访问修饰符可以用于类的属性、方法、构造函数以及存取器(getter 和 setter)。通过使用不同的访问修饰符,可以控制类的成员在类内部和外部的可访问性,提高代码的封装性和安全性。

哪些操作会造成内存泄漏?

内存泄漏是指程序中一些已经不再使用的内存没有被正确释放,导致这部分内存无法被再次利用的情况。以下是一些常见的操作可能会导致内存泄漏的情况:

  1. 意外的全局变量:如果意外地将一个对象赋值给全局变量,而忘记在后续的代码中释放它,就会导致内存泄漏。
  2. 定时器和回调函数:定时器和回调函数可能会持有对其他对象的引用,如果定时器或回调函数没有被正确清理,那么这些对象就无法被垃圾回收,从而导致内存泄漏。
  3. 未销毁的事件监听器:如果在页面中注册了事件监听器,但是忘记在元素被移除之前手动取消注册,就会导致内存泄漏。
  4. 循环引用:当两个或多个对象相互引用,并且没有其他对象引用它们时,这将形成循环引用。如果这些对象没有被及时销毁,就会导致内存泄漏。
  5. 未释放的资源:例如打开的文件或数据库连接等资源,在使用完毕后如果没有正确释放,就会造成内存泄漏。
  6. 缓存:如果长时间保留大量无用的缓存数据,而不及时清理过期或不再需要的缓存,就会导致内存泄漏。
  7. 不合理的 DOM 操作:频繁创建和删除 DOM 元素、大量使用匿名函数等操作也可能导致内存泄漏。

为避免内存泄漏,应该注意及时释放不再使用的资源、取消注册事件监听器、避免不必要的全局变量,以及避免循环引用等问题。同时,使用开发者工具进行内存分析和性能优化也是预防和解决内存泄漏的有效方法。

如何将 unknown 类型指定为一个更具体的类型?

要将 unknown 类型指定为一个更具体的类型,可以使用类型断言(Type Assertion)或类型守卫(Type Guard)。

  1. 类型断言:通过使用类型断言,可以告诉编译器将 unknown 类型视为某个更具体的类型。有两种方式进行类型断言:

    a. 尖括号语法(Angle Bracket Syntax):

    typescriptCopy Codelet value: unknown = "hello";
    let length: number = (value as string).length;
    

    b. as 语法:

    typescriptCopy Codelet value: unknown = "hello";
    let length: number = (value as string).length;
    

    在上述示例中,我们使用类型断言将 unknown 类型的变量 value 断言为 string 类型,然后可以安全地访问其 length 属性。

  2. 类型守卫:使用类型守卫可以根据特定的条件检查变量的类型,并在满足条件时将 unknown 类型缩小为更具体的类型。常见的类型守卫包括 typeofinstanceof 和自定义类型谓词函数。

    a. typeof 类型守卫:

    typescriptCopy Codefunction processValue(value: unknown) {
      if (typeof value === "string") {
        console.log(value.length);
      }
    }
    

    b. instanceof 类型守卫:

    typescriptCopy Codeclass MyClass {
      // ...
    }
    
    function processValue(value: unknown) {
      if (value instanceof MyClass) {
        // 处理 MyClass 类型的 value
      }
    }
    

    c. 自定义类型谓词函数:

    typescriptCopy Codefunction isString(value: unknown): value is string {
      return typeof value === "string";
    }
    
    function processValue(value: unknown) {
      if (isString(value)) {
        console.log(value.length);
      }
    }
    

以上是将 unknown 类型指定为更具体类型的几种常见方法。在使用这些方法时,需要注意类型的安全性,确保进行适当的类型检查和处理,以避免潜在的类型错误。

offsetWidth/offsetHeight,clientWidth/clientHeight与scrollWidth/scrollHeight的区别?

offsetWidthoffsetHeight是元素的可见宽度和高度,包括元素自身的宽度和高度、边框的宽度以及滚动条(如果存在)的宽度。这两个属性可以用来获取元素在页面中占据的空间大小,包括边框和滚动条。

clientWidthclientHeight是元素的内部宽度和高度,不包括边框和滚动条的宽度。这两个属性可以用来获取元素的内容区域的大小,即去除了边框和滚动条后的可视区域的大小。

scrollWidthscrollHeight是元素的滚动宽度和滚动高度,表示元素内容在没有使用滚动条的情况下所需要的空间大小。如果元素的内容超过了可视区域,那么可以通过滚动来查看剩余的内容。因此,scrollWidthscrollHeight属性可以用来获取元素内容的总大小,包括被隐藏在滚动视图之外的部分。

总结:

  • offsetWidthoffsetHeight:包含元素自身的宽度和高度,以及边框和滚动条的宽度。
  • clientWidthclientHeight:元素内容区域的宽度和高度,不包括边框和滚动条。
  • scrollWidthscrollHeight:元素内容的总宽度和总高度,包括被隐藏在滚动视图之外的部分。

需要注意的是,这些属性都返回的是整数值,并且可以通过JavaScript来访问和修改。在使用这些属性时,要根据具体的需求选择合适的属性来获取所需的尺寸信息。

解释一下TypeScript中的枚举?

在TypeScript中,枚举(Enum)是一种用于定义一组命名常量的数据类型。枚举为一组相关的值提供了一个友好的名称,并且这些名称可以在代码中使用。

以下是一个简单的枚举示例:

typescriptCopy Codeenum Direction {
  Up,
  Down,
  Left,
  Right
}

在这个例子中,我们定义了一个名为Direction的枚举,它包含了四个常量:UpDownLeftRight。在默认情况下,枚举成员的值从0开始自增,即Up的值为0,Down的值为1,以此类推。

枚举的使用示例:

typescriptCopy Codelet playerDirection: Direction = Direction.Up;
console.log(playerDirection);  // 输出 0

if (playerDirection === Direction.Up) {
  console.log("Player is moving up");
}

在这个示例中,我们声明了一个变量playerDirection,并将其赋值为Direction.Up。我们也可以直接使用枚举成员的值,但最好是使用枚举本身来提高代码的可读性。通过比较playerDirectionDirection.Up,我们可以执行相应的操作。

枚举还可以手动设置成员的值:

typescriptCopy Codeenum Direction {
  Up = 1,
  Down = 2,
  Left = 3,
  Right = 4
}

在这种情况下,Up的值为1,Down的值为2,以此类推。我们也可以给部分枚举成员手动设置值,剩余的成员将会自动递增。

枚举还有一些其他特性,如反向映射、字符串枚举和常量枚举等。反向映射允许通过枚举值获取对应的名称,字符串枚举允许枚举成员的值为字符串类型,而常量枚举在编译阶段会被移除,只保留枚举成员的使用处。

总结:

  • 枚举用于定义一组命名常量。
  • 枚举成员的值默认从0开始自增,也可以手动设置成员的值。
  • 枚举提供了一种更可读和可维护的方式来处理常量集合。
  • 枚举还有其他特性,如反向映射、字符串枚举和常量枚举等。

枚举在很多情况下非常有用,例如表示状态、方向、选项等。它们可以提高代码的可读性和可维护性,并减少错误。

谈谈变量提升,变量的?函数的?

变量提升是JavaScript中的一个概念,它指的是在代码执行前,变量和函数的声明会被提升到当前作用域的顶部,即在声明之前就可以使用它们。

变量提升分为两种情况:变量和函数。

  1. 变量提升(Variable Hoisting): 在JavaScript中,使用var关键字声明的变量会被提升至当前作用域的顶部。这意味着可以在变量声明之前引用该变量,但是变量的赋值操作仍然发生在原来的位置。

例如:

javascriptCopy Codeconsole.log(x);  // undefined
var x = 10;

在这个例子中,变量x的声明被提升到了作用域的顶部,所以在console.log()语句中可以引用x,但是由于赋值操作发生在原来的位置,所以输出结果是undefined

  1. 函数提升(Function Hoisting): 与变量不同,使用函数声明方式定义的函数会被整个提升到当前作用域的顶部。这意味着可以在函数声明之前调用它们。

例如:

javascriptCopy Codefoo();  // "Hello from foo!"
function foo() {
  console.log("Hello from foo!");
}

在这个例子中,函数foo()的声明被提升到了作用域的顶部,所以在函数声明之前可以调用foo(),并输出相应的结果。

需要注意的是,变量提升只会将声明提升到顶部,而不会提升赋值操作。如果只是声明了变量但没有赋值,那么在声明之前使用该变量会得到undefined。函数声明会被整体提升,包括函数的定义和函数体内的代码。

为了避免变量提升可能带来的问题,通常推荐在作用域的顶部声明所有的变量,并且在使用之前进行初始化。

总结:

  • 变量提升指的是变量和函数声明会被提升至当前作用域的顶部。
  • 使用var关键字声明的变量会被提升,但赋值操作仍然发生在原来的位置。
  • 使用函数声明方式定义的函数会被整个提升,包括函数的定义和函数体内的代码。
  • 变量提升只提升声明,不提升赋值操作。

了解变量提升对于理解JavaScript中的作用域和变量访问很重要,因此在编写代码时应该注意变量和函数的声明位置,以避免潜在的问题。

TypeScript中的方法重写是什么?

在 TypeScript 中,方法重写(Method Overriding)是一种面向对象编程的概念,它允许子类在继承父类的同时对父类中的方法进行修改或重写。通过方法重写,子类可以根据自身的需要来重新实现或修改从父类中继承的方法。

在使用方法重写时,子类需要满足以下条件:

  1. 子类必须继承父类。
  2. 子类中的方法名、参数列表和返回类型必须与父类中被重写的方法完全相同。
  3. 子类中的方法修饰符必须兼容于父类中被重写的方法,即子类中的访问修饰符要比父类中的方法更加宽松(例如,父类中的方法是protected,子类中可以是protected或者public,但不能是private)。

下面是一个简单的示例,展示了方法重写的用法:

typescriptCopy Codeclass Animal {
  move(): void {
    console.log("Animal is moving.");
  }
}

class Dog extends Animal {
  move(): void {
    console.log("Dog is running.");
  }
}

const animal: Animal = new Animal();
animal.move();  // 输出: "Animal is moving."

const dog: Dog = new Dog();
dog.move();  // 输出: "Dog is running."

在这个例子中,Animal类有一个名为move的方法,在Dog类中重写了这个方法。当调用animal.move()时,输出的结果是"Animal is moving.";而调用dog.move()时,输出的结果是"Dog is running."。因为Dog类重写了从Animal类中继承的move方法,并且方法体被修改为输出"Dog is running."

通过方法重写,子类可以根据自身的需求来修改父类中的方法实现,从而实现更加灵活和特定的行为。

需要注意的是,方法重写只影响子类的实例。父类中的方法在其他地方仍然保持不变。此外,如果希望在子类中调用父类被重写的方法,可以使用super关键字。

Umi中umijs/plugin-access插件如果使用?

umijs/plugin-access插件是 UmiJS 框架提供的一个官方插件,用于实现权限控制功能。它可以帮助你在 UmiJS 项目中轻松地进行页面和路由级别的权限管理。

要使用 umijs/plugin-access 插件,你需要按照以下步骤进行设置:

  1. 在项目中安装 umijs/plugin-access 插件:

    shellCopy Codenpm install @umijs/plugin-access --save-dev
    
  2. .umirc.tsconfig/config.ts 文件中引入和配置插件,例如:

    typescriptCopy Codeexport default {
      // ...
      plugins: ['@umijs/plugin-access'],
      access: { // 添加 access 配置
        canAdmin: true,
        canUser: false,
        // 更多权限配置...
      },
    }
    
  3. 在路由文件中定义路由规则,并使用 access 属性指定需要的权限配置,例如:

    typescriptCopy Codeimport { IRoute } from 'umi';
    
    const routes: IRoute[] = [
      {
        path: '/',
        component: '@/pages/index',
        access: 'canAdmin', // 指定需要的权限配置
      },
      // 更多路由配置...
    ];
    
    export default routes;
    
  4. 在组件或页面中使用 useAccess 钩子来获取权限信息,例如:

    typescriptCopy Codeimport { useAccess } from 'umi';
    
    const MyComponent: React.FC = () => {
      const access = useAccess();
      const { canAdmin, canUser } = access;
    
      return (
        <div>
          {canAdmin && <p>你有管理员权限</p>}
          {!canUser && <p>你没有用户权限</p>}
        </div>
      );
    };
    
    export default MyComponent;
    

通过上述步骤,你就可以在 UmiJS 项目中使用 umijs/plugin-access 插件实现页面和路由级别的权限控制了。通过配置路由的 access 属性,你可以根据自己的需求进行灵活的权限管理。同时,在组件中使用 useAccess 钩子可以获取当前页面或路由的权限信息,从而在页面中根据权限进行条件渲染或其他操作。

Typescript中 interface 和 type 的区别是什么?

在 TypeScript 中,interfacetype 都可以用来定义对象的结构或函数的类型。它们的区别在于一些语义和使用上的细微差异。

下面是 interfacetype 的区别:

  1. 语法不同: interface 使用 interface 关键字进行定义,而 type 使用 type 关键字进行定义。例如:

    typescriptCopy Codeinterface Person {
      name: string;
      age: number;
    }
    
    type PersonType = {
      name: string;
      age: number;
    };
    
  2. 可合并性不同: 当定义同名的 interfacetype 时,interface 会自动合并属性,而 type 会报错。这意味着你可以通过多次声明同一个 interface 来扩展它的属性,但是在 type 中是不允许的。例如:

    typescriptCopy Codeinterface A {
      name: string;
    }
    
    interface A {
      age: number;
    }
    // 合并后的 A 接口为 { name: string; age: number; }
    
    type B = {
      name: string;
    };
    
    type B = {
      age: number;
    };
    // 报错: "B" 已经定义了。
    
  3. 使用场景略有不同: 虽然 interfacetype 在功能上大部分重叠,但是它们在使用场景上有一些差异。一般来说,interface 更适合用于描述对象的形状(用于声明类、接口等),而 type 更灵活,可以表示联合类型、交叉类型等复杂的类型。例如:

    typescriptCopy Code// interface
    interface Point {
      x: number;
      y: number;
    }
    
    interface User {
      name: string;
      age: number;
    }
    
    // type
    type PointType = {
      x: number;
      y: number;
    };
    
    type UserType = {
      name: string;
      age: number;
    } | null;
    

总结来说,interfacetype 在大部分情况下可以互换使用,选择使用哪个要根据自己的需求和代码风格来决定。对于大多数简单的对象结构,使用 interface 是一种常见做法;而对于复杂的类型定义或需要使用联合类型、交叉类型等高级特性时,可以选择使用 type 来实现更灵活的类型定义。

Umi路由跳转传参方式都有哪些?

在 UmiJS 中,有多种方式可以进行路由跳转并传递参数。下面列举了几种常见的方式:

  1. URL 参数: 你可以将参数直接作为 URL 的一部分,使用 history.push 或 `` 组件进行跳转。例如:

    typescriptCopy Codeimport { history } from 'umi';
    
    // 使用 history.push 跳转并传递参数
    history.push('/user?id=123');
    
    // 或者使用 Link 组件
    import { Link } from 'umi';
    
    <Link to="/user?id=123">跳转到用户页面</Link>
    

    在目标页中,你可以通过 this.props.location.query 或者 useLocation 钩子来获取 URL 参数的值。

  2. 路径参数(动态路由): 你可以在定义路由规则时,使用冒号(:)来指定参数的占位符,然后在跳转时将参数传递给占位符。例如:

    typescriptCopy Codeconst routes = [
      {
        path: '/user/:id',
        component: '@/pages/User',
      },
    ];
    

    使用 history.push 或 `` 组件时,将参数传递给占位符。例如:

    typescriptCopy Codeimport { history } from 'umi';
    
    // 使用 history.push 跳转并传递参数
    history.push('/user/123');
    
    // 或者使用 Link 组件
    import { Link } from 'umi';
    
    <Link to="/user/123">跳转到用户页面</Link>
    

    在目标页中,你可以通过 this.props.match.params 或者 useParams 钩子来获取路径参数的值。

  3. query 对象: 你还可以在跳转时使用对象来传递参数,不管是使用 history.push 还是 `` 组件。例如:

    typescriptCopy Codeimport { history } from 'umi';
    
    // 使用 history.push 跳转并传递参数
    history.push({
      pathname: '/user',
      query: { id: '123' },
    });
    
    // 或者使用 Link 组件
    import { Link } from 'umi';
    
    <Link to={{ pathname: '/user', query: { id: '123' } }}>跳转到用户页面</Link>
    

    在目标页中,你可以通过 this.props.location.query 或者 useLocation 钩子来获取 query 参数的值。

无论哪种方式,目标页中都可以通过 this.props.location 或者 useLocation 钩子来获取传递的参数信息。注意,如果你在配置路由时使用了 exact 属性,则需要确保路由完全匹配才能进行跳转。

以上是 UmiJS 中常用的几种路由跳转传参的方式。根据你的需求和场景,选择适合的方式进行参数传递即可。

Js中浅拷贝和深拷贝有什么区别,如何实现?

在 JavaScript 中,浅拷贝和深拷贝是两种不同的对象复制方式。

浅拷贝是指创建一个新对象,新对象的属性值是原始对象的引用。换句话说,浅拷贝只复制了对象的一层属性,如果原始对象的属性是引用类型,那么浅拷贝后的新对象仍然会引用同一块内存。因此,当原始对象的属性值发生改变时,浅拷贝后的新对象也会受到影响。

深拷贝是指创建一个新对象,新对象及其所有嵌套的对象属性都是全新的,互不影响。深拷贝会递归地复制对象的所有属性,包括嵌套的引用类型。即使原始对象的属性值发生改变,深拷贝后的新对象也不会受到影响。

下面介绍几种常见的实现浅拷贝和深拷贝的方法:

浅拷贝

  1. 使用 Object.assign(): Object.assign() 方法可以将源对象的可枚举属性浅拷贝到目标对象中,并返回目标对象。例如:

    javascriptCopy Codeconst source = { name: 'Alice', age: 25 };
    const target = Object.assign({}, source);
    
  2. 使用展开运算符(…): 展开运算符可以将对象的可枚举属性浅拷贝到新对象中。例如:

    javascriptCopy Codeconst source = { name: 'Alice', age: 25 };
    const target = { ...source };
    
  3. 使用数组的 slice() 方法: 如果要复制一个数组,也可以使用数组的 slice() 方法进行浅拷贝。例如:

    javascriptCopy Codeconst source = [1, 2, 3];
    const target = source.slice();
    

深拷贝

  1. 使用 JSON 对象的序列化和反序列化: JSON.stringify() 方法将对象序列化为 JSON 字符串,JSON.parse() 方法将 JSON 字符串解析为 JavaScript 对象。通过这两个方法的组合,可以实现深拷贝。但是要注意,该方法有一些限制,例如无法复制函数、正则表达式等特殊对象。例如:

    javascriptCopy Codeconst source = { name: 'Alice', age: 25 };
    const target = JSON.parse(JSON.stringify(source));
    
  2. 使用第三方库: 也可以使用一些第三方库来实现深拷贝,例如 Lodash 的 cloneDeep() 方法。这些库通常提供了更强大和灵活的深拷贝功能,能够处理各种特殊情况。例如:

    javascriptCopy Codeconst source = { name: 'Alice', age: 25 };
    const target = _.cloneDeep(source);
    

需要根据具体的需求选择合适的拷贝方式。浅拷贝适用于简单对象或数组的复制,而深拷贝适用于需要完全独立的对象副本的情况。注意,在处理含有循环引用的对象时,深拷贝可能会导致无限递归,因此要谨慎使用。

为什么不能在循环、条件或嵌套函数中调用 Hooks?

在 React 中,Hooks 是一种用于在函数组件中添加状态和其他 React 特性的方式。Hooks 的使用有一些规则限制,其中一个重要的规则是:Hooks 只能在 React 函数组件的顶层调用,不能在循环、条件或嵌套函数中调用

这个限制是由 React 的设计决策和内部实现所决定的,主要有两个原因:

1. Hooks 依赖于调用顺序: Hooks 的状态和副作用是通过存储在闭包中的特殊变量来实现的,并且这些变量的顺序和调用顺序有着严格的对应关系。如果在循环、条件或嵌套函数中调用 Hooks,就无法保证它们的调用顺序是稳定且一致的,可能会导致状态错乱、副作用执行不正确等问题。

2. Hooks 需要与渲染阶段同步: React 在渲染组件时会按照特定的顺序调用 Hooks,并确保每次渲染时都按照相同的顺序调用相同的 Hooks。这样可以在每次渲染时正确地捕捉和管理组件的状态。而在循环、条件或嵌套函数中调用 Hooks,可能会导致 Hooks 的调用与渲染阶段不同步,从而破坏 React 内部的机制。

为了解决这个问题,可以采用一些方法来满足 Hooks 的规则要求:

  • 循环中的 Hook 调用问题: 如果需要在循环中使用 Hooks,可以将循环放在 Hook 的调用之前。例如,可以使用 map 方法创建一个包含 Hook 调用的数组,在循环之后将其返回。
  • 条件语句中的 Hook 调用问题: 如果需要在条件语句中使用 Hooks,可以将条件移到 Hook 调用之前。例如,可以使用三元表达式或短路表达式确保在条件满足时调用 Hooks。
  • 嵌套函数中的 Hook 调用问题: 如果需要在嵌套函数中使用 Hooks,可以将嵌套函数提取到组件的顶层,并确保在顶层函数中调用 Hooks。

总之,为了确保 Hooks 的正确使用,应该遵守 React 官方文档中对 Hooks 的规则和约定,并注意在何时何地调用 Hooks。这样可以避免潜在的问题,确保组件的状态和副作用的正确管理。

React有哪些性能优化的方法?

  1. 使用 PureComponent 或 shouldComponentUpdate:在组件更新时,通过重写 shouldComponentUpdate 方法或使用 React.PureComponent 来避免不必要的重新渲染。这样可以避免无效的 props 更新和状态更新,提高组件更新的效率。
  2. 使用 memo 包裹函数组件:使用 React.memo 函数包裹函数组件,以避免在相同的 props 下进行重复渲染。
  3. 使用 key 属性优化列表渲染:给列表元素添加唯一的 key 属性,帮助 React 在进行列表 diff 算法时更高效地确定新增、移动和删除的元素。
  4. 避免在 render 方法中创建新对象:避免在 render 方法中创建新的对象或函数,因为每次渲染都会生成新的引用,导致子组件不必要的重新渲染。
  5. 使用 useCallback 和 useMemo:使用 useCallbackuseMemo 钩子来缓存函数和计算结果,以避免在每次渲染时重新创建它们,提高性能。
  6. 异步渲染组件:使用 React.lazySuspense 实现组件的异步加载,使得页面渲染更流畅,减少首屏加载时间。
  7. 避免深层次嵌套的组件:过深的组件层次会增加渲染和协调的成本,尽量保持组件结构扁平化,以提高性能。
  8. 使用虚拟化技术:对于大型列表或表格数据,使用虚拟化技术(如 react-virtualized 或 react-window)只渲染可见区域的部分内容,减少渲染的数量,提高性能。
  9. 合理使用 React DevTools:React DevTools 提供了一些有用的性能分析工具,可以帮助你分析组件重渲染的原因,并找出性能瓶颈。

Webpack有哪些核心概念?

Webpack 是一个现代化的前端打包工具,用于将多个模块打包成一个或多个静态资源文件。Webpack 的核心概念有以下几个:

  1. 入口 (Entry):指定 Webpack 构建的入口文件,Webpack 从入口文件开始递归地解析依赖,构建整个应用程序的依赖图。
  2. 出口 (Output):指定 Webpack 构建生成的静态资源文件的输出目录和文件名。输出目录可以是绝对路径或相对于项目根目录的相对路径。
  3. 加载器 (Loader):Webpack 将所有非 JavaScript 模块视为资源,通过加载器将它们转换成可被 Webpack 处理的有效模块。例如,通过使用 Babel-Loader 可以将 ES6/ES7 的 JavaScript 代码转换为兼容性更好的 ES5 代码。
  4. 插件 (Plugin):插件是用于增强 Webpack 功能的扩展,可以用于执行各种自定义任务,例如优化资源、注入环境变量、生成 HTML 文件等。常见的插件包括 HtmlWebpackPlugin、CleanWebpackPlugin、MiniCssExtractPlugin 等。
  5. 模式 (Mode):Webpack 提供了不同的模式,如开发模式(development)、生产模式(production)和 none 模式。模式会启用相应的内置优化功能,如压缩代码、tree-shaking、作用域提升等。
  6. 代码分割 (Code Splitting):Webpack 支持将代码分割成多个输出文件,以便在运行时按需加载。这有助于优化应用程序的加载性能,并减小打包后的文件大小。
  7. 缓存 (Caching):Webpack 通过生成文件的哈希值来实现缓存机制,如果文件内容没有发生变化,则不会重新生成该文件,而是使用缓存结果,提高构建效率。
  8. 优化 (Optimization):Webpack 内置了一些优化功能,如压缩 JavaScript、压缩 CSS、自动拆分代码等。此外,开发人员还可以通过配置自定义优化规则,以满足特定的需求。

以上是 Webpack 的核心概念,了解并正确使用这些概念可以帮助开发人员更好地配置和管理前端项目的构建过程。

常用的hooks都有哪些,说出他们的作用,最少列出来5个?

以下是五个常用的 React Hooks,以及它们的作用:

  1. useState:useState 是最基本和常用的 Hook 之一,用于在函数组件中添加状态。它接收一个初始值,并返回一个包含当前状态值和更新状态值的数组。可以通过调用返回的函数来修改状态值,并且每次修改状态值时,组件将重新渲染。
  2. useEffect:useEffect 用于在组件渲染完成后执行副作用操作,比如发送网络请求、订阅事件、修改 DOM 等。它接收一个回调函数,在每次组件渲染后执行,并可以通过返回一个清理函数来清理副作用。
  3. useContext:useContext 用于在组件中访问上下文(Context)的值。它接收一个 Context 对象,并返回当前上下文的值。通过使用 useContext,可以避免使用嵌套的 Context.Provider 组件来传递数据。
  4. useReducer:useReducer 是另一种管理组件状态的 Hook,它类似于 Redux 中的 reducer。useReducer 接收一个 reducer 函数和初始状态,并返回当前状态和一个 dispatch 函数。dispatch 函数用于触发 reducer 处理逻辑,从而修改状态。
  5. useCallback:useCallback 用于优化性能,避免不必要的函数重新创建。它接收一个回调函数和一个依赖数组,并返回一个 memoized 的回调函数。只有当依赖数组中的值发生变化时,才会重新创建回调函数。适用于将回调函数作为 props 传递给子组件,并确保子组件不会不必要地重新渲染。

以上是常用的五个 React Hooks,它们可以帮助开发人员更方便地管理组件状态、执行副作用操作和优化性能。当然,还有其他很多有用的 Hooks,如 useContext、useMemo、useRef 等,开发人员可以根据实际需求选择使用。

Typescript中泛型是什么?

在 TypeScript 中,泛型(Generics)是一种参数化类型的机制,用于在定义函数、类和接口时,指定一种可变类型,使其可以适用于多种数据类型而不是特定的数据类型。

使用泛型可以增加代码的灵活性和可重用性,它使函数或类能够适用于不同类型的数据,而不需要重复编写类似的代码。通过在定义时使用类型参数,可以在函数或类内部引用这些类型参数,并将它们作为一种占位符类型在多个地方使用。

在函数中,泛型可以通过在函数名后面使用尖括号 <> 来声明,接着用一个大写字母开头的标识符来表示类型参数。例如:

typescriptCopy Codefunction identity<T>(arg: T): T {
  return arg;
}

上述的 T 就是一个泛型类型参数,它可以代表任意类型。在这个例子中,identity 函数接收一个参数 arg,并将其原封不动地返回,其返回类型也是 T 类型。

除了函数,泛型还可以在类和接口中使用。通过在类名或接口名后面使用尖括号 <> 来声明泛型参数,然后在类或接口内部使用该泛型参数。例如:

typescriptCopy Codeclass Box<T> {
  private value: T;

  constructor(value: T) {
    this.value = value;
  }

  getValue(): T {
    return this.value;
  }
}

在上述的 Box 类中,T 是一个泛型类型参数,表示 Box 类可以包含任意类型的值。通过 value 成员变量和 getValue 方法,我们可以存储和获取相应的值。

通过使用泛型,我们可以编写更加通用和可复用的代码,提高代码的灵活性和类型安全性。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鋜斗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值