菜鸟前端面试记录U·ェ·U
介绍自己
面试官你好,我叫任蓉,来自四川南充,目前研三,就读于重庆邮电大学软件工程专业,我这次应聘的岗位是前端开发工程师的岗位。
在本科阶段我主修了软件工程、数据结构、计算机网络等专业课程。在研究生阶段,我的研究方向为计算机视觉,目前已经发表一篇EI论文,另外还有一篇中文核心在投。
为了毕业进入前端的行业,我自学了前端的相关基础知识,并且拥有两段相关实习经历,第一段是在同程旅行公司的实习,在这段实习中,我主要参与了Martix
项目管理系统的迭代开发和轻任务多维数据平台的开发这两个项目。在Martix
项目的开发过程中,我主要负责实现通过qiankun
搭建微前端应用,以及完成子应用的任务管理界面和甘特图功能、文件上传这一块需求开发,在轻任务多维数据这一块主要负责个人代办事项和团队待办事项的开发,以及设计导入模板。
项目采用了模块化的设计和Websocket通信,提供强大的实时更新和协作能力,支持多种视图类型,包含个人待办事项、团队待办事项和模板库,通过引入自定义待办事项和丰富的模板库,提升用户在项目管理、数据分析和团队协作方面的效率。
我的第二段实习是在完美世界公司,主要负责游戏AI-Partner心动剧场国际版的前端开发,在这个项目中,主要通过uniapp实现跨端开发,适配于PC端和H5界面,PC端是自己重新实现了一套VUE文件,这个项目是结合智能AI技术和创建的NPC互动的聊天游戏,为了收集用户的喜好,然后手动实现了一部分的埋点操作。在此期间,我也负责了这个游戏后台管理系统和灵思剧场小程序的开发,后台管理系统主要是管理白名单、黑名单,对游戏玩家和NPC角色违规行为的处理,比如一些言论之类的。灵思剧场小程序是AI-Partner的衍生有声小说。
通过这两段实习经历,我不仅熟悉了与后端的协同工作流程,还学到了如何高效解决项目开发过程遇到的困难,还培养了良好的沟通能力。我相信,这些宝贵的经验将为我未来的职业发展奠定坚实基础,并帮助我在新团队中快速融入并作出贡献。
以上是我的自我介绍,非常希望能够加入贵公司为公司创造一份价值。
完美世界
开发AI-Partner心动剧场国际版,融合AI技术与社交互动,有效提升了平台的用户活跃度和充值率,显著增强了用户留存率和整体平台收益。
开发运营后台,实现用户和NPC、权限和内容配置等模块的管理,支持封号、申诉、公告发布等功能,提升了平台的运营效率和管理便捷性。
开发灵思剧场小程序,通过解析剧本动态展示有声小说章节内容,并通过AI动态聊天功能增强用户互动和沉浸感。
参与完美AI策略聚合平台的开发,整合OpenAI等大语言模型,提供AI对话、绘图和语音生成功能。负责智能对话模块,集成非流式和流式GPT策略,开发策略创建和管理功能。
AI-Partner
AI-Partner
心动剧场是一款面向海外市场的AI聊天游戏,支持PC
端和H5
端,结合先进的AI技术与丰富的社交互动功能。用户通过谷歌邮箱注册,创建和管理由GPT、BERT
等AI模型驱动的NPC
角色,进行个性化互动。游戏中设计了排行榜、动态主题切换、聊天背景音乐等功能,并集成了Solana区块链
的USDT支付
,支持用户进行虚拟货币的充值和购买。项目通过创新的社交互动设计,成功吸引了大量海外用户,显著提升了用户粘性和系统性能。
谷歌注册-OAuth2.0
OAuth 2.0是一种授权框架,允许第三方应用访问用户的资源,而不暴露用户的凭证。它通过授权码和令牌的方式进行交互。
在 Google 登录流程中,通过 Google API控制台创建应用并获取 client_id,然后使用 Google 提供的 JavaScript 库通过 OAuth 2.0 授权机制获取用户的身份验证凭证(credential)。
设置了适当的回调 URI,确保用户登录成功后能够安全地重定向回应用程序,并使用令牌验证用户的身份。
如何在前端实现 Google 登录的?
使用 Vue.js 框架来构建用户界面。在页面的 中,创建一个按钮,并使用 Google 提供的 window.google.accounts.id.renderButton 方法进行渲染,使用户能够通过点击该按钮触发 Google 登录。
我在 onMounted 钩子中调用 window.google.accounts.id.initialize 初始化 Google 登录服务,并设置 client_id 和回调函数 handleCredentialResponse,以便在用户登录后处理身份验证响应。
点击按钮后,会触发 window.google.accounts.id.prompt() 方法,这会弹出 Google 登录窗口让用户进行身份验证。
如何在后端处理 Google 登录的?
用户登录后,Google 会返回一个身份验证令牌(JWT)。将这个令牌通过 API 请求发送到后端服务器,服务器会验证该令牌的有效性,并从中提取用户信息。
在后端,我使用 Google 的令牌验证 API 或 JWT 解码库来验证令牌的签名和有效性,确保令牌是来自 Google 的合法请求。
验证通过后,我会在服务器端创建用户的 session 或生成一个应用程序的访问令牌(token),并将其返回给前端,存储在setStorageSync。
用户体验和界面设计
问题:你在设计登录流程时是如何考虑用户体验的?
我确保 Google 登录按钮在页面上清晰可见,并且通过 window.google.accounts.id.renderButton 方法选择合适的样式(如 pill 形状、图标等),提高用户的视觉感受。
为了减少用户登录的步骤,我设置了 auto_select 为 false,并确保在用户登录后立即重定向回原始页面,提升流畅的体验。
我还考虑到多设备兼容性,确保按钮在移动设备和桌面设备上都能正常显示和操作。
如何测试 Google 登录功能的?部署时考虑了哪些问题?
我编写了单元测试来验证登录的核心逻辑,并通过模拟 Google API 响应来测试回调函数的正确性。
我使用集成测试确保登录流程在前后端交互中能够正常工作,同时使用真实环境中的 Google API 测试登录流程的完整性。
部署时,我确保所有的 OAuth 2.0 配置(如重定向 URI 和 client_id)在生产环境中正确配置,并设置了错误日志记录,以便监控任何登录失败或 API 调用异常。在开发过程中遇到的最大挑战是什么?你是如何解决的
我遇到的最大挑战是处理跨域资源共享(CORS)的问题。当后端和前端不在同一域时,Google 返回的令牌可能会被阻止。
为了解决这个问题,我在后端配置了 CORS 规则,允许特定的前端域访问 Google 登录 API,并确保在安全范围内的跨域访问。
这个过程如何保证数据的安全?
使用HTTPS;token合理存储:set-cookie: httpOnly;token设置过期时间,通过解析token的第二部分payload部分获取日期判断过期了没有
如何判断设备是什么平台
项目使用的Uniapp框架
进行开发,因此使用的条件编译-ifdef H5
,不使用Uniapp
开发的可以通过 user-agent
来判断平台类型。
#ifdef
和#ifndef
:在编译期间,用于选择性包含代码块。根据目标平台,如 H5、小程序、App 等,决定哪些代码应该被编译和执行。这里的#ifdefH5
通过编译时的宏指令,仅在 Web 浏览器环境下执行。类似的编译指令也可以用于其他平台,如原生应用、微信小程序等。这种方法有助于在跨平台开发时有针对性地优化每个平台的功能和性能。
主题切换
css+var定义的
CSS 变量 --background-color
和 --text-color
的值
聊天页面跳转动画实现–基于原生css实现的动画过程-单例模式
动画是基于原生的javascript实现的,没有使用第三方动画库,减少项目的体积,更加轻量,比较精确控制动画的细节(位置、尺寸、透明度、速度等),负责实现某社交平台的发现页到聊天页面跳转的动画效果。用户点击发现页卡片时,卡片从点击位置动态展开,并过渡到全屏的聊天页面,形成自然流畅的动画体验。
Vue 动态渲染:通过
createVNode
和 render 实现 Vue组件的动态挂载,实时渲染卡片内容,并与用户交互。动画容器创建:使用 JavaScript 动态创建全屏覆盖的容器,并通过精确控制卡片的位置和尺寸,确保动画效果的流畅和一致性。
CSS 动画与 JavaScript 结合:动画主要通过 JavaScript 操控 DOM 元素的位置与大小,同时结合 CSS 过渡效果,增强用户体验。
单例模式:通过单例模式管理动画实例,确保在同一时刻只存在一个动画效果,避免重复创建动画实例导致性能下降和页面冲突。
使用requestAnimationFrame
对于需要精确控制每一帧的动画场景,是一种高效的方式,来控制每一帧的渲染,避免不必要的性能开销。
为什么不使用setTimeout
?
requestAnimationFrame
- 自动同步帧率:
requestAnimationFrame
会根据显示器的刷新率(通常是 60Hz,即每秒 60 帧)来调用回调函数。这意味着动画的帧率会自动与显示器的刷新率同步,从而提供更流畅的动画效果。 - 流畅性:动画的更新频率与浏览器的重绘频率一致,避免了帧率不一致导致的卡顿或跳帧现象。
- 后台标签页优化:当页面处于后台标签页时,requestAnimationFrame 会暂停调用回调函数,从而节省 CPU 和电量。这是由浏览器自动处理的,无需开发者额外配置。
- 自动暂停:在页面不可见时,动画会自动暂停,避免了不必要的资源消耗。
- 布局计算优化:
requestAnimationFrame
会在浏览器完成所有的布局计算和重绘操作之后再执行回调函数,从而避免不必要的布局抖动和重绘。 - 性能优化:由于回调函数在浏览器的重绘周期内执行,可以有效减少布局和重绘的次数,提高性能。
- 简化动画循环:通过递归调用
requestAnimationFrame
,可以轻松实现动画循环,无需手动计算下一帧的时间间隔。
setTimeout
不受帧率控制:setTimeout 只能设置一个固定的时间间隔(例如每 16.67 毫秒更新一次,以达到每秒 60 帧),但它无法确保回调函数会在浏览器的重绘周期内执行。
可能导致卡顿:由于浏览器的重绘频率和 setTimeout 的执行频率不一致,可能会导致动画卡顿或不流畅。
持续执行:即使页面处于后台标签页,setTimeout 仍会继续执行回调函数,导致不必要的资源消耗。
手动处理:需要开发者手动检测页面的可见性,并在页面不可见时暂停或停止动画。
可能导致布局抖动:setTimeout 的回调函数可能在浏览器的布局计算和重绘操作中途执行,导致额外的布局计算和重绘,影响性能。
手动计算时间间隔:需要手动计算下一帧的时间间隔,并在回调函数中使用 setTimeout 设置下一次执行的时间。
虽然 setTimeout 也可以用来实现动画,但
requestAnimationFrame
提供了更高效和流畅的动画解决方案。它通过帧率同步、节省资源、避免布局抖动和简化动画循环,使得动画更加自然和高效。因此,在大多数情况下,requestAnimationFrame
是实现网页动画的更好选择
为什么需要使用 SSE 和 WebSocket 两种方法进行流式传输?
不同的应用场景需求:
SSE(Server-Sent Events)
—fetch
单向数据推送:适用于服务器需要向客户端推送更新的场景,比如新闻推送、社交媒体通知、实时数据更新等。
简单实现:SSE 基于 HTTP 协议,客户端与服务器之间的连接管理相对简单,自动处理重连。
轻量级:由于只需要服务器推送数据到客户端,SSE 在资源消耗和实现复杂度上都较低,适合那些无需复杂双向通信的场景。
WebSocket
双向通信:适用于需要实时双向交互的场景,比如实时聊天、在线游戏、协同编辑等。
低延迟:WebSocket 提供了更低的延迟,因为它在初次握手之后会保持一个持续的连接,适合需要高实时性的数据传输。
数据类型丰富:支持传输文本和二进制数据,能够满足更多样化的数据传输需求。
实现和维护的复杂度:
SSE
- 实现简单:SSE 只需服务器端推送,客户端接收并处理消息,适合开发周期短、维护成本低的项目。
- 内置自动重连:SSE 内置了自动重连机制,无需额外的心跳检测和重连逻辑,简化了开发。
WebSocket
- 实现复杂:WebSocket 需要处理连接的建立、维护和关闭,需实现心跳机制和重连策略,适合对实时性和稳定性要求较高的应用。
- 资源占用:由于 WebSocket 连接会保持长时间的持续连接,对服务器资源的管理要求较高。
兼容性和环境限制:
SSE
- 兼容性好:SSE 在现代浏览器中有良好的兼容性,特别适用于 HTTP/2 协议的支持。
- 微信小程序环境:在微信小程序等特定环境中,SSE 的实现和使用可能更加方便和稳定。
WebSocket
- 现代化特性:WebSocket 是现代浏览器和应用中的标准协议,在需要复杂交互的应用中表现出色。
- 适应性强:在需要高频率双向数据交换的场景中,WebSocket 能提供更好的用户体验和性能。
用户体验和性能优化:
SSE
- 低频率推送:适用于更新频率较低的场景,可以减少不必要的资源消耗,提升应用性能。
- 用户体验:对于一些只需服务器端推送数据的应用,SSE 提供了足够的实时性和可靠性。
WebSocket
- 高频率双向交互:适用于高频交互的场景,提供更好的实时性和互动性,增强用户体验。
- 性能优化:通过保持长连接,减少了频繁建立连接的开销,提升了数据传输效率。
总结:
在我们的应用中,同时使用 SSE 和 WebSocket 是基于不同场景和需求的合理选择。SSE 适用于简单的单向数据推送,具有实现简单、资源消耗低的优点。而 WebSocket 则适用于需要低延迟双向通信的复杂场景,能够提供更好的实时性和交互性。通过合理选择和组合这两种技术,我们能够在不同的应用场景中实现最佳的用户体验和性能优化。
流式输出结合webSocket心跳机制
使用的是SSE--Uniapp
和Websocket--PC
- SSE:适用于服务器向客户端推送数据的场景,使用简单,基于HTTP协议,自动重连,但只能传输文本数据,通信是单向的。基于
uni-app
和fetch-event-source
的请求封装,支持普通 HTTP 请求、文件上传、流式请求(使用 SSE),并处理了一些常见的请求逻辑,如鉴权、错误处理、登录跳转等。 - WebSocket:适用于需要低延迟双向通信的实时应用,可以传输文本和二进制数据,全双工通信,但实现相对复杂,且需要处理连接的管理和维护。
剧情聊天推送使用的是SSE,
事件
- onopen
- onmessage
- onclose
- onerror
方法
- send
- close
webSocket心跳机制
SSE和websocket的区别
SSE(Server-Sent Events)和 WebSocket 都是用于在客户端和服务器之间进行双向通信或实时数据传输的技术,但它们有一些关键的区别。下面我从多个角度详细解释这两者的差异:
1. 通信方向
SSE (Server-Sent Events):
- 单向通信:服务器向客户端推送数据,但客户端无法通过同一连接发送数据到服务器。
- 客户端通过
EventSource
与服务器建立连接,服务器可以持续推送数据到客户端。
WebSocket:
- 双向通信:客户端和服务器可以通过同一个 WebSocket 连接相互发送数据。既可以从服务器推送数据到客户端,也可以从客户端发送数据到服务器。
2. 协议层面
SSE:
- 使用的是 HTTP 协议,具体是 HTTP/1.1 长连接。建立连接后,服务器可以不断发送数据,但这个连接仍然是单向的(服务器到客户端)。
- SSE 在 HTTP 请求的头部声明
text/event-stream
来表示这是一个持续发送事件的连接。
WebSocket:
- 使用的是 独立的 WebSocket 协议。WebSocket 是在 HTTP 握手之后升级协议,从 HTTP 切换到 WebSocket 协议。
- 握手成功后,WebSocket 在底层是基于 TCP 的全双工通信。
3. 连接特性
SSE:
- 单向连接:只能由服务器推送事件到客户端。
- 自动重连:SSE 内建支持自动重连机制,当连接断开时,浏览器会自动尝试重新连接。
- Event ID 支持:SSE 可以追踪事件的 ID,如果连接断开,可以从最后一个 ID 继续接收数据,确保不会丢失消息。
WebSocket:
- 全双工连接:支持双向实时通信,客户端和服务器都可以通过同一连接发送和接收消息。
- 需要手动处理重连:如果 WebSocket 连接断开,开发者需要手动处理重连逻辑。
4. 性能与复杂度
SSE
- 轻量级:由于是基于 HTTP 长连接的单向通信,SSE 的实现相对简单,特别适合服务器频繁向客户端推送更新的场景。
- 消息格式:SSE 发送的消息是文本格式(
text/event-stream
),比较简单,不支持二进制数据。 - 适合推送数据的场景:如社交媒体通知、股票价格更新、实时消息推送等。
WebSocket
- 更复杂但更灵活:支持双向通信,同时可以发送文本和二进制数据,适合需要高频交互的场景。
- 消息格式:支持传输二进制数据和文本数据,因此适合需要传输大量数据的场景(如游戏、图像、视频流等)。
- 适合实时互动场景:如在线游戏、聊天应用、股票交易平台等,客户端和服务器频繁需要双向通信的场景。
5. 浏览器支持
SSE:
- 原生支持度较好:大部分现代浏览器都支持 SSE,且兼容性较好(IE 不支持,但 Edge 支持)。
WebSocket:
- 浏览器支持广泛:几乎所有现代浏览器都支持 WebSocket。
6. 使用场景总结
SSE:
- 适用于服务器单向推送数据的应用场景,例如:
- 实时新闻更新
- 股票行情
- 社交网络通知
- 不适合需要客户端发送大量数据或需要双向实时通信的场景。
- 适用于服务器单向推送数据的应用场景,例如:
WebSocket:
- 适用于双向实时通信的场景,例如:
- 在线多人游戏
- 实时聊天应用
- 视频会议或数据密集型应用
- 适用于双向实时通信的场景,例如:
7. 连接数量的处理
SSE:
- 每个客户端与服务器之间使用一个 HTTP 长连接。对于需要处理大量客户端连接的服务器而言,相对来说负担较小。
WebSocket:
- WebSocket 是基于 TCP 连接的,因此服务器需要为每个客户端维护一个独立的 TCP 连接。如果连接数巨大,可能会加重服务器的负担,尤其是没有正确优化时。
8. 浏览器 API
SSE:
客户端代码通常使用
EventSource
对象来处理 SSE:1
2
3
4const eventSource = new EventSource('/events');
eventSource.onmessage = function(event) {
console.log(event.data);
};
WebSocket:
客户端代码通常使用
WebSocket
对象来处理 WebSocket 通信:1
2
3
4
5const socket = new WebSocket('ws://localhost:8080');
socket.onmessage = function(event) {
console.log(event.data);
};
socket.send('Hello Server!');
总结:
- 如果只需要 服务器向客户端发送数据,而不需要客户端频繁发回数据,SSE 是较为简单且合适的选择。
- 如果需要 双向实时通信,例如在线聊天或游戏,WebSocket 是更灵活且功能更强大的选择。
WebScoket协议的HTTP头部字段
在 WebSocket 协议的握手阶段,会涉及多个 HTTP 头部字段,这些字段用于在客户端和服务器之间建立 WebSocket 连接。以下是 WebSocket 握手时常用的几个关键 HTTP 头部字段及其含义:
1. Upgrade
- 含义:表示客户端希望将 HTTP 协议升级为另一种协议。
- WebSocket 使用:
Upgrade: websocket
,表示客户端请求将 HTTP 升级为 WebSocket 协议。
2. Connection
- 含义:控制 HTTP 连接的行为,通常用于决定是否保持连接。
- WebSocket 使用:
Connection: Upgrade
,表示当前连接将用于协议升级。
3. Sec-WebSocket-Key
- 含义:客户端生成的一个随机的 Base64 编码的值,用于在握手过程中验证服务器的响应。
- WebSocket 使用:
Sec-WebSocket-Key: <base64-encoded-value>
,这个值用于安全验证,防止非 WebSocket 服务器误接受连接请求。
4. Sec-WebSocket-Version
- 含义:指定 WebSocket 协议的版本号。
- WebSocket 使用:
Sec-WebSocket-Version: 13
,通常表示使用 WebSocket 的第 13 版协议,这是当前广泛使用的版本。
5. Sec-WebSocket-Accept
- 含义:服务器在响应中返回的值,用于确认 WebSocket 握手成功。它是基于客户端发送的
Sec-WebSocket-Key
和固定字符串计算得出的 Base64 值。 - WebSocket 使用:
Sec-WebSocket-Accept: <base64-encoded-value>
,客户端会验证这个值以确定服务器是否正确响应。
6. Host
- 含义:表示请求的目标主机及端口号。
- WebSocket 使用:
Host: <server-host>
,指定连接的服务器地址和端口。
7. Origin(可选)
- 含义:用于防止跨站请求伪造(CSRF),表示请求的来源。
- WebSocket 使用:
Origin: <origin>
,服务器可以根据此头部字段来判断是否接受来自某个来源的 WebSocket 连接。
8. Sec-WebSocket-Protocol(可选)
- 含义:用于指定客户端希望使用的子协议(如
chat
,superchat
等)。 - WebSocket 使用:
Sec-WebSocket-Protocol: <subprotocol>
,客户端可以通过此字段指定某个应用协议,服务器在响应中会选择一个或多个子协议。
9. Sec-WebSocket-Extensions(可选)
- 含义:用于协商 WebSocket 的扩展功能,例如压缩数据传输等。
- WebSocket 使用:
Sec-WebSocket-Extensions: <extension>
,可以声明 WebSocket 连接支持哪些额外的功能。
握手示例:
客户端请求:
1 | GET /chat HTTP/1.1 |
服务器响应:
1 | HTTP/1.1 101 Switching Protocols |
总结:
WebSocket 握手使用了多个 HTTP 头部字段来完成协议升级。Upgrade
和 Connection
用于表明升级协议的请求和确认,Sec-WebSocket-Key
和 Sec-WebSocket-Accept
用于安全验证,而 Sec-WebSocket-Version
用于声明使用的 WebSocket 协议版本。
网络请求封装
SSE和小程序的流式输出
微信小程序使用二进制数据流(arraybuffer)做分块传输
SSE长连接(Connection: keep-alive),减少频繁建立连接的开销,提升传输效率。
埋点-日志记录
使用window.dotLog进行数据收集,在需要捕获用户行为的地方调用window.dotLog
window.dotLog = dotLog 这行代码将 dotLog 函数挂载到全局的 window 对象上,以便在整个应用的任何地方都可以方便地调用这个函数,通常这是为埋点服务的。埋点是指在特定位置(如页面加载、按钮点击等)记录用户行为或应用事件,以便在后期进行分析和优化。
埋点的目的是追踪用户行为,如页面点击、表单提交、滚动等。这些信息可以用于用户行为分析、A/B 测试、用户体验优化等。
项目的性能优化
首页图片的预加载,使用Promise.all
进行网络的并行预加载,设置了最大的并发进行控制,及时的销毁最上面的元素,使用虚拟列表结合懒加载
预加载图片资源时有哪些性能优化措施?
- 并行加载:通过
Promise.all
来并行预加载所有图片,这能够最大化利用网络带宽,加快加载速度。 - 进度反馈:通过
scheduleChange
回调函数,将加载进度实时反馈给调用方,可以在页面上展示加载动画或进度条,提升用户体验。 - 错误处理:虽然
img.onerror
捕捉了加载错误,但目前代码中没有针对错误的具体处理措施,这可能是未来的一个改进点。这段代码已经包含了一些性能优化的措施: - 并行加载:通过
Promise.all
来并行预加载所有图片,这能够最大化利用网络带宽,加快加载速度。 - 进度反馈:通过
scheduleChange
回调函数,将加载进度实时反馈给调用方,可以在页面上展示加载动画或进度条,提升用户体验。 - 错误处理:虽然
img.onerror
捕捉了加载错误,但目前代码中没有针对错误的具体处理措施,这可能是未来的一个改进点。
如果预加载的图片非常多,可能会产生什么性能问题?如何改进?
当预加载的图片数量非常多时,使用 Promise.all
并行加载所有图片可能会带来以下问题:
网络压力:瞬间发送大量并发请求可能会导致网络拥塞,尤其是在网络带宽有限的情况下。
浏览器连接限制:浏览器对于同一域名的并发请求数是有限制的,超过限制的请求会进入等待队列,反而可能降低整体加载效率。
并发控制:通过设置最大并发数,限制同时发出的请求数量,从而控制加载节奏,避免瞬间大量请求。例如,可以使用自定义的并发控制函数来实现。
如何检测和处理预加载失败的图片?有什么方法可以确保加载的图片质量和加载的可靠性?
当前代码使用 img.onerror
来捕获图片加载失败,但没有进一步的处理。如果某张图片加载失败,可以采取以下策略:
- 重试机制:对于加载失败的图片,可以实现自动重试机制,尝试重新加载,增加成功率。
- 加载占位符图片:如果多次重试失败,可以为该图片展示一个默认的占位符,避免页面空白,改善用户体验。
- 错误日志记录:可以将加载失败的图片 URL 记录下来,用于后续的排查和修复。
你如何判断已经加载的图片是否需要重新加载?可以如何进一步避免重复加载?
为了避免重复加载已经成功加载过的图片,可以利用浏览器的缓存机制。以下是具体的优化方法:
- **使用
img.complete
**:在每次加载图片之前,先检查图片是否已经被浏览器缓存。如果图片已经加载过并缓存,可以直接使用缓存,避免重复加载。 - 基于 URL 缓存机制:在更复杂的场景中,可以实现 URL 层面的缓存管理,记录每个已加载图片的 URL,防止重复加载同一张图片。
一直往下滑到底部,图片会不会加载?
一直往下滑会造成内存占用增加,带宽消耗
在处理大量数据时,主要面临的挑战包括:
- 内存占用:加载和渲染十万条数据会消耗大量的内存,导致页面卡顿甚至崩溃。
- 渲染性能:如果页面不断渲染大量 DOM 元素,尤其是带图片的元素,会导致性能瓶颈。
- 图片资源管理:图片在内存中的占用是非常高的,尤其是当用户滚动时,图片资源的管理必须谨慎处理。
虚拟列表(Virtual Scrolling)
概念:虚拟列表技术是处理长列表渲染的最佳实践之一。它只渲染当前可视区域的内容,其他不可见部分的 DOM 节点则不会被渲染或保留在内存中。对于十万条数据,虚拟列表能显著减少页面的内存占用和 DOM 渲染的开销。
- **React 的
react-window
或react-virtualized
**:这些库专为长列表的虚拟化而设计,它们只会渲染当前可见部分的元素,并随着用户滚动动态更新可见区域的 DOM。 - **Vue 的
vue-virtual-scroller
**:Vue 中也有类似的库用于虚拟化长列表。
1 | import { FixedSizeList as List } from 'react-window'; |
- 只渲染当前可视区域的内容,显著减少 DOM 节点的数量。
- 动态管理 DOM 元素的创建与销毁,大幅降低内存占用。
按需数据加载
对于十万条数据,不应该一次性加载到页面中。可以采用按需加载的策略,结合分页或滚动加载数据,逐步获取数据。
- 分页加载:每次加载一小部分数据(例如 100 条或 500 条),随着用户的滚动逐步加载更多数据。
- IntersectionObserver + Infinite Scroll:可以通过
IntersectionObserver
监测用户的滚动,当用户接近底部时自动加载更多数据。
1 | let page = 1; |
图片懒加载与资源回收
对于图片资源的管理,需要结合懒加载和销毁策略,确保当前只加载可见的图片,并且当图片滚出可视区域时释放内存。
- 懒加载图片:仅当图片出现在视口中时,才加载它。可以使用
IntersectionObserver
实现图片的懒加载。 - 资源回收:对于已经滚出视口且长时间不可见的图片,应该销毁它们并释放内存。可以将
src
设置为空或移除 DOM 节点,确保不会占用多余的内存。
1 | const images = document.querySelectorAll('img[data-src]'); |
节流与防抖
为了防止过多的滚动事件导致频繁的渲染和数据加载,可以对滚动事件进行节流(throttle)或防抖(debounce)处理。这样可以确保只有当用户停止滚动或滚动一段距离时,才会触发新的渲染和加载逻辑。
1 | const throttle = (func, limit) => { |
是否需要销毁最开始的图片?
应该销毁最开始的图片资源,但要有一个合理的策略。
- 内存优化:由于图片占用的内存较大,长时间保留不可见的图片会导致内存使用不断增加。因此,不可见的图片应该销毁,从而释放内存。
- 重新加载:当用户返回顶部时,应该能够重新加载那些已经销毁的图片。可以通过懒加载的方式重新加载已经滚出视口并被销毁的图片。
合理的图片销毁策略:
- 滚出视口后延迟销毁:不要立即销毁刚刚滚出视口的图片,而是可以延迟一段时间再进行销毁。这是为了防止用户稍后返回时,频繁加载和卸载图片造成卡顿。
- 缓存重要内容:对于一些关键图片或内容,可以通过缓存机制保留在内存中,以免频繁销毁和重新加载。
总结
在处理包含大量数据(如十万条)的页面时,性能优化的关键是:
- 虚拟列表:通过虚拟化技术,只渲染当前可视区域,减少 DOM 节点。
- 按需加载数据:采用分页和滚动加载策略,避免一次性加载所有数据。
- 懒加载和销毁图片:通过懒加载只加载可见图片,并在滚出视口后延迟销毁以节省内存。
- 节流与防抖:通过节流或防抖技术,优化滚动事件的触发频率,避免频繁触发渲染和加载。
HEAD取代Get方法
完美聚合平台
完美AI策略聚合平台通过整合多个主流大语言模型(如 OpenAI、阿里通义千问等),为用户提供全面的 AI 功能支持,包括对话、绘图和生成语音等,项目主要涵盖四大模块:模型应用、工具类、游戏和虚拟角色。平台面向公司用户,帮助提升生产效率。
在该项目中,我主要负责流式传输的整体架构设计与实现,包括fetchEventSource的集成及其与前端显示的无缝衔接。我实现了消息接收后的数据处理逻辑,确保返回的流数据能够实时展示并保持流畅的用户体验。此外,我还设计了中止控制功能,通过 AbortController实现对流请求的即时取消,并在系统内加入了错误处理机制和状态管理,确保流式传输的稳定性和健壮性。在用户界面方面,我优化了滚动条的自动更新,使得用户能够即时查看到最新生成的内容,显著提升了交互体验。
如何处理流式请求?
实现了一个基于 fetch-event-source 的流式请求。这个实现方式利用了 Server-Sent Events (SSE)来处理流式数据传输,接收服务器推送的数据流;在请求头添加Authorization携带用户的token进行身份验证;根据不同的状态码进行处理,比如跳转登陆页面,公告等。
fetchEventSource配置
url: 请求的完整 URL。
- method: HTTP 请求方法,这里使用 POST。
- headers: 请求头,包含 Content-Type 和 Authorization。
- body: 请求体,使用 JSON.stringify 将数据序列化。
- openWhenHidden: 允许在页面隐藏时保持连接。
- onmessage: 当服务器发送消息时的回调函数,处理接收到的数据。
- onclose: 当连接关闭时的回调函数,处理连接关闭的逻辑。
回调函数
- callback: 处理每条消息的回调函数。
- close: 处理连接关闭的回调函数(如果提供),在连接关闭或者出错的时候,使用自动重试机制
1 | request.stream = (url: string, data: any, callback: (val: any) => void, close?: () => void, token = store.user.token) => { |
在处理流式输出的稳定性时,通常会遇到以下几个主要挑战:网络波动、数据延迟、输出流中断以及不稳定的模型响应
使用 WebSocket 进行实时通讯: WebSocket 提供了双向通信的能力,可以确保服务器和客户端之间的实时数据传输。这对于流式输出尤其重要,因为它能够减少延迟,提供稳定的数据流。相比传统的 HTTP 轮询,WebSocket 更高效且延迟更低。
数据缓冲与重试机制: 在流式输出的过程中,可以引入数据缓冲机制,确保数据能够有序且完整地输出。如果网络波动导致流式输出中断,可以使用重试机制来重新请求未完成的部分,保证用户体验不受影响。
设置超时与断开重连机制: 为了避免网络问题导致长时间等待或流式数据丢失,建议在客户端设置超时机制,一旦超过指定时间没有收到数据,就主动重连服务器。这样可以在网络短暂中断后,恢复流式输出的连续性。
逐步加载和流式传输优化: 当处理流式 GPT 模型时,可以分批次逐步加载模型生成的内容,而不是等待整个输出完成后再传输。这种分块式的流数据传输可以大大提升用户体验,避免长时间等待。
带宽和网络优化: 流式传输对网络带宽要求较高,因此可以通过优化传输协议或压缩传输的数据包,减少带宽占用,增强稳定性。
异步处理与多线程: 在前端实现中,可以采用异步处理或者多线程技术来处理接收到的流式数据,确保数据在接收到时就能被即时渲染,而不需要等待整个流式输出的完成。
流数据的分片与合并: 对于较大的数据流,可以将其拆分成小块进行传输,客户端接收到小块数据后立即渲染,这样能提升实时响应速度,降低数据丢失或超时的风险。
状态监控与回退机制: 实时监控流式输出的状态,如果检测到输出过程不稳定或中断,可以立即触发回退机制,通知用户进行重试,或者从最后一个接收的有效输出开始重新连接数据流。
处理流式
发送请求:根据 props.strategy.stream 判断是否采用流式传输,如果是流式传输,则使用 fetchEventSource 进行数据的流式获取。
流式处理:使用 fetchEventSource 发起 POST 请求,请求的数据通过流式输出方式返回。
在 onmessage 回调函数中处理每一段流数据,根据返回的数据类型(如 text、audio 等)动态更新 dialogue 列表,并通过滚动条的更新(scrollBottom())确保页面自动滚动到最新内容。通过 requestId 确保每个流的消息顺序正确,并处理可能的重复消息或延迟消息。
- 错误处理:在 onopen、onclose 和 onerror 回调中进行相应的连接、关闭和错误处理,确保流在出现异常时可以正确中止并反馈给用户。
关键的流式稳定性优化
重试机制与中止控制:通过 AbortController 实现了对流式请求的中止控制,用户可以在需要时中止请求,或者在网络问题出现时取消请求。处理了 requestId 的匹配逻辑,确保流在处理时不会因为前后请求的混淆导致输出错误。
逐步加载和数据拼接:onmessage 中对返回的流数据进行了处理,通过判断返回数据类型进行拼接和内容追加,特别是对 text 类型的数据,通过累加 value 进行逐步渲染,保证流式数据的顺滑展示。
连接与状态管理:在 onopen 中判断连接状态并设置 streamChatEnd,确保流式传输的状态正确维护。在 onclose 和 onerror 中根据 requestId 检查当前流的状态,防止错误处理遗漏或误判,确保用户能够得到及时的反馈。
滚动条与界面更新:通过 scrollBottom() 保证页面的自动滚动,使得流式输出的内容能够即时展现给用户,提高了用户体验。
优化建议
- 延时与节流控制:在高频率的 onmessage 更新中,可以引入节流机制,避免页面的频繁更新导致的卡顿现象。
- 多策略并行处理:考虑是否会有多个策略同时运行的情况,必要时可以对 requestId 处理逻辑进行更加精细的管理。
- 重连机制:目前的实现较为基础,如果需要更高的稳定性,建议加入自动重连机制,在流意外中断时自动尝试重新连接。
取消请求
使用CancelToken创建取消令牌,再发起请求的时候传递给Axios—Axios
使用AbortController 创建一个控制器,用于取消请求。—Fetch
Martix一站式开发
负责Martix项目管理平台和轻任务协作数据管理平台完成前端业务的开发,Martix的项目进行了第二版本的性能优化,首页性能提高到170ms以内,对轻任务进行个人待办事项和团队待办事项的开发。
微前端–qiankun
- Single-SPA
- Micro-App:Web Component
- Module Federation
- qiankun
优点:
- 支持多种框架:Qiankun 可以与 React、Vue、Angular 等多种前端框架共存,支持跨框架的微前端架构。
- 独立部署:每个子应用可以独立开发、测试和部署,降低了各模块之间的耦合。
- 按需加载:支持动态加载子应用,减少初始加载时间,提高性能。
- 共享依赖:通过优化共享依赖,避免重复加载相同的库,节省资源。
对比:
- Qiankun 使用 JavaScript 直接集成,避免了 iframe 带来的性能和通信问题。
iframe
的缺点包括性能问题(增加加载时间和资源消耗)、安全性隐患(如跨域限制和点击劫持)、SEO 不友好(搜索引擎不索引内容)、样式和布局隔离(导致视觉不一致)、用户体验不佳(导航和历史管理问题)以及调试难度较大。这些缺点使得在某些场景下,使用iframe
不够理想。
生命周期
如何做的样式隔离,原理是什么?
通过沙箱机制实现样式隔离。其原理主要包括以下几个方面:
样式封装:Qiankun 会在每个微应用的运行环境中,将其样式进行封装,确保不同微应用之间的样式不会相互干扰。常用的技术包括 CSS Modules 和 Scoped CSS。
Shadow DOM:在某些情况下,Qiankun 会使用 Shadow DOM 机制,为每个微应用创建一个独立的 DOM 结构,样式只在该 DOM 内部生效。
动态注入样式:Qiankun 在加载微应用时,会将微应用的样式动态注入到页面中,并为其添加唯一标识,以避免样式冲突。
CSS 变量:为了实现主题定制和样式共享,Qiankun 还可以利用 CSS 变量,在不同微应用之间传递一些可定制的样式属性。
这些机制结合起来,确保了在同一页面中运行多个微应用时,各自的样式能够独立且不相互影响,从而实现良好的样式隔离效果。
子应用间的通信
事件总线:可以使用一个全局的事件总线(如 Node.js 的 EventEmitter 或者浏览器的 CustomEvent)来广播和监听事件。各微应用可以通过订阅和发布事件来进行通信。
自定义事件:微应用可以使用浏览器的
CustomEvent
来创建和触发自定义事件,实现应用间的消息传递。共享状态管理:可以使用如 Redux、MobX 等状态管理库,将状态提升到主应用或一个共享的状态管理模块,以便各微应用共享和更新状态。
全局对象:在主应用中定义一个全局对象,微应用可以通过这个对象来访问和更新共享数据。注意要避免命名冲突。
API 接口:微应用之间可以通过定义 RESTful API 或 GraphQL 接口进行数据交互。主应用或其他微应用可以调用这些接口来获取或更新数据。
postMessage:在跨域的情况下,可以使用
window.postMessage
方法进行安全的跨窗口或跨域通信。
这些方式各有优缺点,选择合适的通信方式通常取决于具体的应用场景和需求。
性能优化
服务端渲染,减少不必要的服务端渲染,
动态加载子应用,使用dynamic动态导入进行按需加载资源
v-if和v-show
watch合理使用监听状态,使用computed代替watch
按需加载+懒加载+虚拟列表
- 组件懒加载
- 路由懒加载
keep-alive
分片上传+断点续传
vuex
主子应用通信
props和EventBus
尽量使用事件驱动方式触发主子应用通信,避免频繁数据同步
轻任务
基于ApiTable开源的实时协作数据管理平台,允许用户创建、管理和共享数据表、表单和仪表板。提供了强大的数据操作和协作功能。支持多种视图类型(画廊、日历、多维表格、看板等)和扩展性,包含个人待办事项、团队待办事项和模板库,通过引入自定义待办事项和丰富的模板库,极大提升了用户在项目管理、数据分析和团队协作方面的效率。
在AI-Partner
心动剧场项目中,我主要负责了多个核心模块的开发和优化。包括实现NPC角色的展示与互动功能,集成GPT、BERT等AI模型,提供智能对话体验;设计并开发了用户注册和登录功能,使用Google OAuth 2.0
认证简化用户流程,并通过AutoLogin
功能优化了用户体验。同时,我还负责了动态主题切换功能,确保跨设备的无缝体验,并提升了页面渲染性能。此外,成功集成了Solana区块链USDT支付系统,保证了虚拟货币交易的安全性和高效性,支持游戏内的购买和充值功能。文件管理方面,通过优化大数据处理和管理机制,显著提高了系统的稳定性与性能。
如何实现数据同步
使用WebSocket
来实现实时数据同步。WebSocket允许在客户端和服务器之间建立持久连接,支持双向通信。每当用户在前端进行数据操作时,这些操作会通过WebSocket即时发送到服务器,服务器处理后再将更新的数据广播给所有连接的客户端,从而实现数据的实时同步。
如何实现视图切换
多视图切换的核心在于视图状态的管理。我们使用了React的状态管理工具(如Redux)来管理当前的视图状态。每种视图(如表格视图、相册视图)都对应一个独立的组件,这些组件共享同一套数据源。通过在状态管理中维护当前视图的类型,我们可以根据状态的变化动态加载和渲染不同的视图组件,实现视图的无缝切换。
实现复杂的查询功能,如何处理性能问题
索引、缓存、分页
为了处理复杂查询的性能问题,我们采取了多种优化措施。首先,在数据库层面,我们对常用的查询字段建立了索引,以加快查询速度。其次,我们使用了缓存机制,将频繁查询的数据缓存到内存中,减少数据库的访问次数。另外,我们还实现了分页加载,避免一次性加载大量数据,进一步提升性能。
React.memo:高阶组件,对比props
useCallback:函数
useMemo:缓存结果
React.lazy
按需加载组件
虚拟列表加载:可视区域,分页
如何多用户的实时协同时的数据一致性
在多用户实时协同环境下,数据一致性是一个重要问题。我们采用了乐观锁机制来处理并发修改。每次数据更新时,都会附带一个版本号,只有当版本号匹配时,才允许更新成功,否则会提示用户数据冲突。对于冲突的数据,我们提供了冲突解决机制,用户可以选择保留自己的修改或接受他人的修改。
在这个项目中,多用户同步功能是通过WebSocket实现的。每个用户在登录或进入特定空间时都会建立一个WebSocket连接,并加入对应的空间。服务器端维护一个WebSocket连接池,根据消息的目标空间ID,将消息广播给在同一空间内的所有连接。客户端接收到消息后,根据消息的类型和内容进行相应的处理,如更新Redux状态或显示通知,从而实现多用户之间的实时数据同步和协作。
如何保证数据的安全性和隐私性的?
为了保证数据的安全性和隐私性,我们采取了多种措施。首先,在数据传输过程中,我们使用HTTPS协议加密通信,防止数据被窃取。其次,在数据存储方面,我们对敏感数据进行加密存储,确保数据在被盗或泄露时仍然无法被解读。此外,我们还实施了严格的访问控制机制,只有授权用户才能访问和操作数据。我们定期进行安全审计和漏洞扫描,及时发现和修复安全问题。
遇到最大的问题–挑战
开发过程中最大的挑战是实现实时协同功能,因为这涉及到复杂的并发控制和数据同步问题。为了解决这个问题,我们首先进行了详细的技术调研,选择了合适的技术方案(如WebSocket和乐观锁机制)。然后,我们通过不断的迭代开发和测试,逐步完善了实时协同功能,并在实际使用中进行优化和改进。
使用Google OAuth 2.0认证,用户可以通过Google账户进行安全便捷的注册和登录。开发了AutoLogin方法,支持自动登录功能,通过解析URL查询参数、验证码验证和用户信息获取,实现无缝登录体验。
负责开发NPC角色创建和管理模块,支持用户通过表单填写基础信息、设定角色个性,选择不同的GPT模型,添加角色介绍,并设置角色的公开、私有、半公开状态,提升游戏的个性化和用户参与度
实现与NPC角色的聊天互动功能,支持聊天背景和音乐设置、聊天内容分享,并在聊天过程中动态调整所使用的AI模型,增强互动体验。
开发NPC角色创建,允许用户创建、编辑和管理多个NPC角色。调用并集成预训练的AI模型(如GPT系列、BERT等),为NPC角色提供智能对话能力。
开发了用户自定义剧场功能,使用户能够自主创建剧场,选择剧场类型,上传相关文件,并进行内容的更新与发布,增强了游戏的个性化和用户参与度。
建立和管理
WebSocket
连接,确保连接稳定,处理服务器发送的消息,并通过心跳检测机制保持连接活跃。如果连接中断,会自动重连。通过回调管理机制,可以方便地添加和移除消息处理逻辑,使其具有良好的扩展性和维护性。在心动剧场实现聊天记录组件,通过
ResizeObserver
和 Vue 的watch
,确保每次接收到新消息时自动平滑滚动到底部,并确保用户始终能看到最新消息。支付功能,使用虚拟货币支付,创建了Express服务器,通过
/sendUSDT
路径处理USDT代币转移请求。它检查请求的合法性,处理必要的参数,并通过与Solana区块链交互完成代币转移。开发了一个Express服务器和实用函数,用于处理Solana区块链上的USDT代币转账。服务器通过验证请求来源和必要参数,确保安全性。使用自定义的
transferSolanaToken
函数管理代币账户,创建并签署转账指令,发送并确认交易。该过程包括全面的错误处理和详细的日志记录,确保转账过程的高效和可靠。
数据缓冲与重试机制:
在流式输出的过程中,可以引入数据缓冲机制,确保数据能够有序且完整地输出。如果网络波动导致流式输出中断,可以使用重试机制来重新请求未完成的部分,保证用户体验不受影响。设置超时与断开重连机制:
为了避免网络问题导致长时间等待或流式数据丢失,建议在客户端设置超时机制,一旦超过指定时间没有收到数据,就主动重连服务器。这样可以在网络短暂中断后,恢复流式输出的连续性。逐步加载和流式传输优化:
当处理流式 GPT 模型时,可以分批次逐步加载模型生成的内容,而不是等待整个输出完成后再传输。这种分块式的流数据传输可以大大提升用户体验,避免长时间等待。带宽和网络优化:
流式传输对网络带宽要求较高,因此可以通过优化传输协议或压缩传输的数据包,减少带宽占用,增强稳定性。异步处理与多线程:
在前端实现中,可以采用异步处理或者多线程技术来处理接收到的流式数据,确保数据在接收到时就能被即时渲染,而不需要等待整个流式输出的完成。流数据的分片与合并:
对于较大的数据流,可以将其拆分成小块进行传输,客户端接收到小块数据后立即渲染,这样能提升实时响应速度,降低数据丢失或超时的风险。状态监控与回退机制:
实时监控流式输出的状态,如果检测到输出过程不稳定或中断,可以立即触发回退机制,通知用户进行重试,或者从最后一个接收的有效输出开始重新连接数据流。
流程
- 发送请求:根据
props.strategy.stream
判断是否采用流式传输,如果是流式传输,则使用fetchEventSource
进行数据的流式获取。 - 流式处理:
- 使用
fetchEventSource
发起 POST 请求,请求的数据通过流式输出方式返回。 - 在
onmessage
回调函数中处理每一段流数据,根据返回的数据类型(如text
、audio
等)动态更新dialogue
列表,并通过滚动条的更新(scrollBottom()
)确保页面自动滚动到最新内容。 - 通过
requestId
确保每个流的消息顺序正确,并处理可能的重复消息或延迟消息。
- 使用
- 错误处理:
- 在
onopen
、onclose
和onerror
回调中进行相应的连接、关闭和错误处理,确保流在出现异常时可以正确中止并反馈给用户。
- 在
关键的流式稳定性优化
重试机制与中止控制:
- 通过
AbortController
实现了对流式请求的中止控制,用户可以在需要时中止请求,或者在网络问题出现时取消请求。 - 处理了
requestId
的匹配逻辑,确保流在处理时不会因为前后请求的混淆导致输出错误。
- 通过
逐步加载和数据拼接:
onmessage
中对返回的流数据进行了处理,通过判断返回数据类型进行拼接和内容追加,特别是对text
类型的数据,通过累加value
进行逐步渲染,保证流式数据的顺滑展示。
连接与状态管理:
- 在
onopen
中判断连接状态并设置streamChatEnd
,确保流式传输的状态正确维护。 - 在
onclose
和onerror
中根据requestId
检查当前流的状态,防止错误处理遗漏或误判,确保用户能够得到及时的反馈。
- 在
滚动条与界面更新:
- 通过
scrollBottom()
保证页面的自动滚动,使得流式输出的内容能够即时展现给用户,提高了用户体验。
- 通过
Martix项目管理平台
为什么选择qiankun做微前端
微前端的生命周期
如何做样式隔离
- 使用qiankun微前端架构,使用 React 开发的主应用负责动态加载和管理多个独立开发的微应用;
- 封装富文本编辑器组件,实现图片自动上传、代码块编辑、历史版本管理和恢复以及内容缓存管理等功能。组件支持内容粘贴处理、自动识别和转换图片,确保编辑过程的流畅性和稳定性,优化用户界面的互动体验。
- 开发需求管理组件和实现需求评审流程配置功能,运用Vue.js、Vuex等技术实现复杂表单的动态生成和校验,处理多层嵌套组件的通信和状态管理,优化异步数据的加载和处理逻辑,实现了需求数据的动态加载和实时更新;
- 开发文件批量上传与管理组件,支持文件多种操作(如上传、预览、关联文档等),通过客户端验证减少服务器压力,提高项目文档管理的效率和用户体验;
- 开发甘特图视图组件,采用虚拟滚动和延迟加载技术解决大规模数据渲染带来的性能挑战,开发自定义列配置组件,利用localStorage实现了用户配置的跨会话持久化,实现的视图切换和动态数据过滤功能,包括精确的时间线对齐、动态数据绑定、以及用户交互的拖拽和缩放功能,提高甘特图的查看和操作效率。
微前端
使用registerMicroApps注册微前端,使用nextjs的useRouter路由和useEffect监听路由,
并通过全局状态管理与子应用共享路由状态,使得子应用能够响应路由变化。
qiankun生命周期,在子应用中使用
bootstrap、mount、unmount、update
当使用 Vue 2 构建 Qiankun 的子应用时,你需要在子应用中定义和导出相应的生命周期钩子,这些钩子会与 Qiankun 主应用进行交互以实现模块的正确加载和卸载。这些生命周期钩子包括:bootstrap
、mount
、unmount
,以及可选的 update
(如果你需要处理主应用传递的 props)。下面是如何在 Vue 2 子应用中实现这些生命周期钩子的一个示例:
bootstrap:
- 这个阶段主要用于全局级别的初始化工作,如设置日志、初始化服务等,通常只运行一次。
mount:
- 这个阶段用于将 Vue 实例挂载到 DOM 上,通常在这里初始化和渲染 Vue 组件。
unmount:
- 这个阶段用于清理、卸载 Vue 实例,避免内存泄露,确保所有事件监听器和依赖都被适当移除。
update:
- 这个阶段用于处理新的 props 或状态更新,是可选的,依赖于你的应用是否需要响应外部变化。
实现注意事项
- 确保在
mount
函数中正确地挂载 Vue 实例到由 Qiankun 提供的 DOM 容器中。通常这是通过props.container.querySelector('#app')
实现的,其中#app
是容器内的一个 DOM 元素的 ID。 - 在
unmount
函数中,彻底销毁 Vue 实例和清理 DOM 是至关重要的,以防止内存泄漏或其他副作用。 - 如果实现了
update
函数,确保你的应用可以响应外部状态的变化,例如,接收来自主应用的数据更新。
通过这样的设置,Vue 2 子应用可以被 Qiankun 框架有效地管理,实现真正意义上的微前端架构,其中各个子应用可以独立开发、部署和更新,而不会影响到主应用或其他子应用的运行。
路由鉴权
您提供的 JavaScript 函数 hasPermission
是用于在基于路由的系统中检查用户是否拥有访问某个特定路由的权限。这个函数通过分析路由对象和用户的权限列表来决定是否允许访问某个路由。下面我将详细解释这个函数的逻辑和作用:
函数参数
to
: 这个参数表示即将访问的路由对象。通常,这个对象包含了路由的元数据(如名称、查询参数等)。hasPermissionList
: 这是一个数组,包含了用户拥有的权限名称。
函数逻辑
检查元数据是否存在:
- 函数首先检查
to.meta
是否存在,to.meta
通常包含与路由相关的元数据,比如权限相关信息。
- 函数首先检查
特定路由的权限检查:
- 函数中有特定的逻辑来处理名为
workload
和bugreport
的路由。对于这些路由,它进一步检查查询参数to.query.type
,来决定用户是否有权限访问不同类型的统计信息。 - 对于
workload
路由:- 如果
type
是dept
(部门),则检查用户权限列表中是否包含'部门工作量统计'
。 - 如果
type
是member
(员工),则检查是否包含'员工工作量统计'
。 - 如果
type
是project
(项目集),则检查是否包含'项目集工作量统计'
。
- 如果
- 对于
bugreport
路由:- 类似地,基于
type
(部门、员工或项目集),检查相应的'部门缺陷量统计'
、'员工缺陷量统计'
或'项目集缺陷量统计'
。
- 类似地,基于
- 函数中有特定的逻辑来处理名为
通用权限检查:
- 如果路由名称不是
workload
或bugreport
,则检查hasPermissionList
是否包含to.meta.title
。如果不包含,则表示没有权限。
- 如果路由名称不是
默认行为:
- 如果没有
to.meta
,函数默认允许访问,即返回true
。
- 如果没有
示例使用场景
假设这个函数被用在一个 Web 应用的路由守卫中,每次路由跳转之前都会调用这个函数来确定用户是否有权限访问目标路由。这种权限检查是维护应用安全性的重要方式,确保用户只能访问他们被授权的部分。
代码改进建议
虽然这个函数能够正常工作,但它的逻辑有些重复,可以通过一些重构来优化:
- 提取重复逻辑:对于检查权限的逻辑,可以考虑抽象出一个辅助函数,简化代码。
- 使用映射:将权限名称和路由查询类型映射起来,减少重复的
if-else
语句。 - 错误处理:在实际应用中,可能需要更复杂的错误处理逻辑,比如当路由对象缺失必要信息时提供默认行为或记录错误。
这个函数的存在显示了如何在客户端进行基于路由的访问控制,这是构建安全且可维护的现代 Web 应用的一个关键方面。
描述:实时协作数据平台,允许用户创建、管理和共享数据表、表单和仪表板。项目采用了模块化的设计和Websocket通信,提供强大的实时更新和协作能力,支持多种视图类型,包含个人待办事项、团队待办事项和模板库,通过引入自定义待办事项和丰富的模板库,提升用户在项目管理、数据分析和团队协作方面的效率。
登录系统集成无痕验证码技术,通过服务器端自动执行,减少了传统验证码对用户体验的干扰。该系统全面涵盖了用户数据处理、前端校验、无痕验证,以及服务器端的认证与状态管理,确保用户操作的便捷性和系统的安全性;
实现动态的页面导航,通过postMessage实现了iframe中的应用与主页面的安全通信,根据不同的应用场景,如分享视图、嵌入视图、个人路径等,实现了条件性导航逻辑,在不同上下文中都能够正确地引导用户到期望的视图;
通过Sentry用于错误监控和性能优化,提高错误追踪的准确性和应用性能监控的效率,设计自定义hooks用于封装复杂的业务逻辑和API请求,包括节点的增删改查、个人待办事项的管理、共享和收藏节点操作等;
基于Websocket实现实时双向通信,在不刷新页面情况下同步和更新用户数据,增强用户交互体验;性能优化与后台处理:引入 Web Workers 处理复杂的后台数据计算任务,解放了主 UI 线程,避免了复杂运算对用户界面响应的影响。通过有效管理线程,提高了页面渲染效率和应用响应速度。
负责甘特图视图内任务内容的动态渲染组件开发,使用延迟加载和按需渲染的策略,减少了页面的初始加载时间和运行时的性能开销,采用 React 和 Konva 库实现客户端绘制,通过 Next.js 动态导入技术优化加载速度;
负责轻任务应用的主导航栏组件,支持在不同设备和屏幕尺寸上的操作体验,处理了用户登录、空间选择、通知计数等多个状态的同步和更新,根据用户权限动态调整导航栏展示的功能模块,对于非管理员用户隐藏空间管理入口,对于删除空间的用户限制访问等,实现了细粒度的功能控制。
开发个人待办事项管理组件,通过Hooks和高阶组件等技术实现功能模块的解耦和复用,利用自定义hooks处理数据请求和副作用,在处理共享目录的逻辑时,通过Redux和封装自定义hooks,实现了状态的更新和数据的同步;
04-09北京海致科技集团
遍历数据方法?
for循环
1 | for(let i = 0; i < 5; i++) { |
for of:ES6中的方法,不会遍历数组的私有属性,不能遍历对象,可以遍历字符串,数组,有返回值
1 | let arr=[1,2,3,4,5] |
for in:数组的私有属性也会遍历,所以一般用来遍历对象
1 | let arr=[1,2,3,4,5]; |
foreach:forEach() 方法对数组的每个元素执行一次给定的函数。
1 | let arr=[1,2,3,4,5]; |
遍历对象的方法和遍历数组的方法分别说五个
在 JavaScript 中遍历对象并访问其属性的方法有多种,每种方法都适用于不同的情况和需求。以下是一些常见的方法:
1. for...in
循环
这是遍历对象属性最传统的方式。for...in
循环会遍历对象自身的所有可枚举属性以及它继承的可枚举属性。
1 | const obj = { a: 1, b: 2, c: 3 }; |
注意:使用 for...in
时,通常需要使用 hasOwnProperty
方法来过滤掉从原型链继承的属性:
1 | for (let key in obj) { |
2. Object.keys()
方法
Object.keys()
返回一个数组,包含对象自身的所有可枚举属性名称。配合 forEach
循环使用可以遍历对象属性:
1 | Object.keys(obj).forEach(function(key) { |
3. Object.values()
方法
这个方法返回一个数组,包含对象自身的所有可枚举属性值:
1 | Object.values(obj).forEach(function(value) { |
4. Object.entries()
方法
Object.entries()
返回一个数组,其元素是与对象自身可枚举属性键值对对应的数组:
1 | Object.entries(obj).forEach(([key, value]) => { |
5. Object.getOwnPropertyNames()
方法
这个方法返回一个数组,包含对象自身的所有属性(无论是否可枚举)的名称:
1 | Object.getOwnPropertyNames(obj).forEach(function(key) { |
6. 使用 for...of
循环(配合 Object.entries()
)
如果你想使用 for...of
循环遍历对象属性,可以结合使用 Object.entries()
:
1 | for (let [key, value] of Object.entries(obj)) { |
7. Reflect.ownKeys()
方法
Reflect.ownKeys(obj)
返回一个数组,包含对象自身的所有键(包括不可枚举属性及 Symbol 类型的键):
1 | Reflect.ownKeys(obj).forEach(function(key) { |
使用场景选择
- **
for...in
**:适用于需要遍历继承的属性的情况,但通常需要配合hasOwnProperty
。 - **
Object.keys()
、Object.values()
、Object.entries()
**:适用于现代 JavaScript 应用,可以更方便地遍历对象的可枚举属性。 - **
Object.getOwnPropertyNames()
和Reflect.ownKeys()
**:适用于需要访问对象所有键(包括不可枚举的)的高级用途。
这些方法可以灵活选择和使用,以满足不同的遍历需求和特定的编程环境。
遍历数组的方法有:
- for 循环
- forEach() 方法
- map() 方法
- filter() 方法
- reduce() 方法
- some() 方法
- every() 方法
- for…of 循环
04-11-滴滴日常实习
事件循环
qiankun机制是怎样的
qiankun
是一个实现了微前端架构的JavaScript库,它允许开发者将一个大型的前端应用分解为多个可以独立开发、部署、运行的小型应用(微应用)。qiankun
基于single-spa
,提供了一套比较完善的微前端解决方案,其工作机制主要可以从以下几个方面理解:
初始化和注册
主应用启动:在主应用中,
qiankun
提供registerMicroApps
方法用于注册微应用。在注册时,你需要为每个微应用指定一个唯一的名称、入口(可以是URL或者一段HTML标记)、容器(用于挂载微应用的DOM元素)以及激活条件(当URL符合某个规则时激活对应的微应用)。微应用加载:当激活条件满足时,
qiankun
会动态加载微应用的静态资源(JavaScript、CSS等)。这一过程通常是通过fetch
API完成的。
沙箱机制
JavaScript沙箱:为了防止微应用间的全局变量污染,
qiankun
为每个微应用创建了一个JavaScript运行时沙箱。这个沙箱通过代理全局对象(如window
)来隔离全局变量,确保微应用间的隔离性。样式隔离:
qiankun
使用Shadow DOM或者其他策略来实现CSS隔离,防止微应用间的样式冲突。
生命周期管理
qiankun
为微应用定义了一系列生命周期钩子,例如bootstrap
、mount
、unmount
等。主应用通过这些钩子可以管理微应用的加载、挂载、更新和卸载。微应用需要暴露这些生命周期方法,以便
qiankun
在适当的时刻调用它们。
通信
qiankun
提供了主应用和微应用之间的通信机制。通常,这是通过在注册微应用时传递props
来实现的。props
可以包含需要共享的状态、函数等。
性能优化
预加载:
qiankun
支持配置预加载微应用的静态资源,以缩短微应用首次启动的时间。资源复用:
qiankun
支持主应用和微应用之间的依赖共享,减少了重复加载相同依赖库的需要。
总结
qiankun
的机制提供了一种有效的方式来构建大型的、由多个团队独立开发和部署的前端应用。它通过动态加载、沙箱隔离、生命周期管理和通信机制,实现了技术栈无关、运行时独立、应用间隔离的微前端架构。这使得大型项目可以更加模块化、易于管理,同时还能保证不同团队的开发效率和应用性能。
nextjs做服务端渲染
Next.js 是一个基于 React 的框架,专门用于构建服务端渲染(Server-Side Rendering, SSR)和静态网站生成(Static Site Generation, SSG)的应用。Next.js 通过预渲染页面来提高性能和搜索引擎优化(SEO),预渲染可以在服务端生成HTML,也可以在构建时生成,然后发送到客户端。
服务端渲染(SSR)的工作原理
在传统的React应用中,大部分渲染工作都在客户端进行。而在Next.js中,服务端渲染意味着首次页面加载时的HTML是在服务器上生成的,这个过程包括:
- 请求处理:用户请求一个页面时,该请求被发送到服务器。
- 页面渲染:服务器运行React代码来生成页面的HTML内容,同时也可以在这个阶段获取和包含必要的数据(例如,通过API调用获取)。
- 发送响应:服务器将生成的HTML作为响应发送给客户端。
- 客户端处理:客户端浏览器接收到HTML并呈现给用户。然后,React代码在浏览器端“接管”页面,使其成为一个完全交互式的单页应用。
如何在Next.js中使用SSR
Next.js 通过文件系统基路由和数据获取方法如getServerSideProps
自动处理SSR。
页面组件
每一个位于pages
目录下的.js
、.jsx
、.ts
或.tsx
文件都会自动成为路由,并且默认是通过服务端渲染的。
数据获取
对于需要服务端获取数据的页面,你可以使用getServerSideProps
函数来获取数据:
1 | // pages/posts.js |
getServerSideProps
在请求时运行,允许你在渲染页面前从服务器获取数据。这个函数只在服务端运行,绝不会在客户端运行,因此可以直接写服务器代码,如直接访问数据库等。
优势
- SEO友好:由于页面的HTML是预先在服务器上生成的,搜索引擎可以更容易地抓取和索引内容。
- 性能提升:首次加载时直接提供包含所有必要数据的HTML,可以减少首屏渲染时间。
- 灵活的数据获取策略:Next.js不仅支持SSR,还支持SSG(静态网站生成),以及客户端数据获取,开发者可以根据页面的需求选择最适合的策略。
Next.js通过这些机制,极大地简化了使用React进行服务端渲染应用的开发流程,同时也提供了灵活、高效的数据处理和页面渲染方案。
看题输出–this
1 | let obj = { |
在JavaScript中,this
关键字的值取决于函数是如何被调用的。在对象obj
中定义的方法表现出不同的this
行为:
- a - 是一个正常的函数,当被调用时,
this
指向obj
对象。 - b - 是ES6的简写方法,当被调用时,
this
同样指向obj
对象。 - c - 是一个箭头函数,它没有自己的
this
,this
值是从创建它的上下文(即定义它的上下文)继承的,在全局代码中,this
指向全局对象。 - d - 是一个正常的函数,它返回一个箭头函数。箭头函数内部的
this
由外围最近一层非箭头函数决定,在这里是d
函数,因此箭头函数中的this
指向obj
对象。 - e - 是一个正常的函数,它调用
obj
对象的b
方法,this
指向obj
对象。 - f - 是一个正常的函数,它返回
obj
对象的b
方法,但不执行它。当f
被调用,它返回对b
的引用,此时this
尚未被绑定。
现在,根据以上规则,可以确定console.log
语句的输出:
1 | console.log(obj.a()) // '222222' - 'this'指向obj |
对于obj.c()
,由于箭头函数不绑定this
,它将会捕获定义时的this
值。如果obj
是在全局上下文中定义的,this.dev
可能会返回undefined
(严格模式下)或者全局对象的dev
属性(非严格模式下)。
对于obj.f()
,它返回b
函数的引用,但在执行时它已经失去了原始的obj
上下文,因此在严格模式下this
将会是undefined
,在非严格模式下通常是全局对象。然而,如果obj.f()
在obj
的上下文中直接被调用,this
会正确地指向obj
,因为它继承了f()
调用时的上下文。
请注意,如果obj
对象是在某个函数作用域或模块作用域中定义的,而且该作用域是严格模式(例如ES6模块默认是严格模式),那么this
的行为可能会与非严格模式不同。在Node.js或模块化JavaScript环境中运行时,全局上下文中的this
是undefined
。
宏任务和微任务
在JavaScript中,执行顺序涉及到宏任务和微任务的概念。setTimeout
被放入宏任务队列,而Promise的.then
方法产生的回调被放入微任务队列。微任务队列在每次宏任务执行完毕后执行,且在下一个宏任务开始前,微任务队列会被完全清空。这里是这段代码执行的顺序解释:
console.log('1')
直接执行,打印1
。setTimeout(..., 0)
排入宏任务队列,等待执行。Promise.resolve().then(...)
的回调函数排入微任务队列。- 新的
Promise
立即执行console.log('4')
,打印4
。 - 然后立即执行
console.log('5')
,打印5
。 - 该Promise的
.then(...)
回调函数被排入微任务队列。 - 第二个带
setTimeout
的Promise构造函数执行,但其内部的setTimeout
是一个宏任务,排入宏任务队列,等待执行。 - 当前宏任务(脚本的主体)执行完毕,开始执行微任务队列:首先执行
Promise.resolve().then(...)
的回调,打印3
,然后执行第一个Promise的.then(...)
回调,打印6
。 - 微任务队列清空,开始下一个宏任务,首先是第一个
setTimeout
的回调,打印2
。 - 然后执行第二个
Promise
中的setTimeout
,等待100ms后,它的.then(...)
回调被排入微任务队列。 - 等到该宏任务完成后,执行微任务队列中的回调,打印
7
。
综上,打印顺序是:1
, 4
, 5
, 3
, 6
, 2
, 7
。
1 | console.log('1') |
Promise的常用方法有哪些?
Promise.all(iterable)
- 接受一个 Promise 对象的集合(数组、迭代器等)作为输入。
- 当所有的 Promise 都成功解决(resolved)时,返回一个新的 Promise,该 Promise 成功解决,并且其结果是所有输入 Promise 的结果数组。
- 如果任何一个 Promise 被拒绝(rejected),
Promise.all
返回的 Promise 会立即被拒绝,其拒绝原因是第一个拒绝的 Promise 的原因。
Promise.allSettled(iterable)
- 接受一个 Promise 对象的集合作为输入。
- 无论输入的 Promise 对象是成功还是失败,都会等待它们全部完成。
- 返回一个新的 Promise,该 Promise 解决后的结果是一个数组,数组中每个元素表示对应的 Promise 的结果,包括状态(
fulfilled
或rejected
)和值(或拒绝原因)。
Promise.race(iterable)
- 接受一个 Promise 对象的集合作为输入。
- 返回一个新的 Promise,它将与输入的 Promise 中第一个解决或拒绝的 Promise 具有相同的解决值或拒绝原因。
Promise.resolve(valve)
- 返回一个以给定值解决的 Promise。
- 如果输入是一个 Promise,那么
Promise.resolve
将返回这个 Promise。 - 如果输入带有一个
then
方法(即“thenable”对象),返回的 Promise 会“跟随”这个 thenable 的行为,即采用其最终状态。
Promise.reject(reason)
返回一个以给定原因拒绝的 Promise。
Promise.prototype.then(onFulfilled,onRejected)
- 在 Promise 上添加解决(fulfilled)和拒绝(rejected)的回调函数。
- 返回一个新的 Promise,以链式处理异步操作。
Promise.prototype.catch(onRejected)
- 添加一个拒绝(rejected)回调函数,是
.then(null, onRejected)
的语法糖。 - 返回一个新的 Promise。
Promise.prototype.finally(onFinally)
- 添加一个回调函数,无论 Promise 是解决还是拒绝,该回调函数都会被执行。
- 返回一个新的 Promise,在原始 Promise 解决或拒绝之后解决。
这些方法提供了丰富的接口来处理异步操作,使得异步代码更加可读和易于管理。
Commonjs和ES Module
Commonjs 和 Es Module 有什么区别 ?
CJS:
- 环境:最初被设计用于 Node.js。
- 导入模块:使用
require()
函数导入模块。 - 导出模块:使用
module.exports
和exports
对象导出模块。 - 加载方式:同步加载模块,即在代码执行到
require()
语句时立即同步地加载和执行模块。 - 用例:主要用于服务器端和桌面应用程序。
ESM:
- 环境:作为 ECMAScript(JavaScript的规范)的一部分,被设计用于浏览器和现代 JavaScript 环境。
- 导入模块:使用
import
语句导入模块。 - 导出模块:使用
export
关键字导出模块。 - 加载方式:支持异步加载模块,允许进行代码分割和懒加载。
- 用例:主要用于浏览器应用程序,但现在在 Node.js 和其他 JavaScript 环境中也得到了支持。
区别:
- 语法:CommonJS 使用
require
和module.exports
,而 ES Modules 使用import
和export
。 - 加载机制:CommonJS 模块是同步加载的,主要适用于服务器端,而 ES Modules 可以异步加载,更适合浏览器环境。
- 模块解析:在 CommonJS 中,
require
的路径可以是动态的,支持表达式,而 ES Modules 中的import
路径必须是静态的,不能使用表达式。 - 模块值的处理:CommonJS 导入的模块值是一个模块对象的拷贝,而 ES Modules 导入的是一个模块接口的“活”绑定。
联系:
- 模块化目标:两者都旨在提供一种封装代码的方式,将代码分割成可复用的单元,并控制它们的作用域和公开接口。
- 兼容性考虑:在现代 JavaScript 环境中(特别是在 Node.js 中),有努力使 CommonJS 和 ES Modules 能够相互操作。例如,Node.js 通过特定的导入语法支持在使用 CommonJS 规范的代码中导入 ES Module。
随着 JavaScript 生态的发展,ES Modules 逐渐成为主流的模块化标准,但在很多现有项目和库中,CommonJS 仍然广泛使用。在新项目中选择哪种标准,通常取决于项目的运行环境、目标平台和开发者的偏好。
Commonjs 如何解决的循环引用问题
既然有了 exports,为何又出了 module.exports ? 既生瑜,何生亮 ?
require 模块查找机制 ?
Es Module 如何解决循环引用问题 ?
exports = {} 这种写法为何无效 ?
关于 import() 的动态引入 ?
Es Module 如何改变模块下的私有变量 ?
04-11趣链科技
介绍实习项目
介绍微前端
qiankun
是一个基于 single-spa
的微前端实现库,它通过一系列 API 简化了微前端的实现,允许开发者将不同的前端应用组合成一个完整的应用。以下是 qiankun
的一些底层实现细节以及主应用和子应用通信的方式。
qiankun
底层实现
JavaScript 沙箱:
qiankun
通过 JavaScript 沙箱技术隔离主应用和子应用的运行环境。沙箱主要通过代理全局变量来实现,每个微应用都运行在自己的全局上下文中。
样式隔离:
- 为了防止 CSS 污染,
qiankun
对子应用的样式进行隔离。它可以使用 Shadow DOM 或者为子应用的样式增加特定的前缀来实现。
- 为了防止 CSS 污染,
生命周期钩子:
qiankun
实现了一套生命周期钩子,允许在加载、挂载、更新和卸载微应用时进行相应的处理。
资源加载:
qiankun
使用fetch
API 动态加载微应用的资源文件,包括 HTML、JavaScript 和 CSS。
路由代理:
qiankun
对浏览器历史 API 进行了劫持,以支持微应用的路由系统,确保路由变化能够被正确处理。
主应用和子应用通信方式
Props 传递:
- 类似于 React 或 Vue 组件的 props,
qiankun
允许主应用在加载微应用时传递 props。这些 props 可以是简单的数据,也可以是可以调用的函数,用于主子应用间的直接通信。
- 类似于 React 或 Vue 组件的 props,
全局变量:
- 可以通过全局变量在主应用和子应用之间通信。例如,主应用可以将一个全局变量或者函数挂载到
window
对象上,子应用可以访问和使用这些变量或函数。
- 可以通过全局变量在主应用和子应用之间通信。例如,主应用可以将一个全局变量或者函数挂载到
自定义事件(Custom Events):
- 主应用和子应用可以通过触发和监听自定义事件来实现通信。这种方式不直接依赖于
qiankun
的 API。
- 主应用和子应用可以通过触发和监听自定义事件来实现通信。这种方式不直接依赖于
状态管理库:
- 如果主应用和子应用都使用相同的状态管理库(如 Redux、Vuex),可以通过共享一个状态存储来进行通信。
消息通道(Message Channel):
- 可以使用
window.postMessage
和监听window
上的message
事件来在主应用和子应用之间进行跨域通信。
- 可以使用
Observable 对象:
- 使用诸如 RxJS 这类响应式编程库中的 Observable 对象,可以在应用之间创建一个可观察的数据流。
这些通信方式可以根据应用的实际需要和开发习惯进行选择和使用。在微前端架构中,选择合适的通信机制对于确保应用的独立性和协调一致性非常重要。
Qiankun 是一个基于 single-spa 的微前端框架,提供了更简单的 API 和配置方式,让开发者能够构建大型的、由多个前端应用组合而成的应用系统。在 Qiankun 中,每个子应用都有自己的生命周期,这些生命周期钩子允许主应用在特定时刻介入子应用的状态,实现控制和资源管理。
Qiankun 子应用的生命周期
以下是 Qiankun 子应用的主要生命周期钩子及其用途:
bootstrap:
- 时机:子应用初次加载时调用。
- 用途:用于启动子应用,通常包括初始化资源、依赖项或其它只需执行一次的设置。
mount:
- 时机:每次子应用需要被激活(即展示到视图中)时调用。
- 用途:用于挂载子应用的实例到 DOM,处理状态恢复和事件监听器的添加等。
unmount:
- 时机:每次子应用需要被卸载(从视图中移除)时调用。
- 用途:用于卸载子应用,清除相关资源和事件监听器,保证内存释放,避免内存泄漏。
update(可选):
- 时机:主应用或自身传入新的 props 时调用。
- 用途:响应 props 变化,执行相关更新逻辑。
生命周期的实现
在子应用中,你通常需要导出这些生命周期钩子的实现。这可以通过单独的函数或对象方法实现。例如,如果你的子应用是用 React 构建的,你的生命周期实现可能如下:
1 | export async function bootstrap() { |
使用场景
这些生命周期方法对于管理应用状态、资源分配和优化性能至关重要。例如,通过 unmount
方法清理事件监听器和外部插件,可以避免内存泄漏。通过 mount
和 unmount
方法,可以确保子应用只在必要时占用浏览器资源,从而优化整体的应用性能。
了解和合理利用这些生命周期钩子,是使用 Qiankun 微前端框架有效管理多个前端应用的关键。这有助于确保应用的稳定性、响应性和高效性。
CSS的动画了解吗:比如transition
事件循环机制:宏任务和微任务
浏览器的存储,cookie,localstorage,sessionstorage
说一下Websocket
WebSocket 是一种在单个TCP连接上进行全双工通信的协议。WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。他不是HTTP协议,
节流和防抖的使用场景,闭包
强缓存和协商缓存:cache-control+Expires ,协商缓存:E-Tag,Last-Modified
Promise和一些静态方法(promise.all,promise,race等)
Promise
是JavaScript中用于异步编程的一个重要概念。它代表了一个最终可能完成或失败的操作及其结果值。一个Promise
对象有三种状态:
pending
(等待状态):初始状态,既不是成功,也不是失败状态。fulfilled
(成功状态):操作完成,且成功返回值。rejected
(失败状态):操作完成,但失败,返回拒绝的原因。
Promise
提供了一种更加优雅的处理异步操作的方法,相比于传统的回调函数,它可以减少代码的嵌套,并且更易于理解和维护。
Promise
的静态方法
- **Promise.all(iterable)**:当你需要并行执行多个异步操作,并且只有当所有异步操作成功时才继续执行,可以使用
Promise.all
。 - **Promise.allSettled(iterable)**:如果你需要知道一系列异步操作的每一个的结果,不管是成功还是失败,可以使用
Promise.allSettled
。 - **Promise.race(iterable)**:当你有多个异步操作,并且你只关心哪个操作最先完成(或失败),可以使用
Promise.race
。 - **Promise.resolve(value)和Promise.reject(reason)**:这两个方法可以用来创建已经处于完成或拒绝状态的
Promise
,用于测试或者与其他Promise
操作结合使用。
Vue2的生命周期
创建前后,挂载前后,更新前后,销毁前后
异步请求的阶段:created,mounted
组件通信的方式
- props,$emit;
- provide,inject;
- Event Bus
- vuex
- Vue3 Composition API(Vue3):setup
- Refs 和 $parent / $children:
路由鉴权
路由守卫routerConfig.beforeEach的meta.access的值决定是否允许访问某个路由
微前端生命周期钩子
- bootstrap:子应用的启动
- mount:挂载
- onmount:卸载
在微前端架构下,子应用的静态资源路径可能需要动态设置以确保资源加载正确。代码中通过修改__webpack_public_path__
来实现这一点。
dom节点事件
DOM 事件流描述了从页面中接收事件的顺序,它是一个将事件从窗口发送到具体节点,然后再回传回去的过程。DOM 事件流主要分为三个阶段:捕获、目标、冒泡
1. 捕获阶段(Capturing Phase)
- 事件从
window
对象传导到事件目标的父节点的过程。 - 在捕获阶段,事件会从文档的根节点开始,经过祖先节点,向下传递到目标节点(触发事件的那个最深层的节点)。
- 目的是更早地在文档层次中捕捉事件,但是默认情况下,大多数事件处理程序在此阶段不会处理事件。
2. 目标阶段(Target Phase)
- 当事件到达目标元素时。在这个阶段,事件不会进一步传播,而是在目标上触发指定的监听器。
- 这是事件被实际处理的阶段,可以在这个阶段对事件进行操作或响应。
3. 冒泡阶段(Bubbling Phase)
- 事件从目标元素向上冒泡到文档的根节点。
- 在冒泡阶段,事件会从目标元素开始,逐级向上传递给祖先节点,一直传到文档的根。
- 大部分事件处理发生在此阶段,因为它允许一个单一的父级处理器来监听一个元素和它的所有子元素上发生的事件。
使用事件流的优点
利用 DOM 事件流的特性,可以实现更加灵活的事件处理机制,例如事件委托。事件委托是一种利用事件冒泡原理来减少事件处理器数量的技巧。通过在父节点上监听事件,然后根据事件的目标(event.target
)来判断如何响应,可以有效地处理动态元素或大量相似元素的事件,这样做既可以减少内存占用,也可以提高程序性能。
设置事件监听的阶段
在使用 addEventListener
方法为元素添加事件监听器时,可以指定是在捕获阶段还是冒泡阶段触发处理器。这是通过该方法的第三个参数实现的,如果是 true
,则在捕获阶段触发;如果是 false
或不设置,则在冒泡阶段触发。
理解 DOM 事件流的工作机制,对于开发复杂的交互式网页应用是非常重要的,它有助于开发者编写出更高效、更可控的事件处理代码。
Vue 2 和 Vue 3 在设计和功能上有许多重要的区别,这些区别旨在提高性能、增加灵活性,以及改进开发体验。下面是 Vue 2 和 Vue 3 之间的一些主要区别:
1. 性能提升
Vue 3 引入了许多性能优化,包括更快的虚拟 DOM 算法、优化的组件初始化过程、以及基于代理的观察者机制。这些改进使得 Vue 3 在渲染速度、内存占用以及启动时间上相较于 Vue 2 有显著提升。
2. Composition API
Vue 3 引入了一个全新的 Composition API,它提供了一种更灵活的方式来组织和重用逻辑。与 Vue 2 的 Options API 相比,Composition API 使得在相同组件中处理多个功能点变得更加清晰,并且更容易将逻辑提取到可复用的函数中。
3. 更好的 TypeScript 支持
Vue 3 从一开始就考虑了对 TypeScript 的支持,使得在使用 TypeScript 开发 Vue 应用时的体验更加流畅。相比之下,Vue 2 的 TypeScript 支持是后来通过社区努力逐步改进的。
4. Fragment、Teleport 和 Suspense
- Fragment: Vue 3 允许组件有多个根节点,解决了 Vue 2 中组件必须有单一根节点的限制。
- Teleport: Vue 3 引入的 Teleport 特性允许将组件的子元素渲染到 DOM 树的其他位置,这对于如模态框或通知之类的 UI 元素非常有用。
- Suspense: Vue 3 新增了 Suspense 组件,为异步组件的加载提供了内置的等待机制,使得处理异步组件变得更加方便。
5. 响应式系统的重写
Vue 3 的响应式系统从使用 Object.defineProperty 重写为基于 ES6 的 Proxy,这提供了更好的性能和更灵活的响应式能力,同时解决了 Vue 2 中的一些响应式系统限制,如检测属性的添加和删除、数组索引和长度的变化等。
6. 更轻量和模块化
Vue 3 的设计更加模块化,允许在打包过程中自动摇树优化(Tree-shaking),这意味着最终的应用只包含真正使用到的功能代码,从而减小了应用大小。
7. 新的组件挂载方式和全局 API 更改
Vue 3 更改了组件挂载到 DOM 的方式,以及全局 API(如 Vue.use
、Vue.mixin
等)的使用方式,使得它们更适合模块化的项目结构。
尽管 Vue 3 带来了很多新特性和改进,Vue 2 仍然是一个非常稳定和功能丰富的框架,被广泛用于生产环境中。迁移到 Vue 3 是一个值得考虑的步骤,尤其是对于新项目或正在考虑重构的项目。
代码输出:
1 | for(let i=1;i<=3;i++){ |
这段JavaScript代码定义了一个名为Fn
的构造函数,它没有参数和执行体。接着,在Fn
的原型(Fn.prototype
)上定义了一个名为add
的方法和一个名为count
的属性。
1 | function Fn() {}//`function Fn() {}`:这行代码定义了一个空的构造函数`Fn`。 |
Fn.prototype.count = 0;
:这行代码在Fn
的原型对象上设置了一个属性count
,初始值为0。所有通过Fn
构造函数创建的实例都会共享这个原型上的count
属性。但是,当使用add
方法时,由于属性提升(即实例上没有count
属性,所以会在实例上创建一个新的count
属性,并且将其值设置为原型上count
的当前值加1),每个实例都会有自己独立的count
属性。
然后代码创建了两个Fn
的实例,并且分别调用了它们的add
方法:
let fn1 = new Fn();
:通过new
关键字和Fn
构造函数创建了一个新的实例fn1
。fn1.add();
:调用fn1
实例的add
方法,这会使fn1
实例上的count
属性值从0变为1,并打印出this.count: 1
。let fn2 = new Fn();
:同样地,创建了另一个名为fn2
的Fn
实例。fn2.add();
:调用fn2
实例的add
方法,和fn1
一样,这也会使fn2
实例上的count
属性值从0变为1,并打印出this.count: 1
。
值得注意的是,尽管count
属性最初是定义在Fn
的原型上的,当调用add
方法时,由于this.count++
这行代码的作用,实际上是在各自的实例上创建了一个新的count
属性,并且这个属性不再和原型上的count
属性共享。所以,即使是Fn.prototype.count
的值没有变化,每个实例上的count
属性都是独立的,它们的值互不影响。
Vite 和 webpack 区别
Vite 和 Webpack 是前端开发中常用的两个现代 JavaScript 构建工具,它们在模块打包和开发服务器方面有着根本的不同。了解这两个工具的区别对于选择适合项目的构建工具非常重要。
Vite
- 开发模式: Vite 在开发模式下使用原生 ES 模块导入(ESM),这允许浏览器直接加载模块,而无需打包。这种方法可以显著提高冷启动时间和模块热更新(HMR)的速度。
- 生产模式: 在构建生产版本时,Vite 使用 Rollup 进行打包。Rollup 通常被认为在打包库和轻量级应用时比 Webpack 更高效。
- 优点: Vite 提供了极快的启动时间和即时模块热替换(HMR),因为它不需要预打包。它还利用现代浏览器支持的 ES 模块导入,减少了不必要的代码转换。
- 使用场景: Vite 特别适合 Vue 和 React 项目,但也可以配置用于其他框架。它的设计理念使其特别适合需要快速开发和迭代的现代网页应用(SPA)。
Webpack
- 开发模式: Webpack 在开发和生产模式下都依赖于将项目的所有依赖打包成一个(或多个)bundle的概念。在开发模式下,它提供了模块热替换(HMR)但通常启动和编译速度比 Vite 慢。
- 生产模式: Webpack 使用复杂的算法来优化输出的大小和加载时间,包括代码拆分、树摇(Tree-shaking)、懒加载等。
- 优点: Webpack 提供了高度灵活和可配置的打包策略,支持广泛的插件生态系统,适合于需要复杂打包逻辑的大型应用。
- 使用场景: Webpack 适用于各种项目,从简单的静态网站到复杂的单页应用(SPA)。它的灵活性和插件生态使其成为许多企业级应用的首选。
主要区别
- 启动速度: Vite 在开发模式下提供了更快的启动和热更新速度,因为它避免了预打包的步骤。
- 打包策略: Vite 利用现代浏览器的 ES 模块特性,而 Webpack 使用传统的打包方法,将所有资源和代码编译成一个或多个文件。
- 构建工具链: Vite 默认使用 Rollup 作为生产环境的打包工具,而 Webpack 自身就是一个打包工具。
- 插件和生态系统: Webpack 拥有一个成熟的插件生态系统,几乎可以定制任何构建流程。Vite 也支持插件,但相对较新,生态系统仍在发展中。
- 配置复杂度: Vite 旨在提供开箱即用的体验,其默认配置已经足够多数应用,而 Webpack 通常需要更多的配置和优化。
选择 Vite 还是 Webpack 取决于项目需求、团队熟悉度和特定场景。对于新项目,特别是当开发速度和现代浏览器特性是优先考虑时,Vite 可能是一个更好的选择。对于需要复杂构建流程和已经深度依赖于 Webpack
生态系统的项目,Webpack 仍然是一个强大的选择。
Vue2 升级 Vue3 需要注意什么
数组去重有哪些方法
数组去重是编程中常见的一个需求,尤其是在处理大量数据时。JavaScript 提供了多种方法来实现数组的去重。以下是一些常用的方法:
1. 使用 Set 和展开运算符
这是最简单和最现代的方法之一,利用了 Set
对象可以存储任何类型的唯一值的特性。
1 | const array = [1, 2, 2, 3, 4, 4, 5]; |
2. 使用 Set 和 Array.from()
与使用展开运算符类似,但是通过 Array.from()
方法将 Set
转换成数组。
1 | const array = [1, 2, 2, 3, 4, 4, 5]; |
3. 使用 filter 方法
通过数组的 filter()
方法结合 indexOf()
方法来过滤重复元素。这种方法不需要额外的数据结构。
1 | const array = [1, 2, 2, 3, 4, 4, 5]; |
4. 使用 reduce 方法
使用 reduce()
方法累加数组中的元素,结合条件判断实现去重。
1 | const array = [1, 2, 2, 3, 4, 4, 5]; |
5. 使用 Map 数据结构
利用 Map
的键唯一的特性来去重,适合于数组元素是对象这类比较复杂的情况。
1 | const array = [1, 2, 2, 3, 4, 4, 5]; |
6. 循环比较(传统方法)
传统的双重循环检查方法,性能较低,但在一些特定情况下仍然有其用途。
1 | const array = [1, 2, 2, 3, 4, 4, 5]; |
总结
根据你的具体需求(比如是否考虑到数组中元素的类型、执行效率等),可以选择最适合的方法进行数组去重。对于大多数情况,使用 Set
和展开运算符或 Array.from()
是最简洁和现代的解决方案。
CSS怎么解析选择器
2、V8解析执行JavaScript代码过程
3、script标签
4、子元素里面绝对定位,设置宽高,能否撑起父元素
5、怎么形成BFC
6、ES6新特性
7、ES6新增什么作用域,之前有什么作用域
8、类型转换
9、代码题——立即执行函数能否访问到全局变量
10、React——setState同步异步
11、React父子组件通信
0412网易
介绍项目,项目的适配不是基于移动端的适配,针对不同的电脑
盒模型以及组成
事件流:捕获、目标、冒泡,如果有1000个事件需要触发,如何解决?–事件代理,原生事件问得多
css如何布局?flex:1
–flex-grow,flex-basis,flex-shrink
左右浮动,上面设置右边的flex:1,右边的文字设置超过两行隐藏
css的动画,transition
和animation
transition:只能在初始状态和结束状态之间提供动画效果,不支持中间状态或复杂的动画序列。
1 | div { |
**animation:**用于更复杂的动画效果,它允许你通过关键帧(@keyframes 规则)定义动画的中间步骤。你可以控制动画的中间状态、循环次数、方向、延迟等。
1 | @keyframes example { |
webpack、vite,代码中如何配置,举例说明
分成不同的环境:生产、灰度、线上,
ts的泛型、接口、枚举
- 定义泛型函数
1
2
3
4
5
6function identity<T>(arg: T): T {
return arg;
}
let output1 = identity<string>("myString"); // 类型为 string
let output2 = identity<number>(100); // 类型为 number - ts的泛型有属性的时候可以使用extends关键字进行约束,在这个例子中,泛型 T 被约束了,它必须符合 Lengthwise 接口,这意味着 T 必须有一个 length 属性。
1
2
3
4
5
6
7
8interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // 它有一个 .length 属性
return arg;
} - 枚举遍历,for … in
1
2
3
4
5
6
7
8
9enum Color {
Red,
Green,
Blue
}
for (let color in Color) {
console.log(color); // 打印出 "0", "1", "2", "Red", "Green", "Blue"
}
美团
cookie如何设置跨域
Cookie跨域实现主要依赖于CORS(Cross-Origin Resource Sharing)策略。在服务器端设置Access-Control-Allow-Credentials和Access-Control-Allow-Origin响应头,并在AJAX请求中设置withCredentials为true。
服务器端设置(以Node.js的Express为例):
1 | app.use(function(req, res, next) { |
客户端设置(以JavaScript的XMLHttpRequest为例):
1 | var xhr = new XMLHttpRequest(); |
确保服务器响应头中的Access-Control-Allow-Origin与实际请求的域相匹配,并且如果withCredentials为true,则Access-Control-Allow-Credentials也必须设置为true。
0415DeepLang
postmessage和http通信
浏览器原理,事件循环和url,
ES6创建的函数我不想他被new创造新的实例
使用new target:
1 | function myFunction() { |
对es6的理解
强缓存和协商缓存
用户1修改了数据,用户2及时更新查看,你们是用的是什么方式?请求的数据如何不请求http的方式进行更新
在多用户环境中,确保所有用户看到的数据都是最新的,是一个常见的挑战。特别是在你提到的场景中,用户1修改了数据后,用户2需要无需发送传统HTTP请求就能看到更新的数据。为此,可以使用以下技术和策略:
1. WebSocket
WebSocket 提供了一种在单个连接上进行全双工、双向交互的方式。服务器可以通过WebSocket连接直接向客户端推送数据,这对于实时应用来说非常有用。当用户1更新数据时,服务器可以通过所有打开的WebSocket连接向其他用户推送这些更改。
实施步骤:
- 建立WebSocket连接。
- 当用户1提交更新时,服务器处理这些更改并通过WebSocket向所有其他在线用户广播更新。
- 客户端收到推送的数据后,更新UI以反映最新信息。
2. Server-Sent Events (SSE)
Server-Sent Events 允许服务器向浏览器推送事件。与WebSocket相比,SSE是单向的,从服务器到客户端,这在只需要服务器推送而不需要从客户端到服务器的消息传递的应用中非常有效。
实施步骤:
- 客户端设置监听特定URI的SSE。
- 当用户1更新数据时,服务器将这些更新作为事件发送给所有监听该事件的客户端。
- 客户端接收到这些事件后,更新页面数据。
3. 使用轮询或长轮询
如果WebSocket或SSE不适用于您的情况,轮询是另一种确保用户看到最新数据的方法,尽管它不如前两种方法实时。轮询涉及定期发送HTTP请求以检查更新,而长轮询则保持连接打开直到有更新可发送。
实施步骤:
- 客户端每隔几秒发送一次HTTP请求,询问服务器是否有数据更新。
- 服务器响应请求,如果有更新则发送更新数据。
4. 使用Pub/Sub模型的服务
使用像Google Firebase Realtime Database这样的Pub/Sub模型服务,可以允许客户端订阅数据更新,并在数据发生变化时接收实时更新。
实施步骤:
- 客户端在应用中订阅特定数据。
- 任何数据更改都会触发通知,所有订阅了这些数据的客户端将自动接收更新。
结论
根据你的应用需求(如用户基数、预期负载、实时性需求等)和技术堆栈,你可以选择最适合的方法。WebSocket和SSE提供了更加实时的数据同步方案,而轮询则是一种更简单但请求量较大的解决方案。使用如Firebase这样的实时数据库可以简化开发工作,但可能会引入外部依赖和额外成本。
封装一个hooks
1 | import React, { useState } from 'react'; |
现在我有一个文件,下载需要8s,如何使用协商缓存和强缓存进行优化?
对于文件下载缓慢的问题,正确地使用HTTP缓存策略可以显著提高性能。在HTTP缓存中,主要分为强缓存和协商缓存。这两种缓存策略可以有效减少不必要的网络请求,加快文件访问速度。以下是如何选择和使用这两种缓存策略来优化你的文件下载问题:
强缓存 (Strong Caching)
强缓存直接从浏览器缓存中获取资源,不与服务器进行交互,除非缓存过期。设置强缓存的方法主要是通过Cache-Control
和 Expires
HTTP响应头实现。
Cache-Control: 最常用的缓存控制指令包括
max-age
(资源可以缓存的最大时间,单位是秒)和public
(指示响应可被任何缓存区缓存)。1
Cache-Control: public, max-age=31536000
这表示资源将被缓存并在一年后过期,非常适用于不经常变动的文件。
Expires: 指定资源的过期日期和时间。在
Cache-Control
出现之前就已存在,通常与Cache-Control
一起使用以保证兼容性。1
Expires: Thu, 01 Dec 2022 16:00:00 GMT
如果设置了
Cache-Control
的max-age
,通常不需要Expires
。
协商缓存 (Negotiation Caching)
当强缓存过期后,协商缓存允许浏览器向服务器发出请求,询问资源是否有更新。如果服务器确认资源未更新(通过发送HTTP状态码304),浏览器将从本地缓存加载资源。
Last-Modified / If-Modified-Since: 服务器在响应中发送
Last-Modified
日期,浏览器下次请求时发送If-Modified-Since
,询问服务器在该日期后资源是否被修改。1
Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
ETag / If-None-Match: 服务器发送资源的ETag(一种标识符),浏览器使用
If-None-Match
来发送ETag,检查资源是否有变动。1
ETag: "686897696a7c876b7e"
选择适合的缓存策略
对于你的文件下载问题,考虑以下因素:
- 文件变动频率:如果文件不经常更新,使用强缓存是一个好选择。可以通过设置
Cache-Control
的max-age
为一个相对较长的值来实现。 - 实时性要求:如果文件更新不频繁但需要保证用户能快速获取最新版本的文件,可以结合使用强缓存和协商缓存。强缓存确保在文件不变时快速加载,协商缓存确保文件一旦更新,用户能立即获取最新版本。
实施示例
1 | Cache-Control: public, max-age=86400 |
这种设置意味着文件将被缓存一天,每天浏览器会检查一次文件是否更新。这样可以显著减少不必要的下载时间,同时保持文件的相对新鲜度。如果你的文件是用户每次都需要最新版的,可以适当减少max-age
值,或者仅依赖ETag进行更频繁的检查。
通过这种方式,你可以有效利用HTTP缓存提高文件的访问速度和用户体验。
二面
http的head有哪些
http协议
不使用递归实现中序遍历:栈
mysql的事务有哪些
useMemo和useEffect的区别
useMemo
主要用途:
- 性能优化:避免在每次渲染时进行高开销的计算。
- 记忆化复杂派生数据:基于依赖计算得到的数据,只有当依赖改变时,才重新计算。
示例:
1 | const expensiveValue = useMemo(() => { |
在这个例子中,只有当a
或b
变化时,函数computeExpensiveValue
才会被调用。
useEffect
useEffect
是用于在函数组件中执行副作用的钩子。副作用指的是那些对外部世界产生影响的操作,比如数据获取、手动修改DOM和订阅事件。useEffect
同样接收一个函数和一个依赖数组,但它的工作方式与useMemo
大不相同。
主要用途:
- 与外部世界的交互:执行与渲染无关的操作,如API调用、订阅或定时器。
- 资源清理:在组件卸载时进行清理,如取消订阅或清除定时器。
- DOM更新后的操作:在组件和DOM更新后执行操作。
示例:
1 | useEffect(() => { |
这个例子中,useEffect
用于订阅一个数据源,依赖数组确保只在dataSource
变化时重新订阅。
执行时机的区别
useMemo:
- 在组件渲染过程中执行,即同步执行。因为它可能会影响渲染输出的内容,必须在渲染流程中完成。
useEffect:
- 在组件渲染到屏幕之后执行,即异步执行。这意味着它不会阻塞浏览器的绘制过程,适用于那些对渲染结果无直接影响的操作。
总的来说,useMemo
主要用于计算优化,以减少组件的重渲染成本;而useEffect
主要用于处理副作用,实现与应用的其他部分的交互。正确的使用这两个钩子,可以提高应用的性能和响应速度。
父子传值:useRef
子组件
1 | import React from 'react'; |
父组件:
1 | import React, { useRef } from 'react'; |
vue不仅仅是使用路由进行鉴权
promise.all如何在接受到rejected也成功返回?promise.race
答:通过catch
箭头函数
let const
vuex全局状态管理
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式和库。它以集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 非常适用于大型单页应用,它可以帮助你更好地在组件外部管理状态,让状态的变化更加可追踪和易于调试。
Vuex 的核心概念
State
- Vuex 使用单一状态树 —— 用一个对象就包含了全部的应用层级状态。每个应用将仅仅包含一个 store 实例。
- 状态存储是响应式的,Vue组件从 store 中读取状态,并且当状态发生变化时,相应的组件也会相应地得到高效更新。
Getters
- 类似于 Vue 的计算属性,getters 可以用于从 store 的 state 中派生出一些状态,例如过滤列表、计数等。
- Getters 会接受 state 作为其第一个参数,也可以接受其他 getters 作为第二个参数。
Mutations
- 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutations 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和一个 处理函数 (handler)。
- Mutation 必须是同步函数。
Actions
- Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
- Action 函数接收一个与 store 实例具有相同方法和属性的 context 对象,因此可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。
- Action 类似于 mutation,不同在于:
Modules
- Vuex 允许我们将 store 分割成模块(module),每个模块拥有自己的 state、mutation、action、getter,甚至是嵌套子模块 —— 从而允许我们对复杂的应用进行状态管理。
- 模块内部的 mutation 和 getter 接收的第一个参数是模块的局部状态对象。
Vuex 的使用场景
- 多个视图依赖于同一状态。
- 来自不同视图的行为需要变更同一状态。
Vuex 的使用示例
以下是一个简单的 Vuex store 的定义示例:
1 | import Vue from 'vue'; |
在组件中使用:
1 | <template> |
结论
Vuex 提供了一个集中存储所有组件状态的方式,并以规范的方式更新状态,易于维护和监控状态变化,特别适合处理大型、复杂的应用中多个组件共享状态的情形。
完美世界
介绍一下fiber
箭头函数和普通函数的区别
箭头函数是在ES6中引入的,提供了一种更简洁的方式来写函数表达式。箭头函数与普通函数(如函数声明或函数表达式)相比,有几个主要区别:
语法更简洁:
- 箭头函数提供了更短的函数语法。例如,一个普通的函数表达式
function(x, y) { return x + y; }
可以用箭头函数写为(x, y) => x + y
。
- 箭头函数提供了更短的函数语法。例如,一个普通的函数表达式
**没有自己的
this
**:- 在箭头函数中,
this
关键字具有其封闭执行环境的上下文,这与普通函数不同,普通函数的this
指向在运行时被调用时的上下文(如由一个对象调用)。 - 这使得箭头函数特别适合用在需要维持上下文的情况,如在回调函数和方法链中。
- 在箭头函数中,
不绑定
arguments
对象:- 箭头函数不提供
arguments
对象。如果你需要访问传给函数的参数列表,应使用剩余参数语法(...args
)代替。
- 箭头函数不提供
不可以使用
new
操作符:- 箭头函数不能用作构造函数,不可以使用
new
操作符,因为它们没有[[Construct]]
方法。尝试对箭头函数使用new
会抛出一个错误。
- 箭头函数不能用作构造函数,不可以使用
**不绑定
super
**:- 箭头函数不绑定
super
关键字。在使用类的继承时,这一点需要注意。
- 箭头函数不绑定
不能用作生成器函数:
- 箭头函数不能使用
yield
关键字,在其内部使用会导致语法错误。
- 箭头函数不能使用
总结来说,箭头函数主要用于那些不需要绑定自身 this
、不用作构造函数、并且更注重简洁性的场合。由于它们在功能上的这些限制,它们并不总是可以替代普通函数。
hooks可以写在if判断语句里吗
在React中,Hooks不能写在条件语句(如if
)、循环、嵌套函数或任何不是React函数组件顶层的地方。这是因为Hooks必须按照相同的顺序在每次组件渲染时被调用,以保证Hooks状态的正确性。将Hooks放在条件语句中使用可能会破坏这种顺序,导致不可预测的结果。
为什么Hooks不能放在条件语句中
React的Hooks功能依赖于调用顺序。每次组件渲染时,React按照Hooks被调用的顺序来维护和更新状态。如果你将Hook放入一个条件语句中,那么在某些渲染时Hook可能被调用,而在另一些渲染时可能不被调用,这将破坏React对Hooks调用顺序的跟踪。
错误示例
以下是一个错误的使用示例,展示了将useState
放在一个if
语句中的情况:
1 | import React, { useState, useEffect } from 'react'; |
这种用法会导致React无法正确追踪count
状态的变化,可能会引发运行时错误。
正确用法
如果你需要根据条件初始化状态或进行其他操作,你应该将条件逻辑放在Hook内部,而不是将Hook放在条件语句中。以下是一个改进的示例:
1 | import React, { useState, useEffect } from 'react'; |
在这个改进的例子中,useState
和useEffect
都被放在组件的顶层,并根据条件以适当的方式使用。这保证了Hooks的调用顺序是一致的,同时也能根据不同的条件执行不同的逻辑。
结论
总的来说,React Hooks应始终在函数组件的最顶层使用,无论是条件、循环还是嵌套函数都不应包含Hooks。遵守这一规则将帮助你避免在组件状态管理中遇到复杂和难以调试的问题。
computed与wacth有什么区别
ES6
原型和原型链
原型:prototype,在 JavaScript 中,几乎所有的对象都有一个“原型”。原型本身也是一个对象,被作为一个模板对象,它存储了可以被其他对象继承的属性和方法。当你创建一个对象时,这个对象自动引用一个原型,从中可以继承方法和属性。
例如,所有的 JavaScript 函数都有一个 prototype
属性,这个属性指向一个对象。当你使用构造函数创建一个新对象时(使用 new
关键字),新对象的内部 [[Prototype]]
属性(在大多数浏览器中可以通过 __proto__
访问)将被设置为构造函数的 prototype
对象。
例如:
1 | function Person(name) { |
原型链:prototype chain,原型链是一个对象通过其内部 [[Prototype]]
属性链接到其原型,而这个原型本身也可能有自己的原型,并从中继承方法和属性,这样的链条一直向上直到一个对象的原型为 null
。通常,原型链的终点是 Object.prototype
,这是所有纯粹对象默认的原型。
当你尝试访问一个对象的属性时(包括方法),JavaScript 首先查找对象本身是否有这个属性。如果没有找到,JavaScript 将沿着原型链向上查找,直到找到该属性或到达原型链的末端。
1 | function Person(name) { |
JavaScript 中的继承是通过原型和原型链实现的。这种机制允许对象共享方法和属性,而无需在每个对象上重新定义它们,这提高了效率并节省了内存。理解原型和原型链是深入理解 JavaScript 对象、继承和类行为的关键。
new的过程都干了些什么
在 JavaScript 中,new
操作符用于创建一个用户定义的对象类型的实例或具有构造函数的内置对象类型的实例。当你使用 new
操作符时,实际上会执行以下几个步骤:
创建一个新对象:
new
操作符首先会创建一个空的简单 JavaScript 对象;即一个新的空对象。
设置原型:
- 接着,这个新对象的内部
[[Prototype]]
(也就是__proto__
)属性会被赋值为构造函数的prototype
属性。这意味着新对象将继承构造函数原型上的方法和属性。
- 接着,这个新对象的内部
执行构造函数:
- 构造函数被调用,并将
this
绑定到新创建的对象上。这意味着在构造函数中你可以使用this
关键字来引用新对象,并给这个新对象添加属性和方法。
- 构造函数被调用,并将
返回新对象:
- 如果构造函数返回一个对象,则
new
表达式结果为这个对象;如果构造函数返回的不是对象(包括undefined
或任何原始类型的值),则返回新创建的对象。
- 如果构造函数返回一个对象,则
通过这些步骤,new
操作符不仅仅创建了一个对象实例,还正确设置了对象的原型链,这对于实现基于原型的继承非常关键。这使得实例可以访问构造函数原型上定义的方法和属性。例如:
1 | function Person(name) { |
在这个例子中,new Person("Alice")
创建了一个新的 Person
实例,并设置了 name
属性和 greet
方法。每次使用 new Person
时,都会按照这个模式创建一个新的对象。
事件循环
箭头函数和普通函数有什么区别
假设vue组件里一个ref的变量 然后再里渲染了它 然后去修改这个变量 那页面上为什么会修改这个变量呢? 介绍一下这个具体的过程
在 Vue.js 框架中,ref
是一个用于创建响应式引用的函数,这是 Vue 3 中 Composition API 的一部分。当你在一个 Vue 组件中使用 ref
声明一个变量,并在 <template>
中使用这个变量时,Vue 设置了一个响应式系统,来确保当这个变量的值改变时,视图也会自动更新。下面是这个过程的具体说明:
创建响应式引用:
使用ref
函数创建一个响应式变量时,Vue 会将这个变量包裹在一个响应式对象中。例如,const count = ref(0);
这里的count
实际上是一个带有value
属性的对象,count.value
初始化为 0。在模板中使用:
在 Vue 的<template>
中使用这个响应式变量时,可以直接引用它,如<div>{{ count }}</div>
。Vue 的渲染系统会自动处理ref
并访问其value
属性,因此你无需在模板中写count.value
。依赖跟踪:
当模板被渲染时,Vue 会自动跟踪所有被访问的响应式引用。这意味着 Vue 知道模板中的某部分依赖于count
变量。这是通过 Vue 的响应式系统内部的依赖跟踪和收集机制完成的,通常涉及一些内部的发布者-订阅者模式。响应式更新:
当你更新一个响应式引用的值时,如count.value = 1
,Vue 的响应式系统会察觉到这个变化。因为count
是响应式的,所以任何依赖于count
的视图或计算值都会被 Vue 的调度系统安排重新计算和更新。视图更新:
改变count.value
后,因为视图部分依赖于count
的值,Vue 将重新渲染与之相关的 DOM 元素。Vue 的虚拟 DOM 系统会计算之前和当前虚拟 DOM 树的差异,并高效地更新真实的 DOM 以反映新的状态。
通过这个过程,Vue 确保了界面的数据和视图之间的同步。这就是为什么在 Vue 组件中改变一个通过 ref
创建的变量的值会自动更新页面上相应部分的原因。这种模型极大地简化了动态界面的开发,使得开发者可以专注于数据逻辑而非如何操作 DOM。
垃圾回收(面试官反问了解v8吗,新生代分为哪几块区域,这几块儿区域是用来干什么的?这两块儿区域什么时候互换呢?互换了有什么好处吗?)
JavaScript 中的垃圾回收(Garbage Collection, GC)是一个自动的内存管理机制,它帮助开发者管理内存,避免内存泄露。垃圾回收的主要任务是识别并回收不再使用的内存,以便这部分内存可以被重新利用。
垃圾回收的基本概念
JavaScript 的垃圾回收主要是基于“可达性”(reachability)的概念:
- 可达值:从根(root)集合(通常是在代码中直接引用的变量)出发,可以访问到的值被认为是“可达的”。这些值不会被回收,因为它们可能在未来的执行过程中还需要被使用。
- 不可达值:如果一个值不可通过任何方式被根集合访问,那么这个值就被认为是“不可达的”,并可以被垃圾回收器回收。
V8 引擎的垃圾回收机制
V8 引擎是 Google 开发的 JavaScript 引擎,广泛应用于 Chrome 浏览器和 Node.js 环境。V8 的垃圾回收机制较为复杂,主要包括以下几个方面:
分代回收:
- 新生代(Young Generation):V8 将内存分为新生代和老生代两部分。新生代存放生命周期短的小对象。这部分内存的回收频率较高,使用 Scavenge 算法进行高效的垃圾回收。
- 老生代(Old Generation):存放生命周期长或从新生代晋升过来的对象。老生代的垃圾回收使用 Mark-Sweep(标记-清除)和 Mark-Compact(标记-整理)算法,以减少内存碎片。
停顿时间优化(Stop-The-World):
- 垃圾回收过程中,为了安全地清除不再需要的对象,JavaScript 的执行通常会暂停,这被称为“全停顿”(Stop-The-World)。V8 努力减少垃圾回收带来的停顿时间,通过增量标记(Incremental Marking)和延迟清扫(Lazy Sweeping)等技术,将垃圾回收工作分散到多个小周期中。
并行处理和并发处理:
- 在进行垃圾回收时,V8 尝试使用并行处理,以多线程的方式同时进行垃圾回收,以减少每次停顿时间。
- V8 还引入了并发回收(Concurrent GC),其中某些垃圾回收任务可以在 JavaScript 运行时并行进行,进一步减少对主线程的影响。
总的来说,V8 引擎的垃圾回收策略旨在平衡内存使用和回收效率,同时最大限度地减少对程序执行的干扰。这些技术使得在 V8 上运行的应用能够更加高效地管理内存,提供更好的用户体验。
闭包
ts:interface、type分别是什么时候使用
都是用来定义类型的,接口和类型别名
在 TypeScript 中,interface
和 type
都是用来定义类型的,但它们在某些方面有所不同,并且各自适用于不同的场景。下面是它们的主要区别以及使用场景的指导。
1. 基本区别
Interface(接口):
- 主要用于定义对象的形状,支持继承和实现(implements)。
- 接口是一种更强大的方式来定义包括方法和属性的契约。
- 支持声明合并(Declaration Merging),即允许同名的接口自动合并其成员。
Type(类型别名):
- 类型别名可以用来定义对象类型,也可以用来定义其他类型,如基本类型、联合类型、交集类型等。
- 类型别名不支持声明合并,即同名的类型别名不能重复声明,但可以通过交集和联合类型进行扩展。
- 类型别名更适合用于复杂的类型组合。
2. 使用场景
何时使用 Interface:
- 当你需要定义一个类或对象字面量的结构时,接口是首选。它们提供了一个清晰的方式来描述一个对象应有的形状。
- 当你在构建大型代码库或第三方类型定义(如 DefinitelyTyped)时,接口提供了一种易于扩展和重用的方式。
- 如果需要其他类或接口继承自该类型时,使用接口。接口支持多重继承。
何时使用 Type:
- 当你需要使用联合或交集类型来组合现有的类型时,类型别名是更好的选择。
- 当你需要为基本类型(如字符串或数字)定义一个别名时。
- 当你需要在一个表达式中使用泛型或其他复杂操作时,类型别名提供了更大的灵活性。
3. 实例比较
1 | // 使用 Interface |
4. 总结
虽然 interface
和 type
都可以在许多场景下互换使用,但它们各自的特性使得它们在特定的使用场景下更为合适。接口更适合于定义公共的API的对象结构,而类型别名在处理复杂的类型组合时则更加灵活。选择哪一个,取决于具体的应用场景和个人或团队的偏好。
枚举类型?
构建工具只用过webpack吗?有用过其他的吗?Vite的优势在哪?有没有改过它们的一些配置
webpack里的Loader与Plugin有什么区别
在 Webpack 中,Loader 和 Plugin 是两种扩展 Webpack 功能的方法,它们在使用和功能上有显著的区别。理解它们的不同之处对于有效地使用 Webpack 构建项目至关重要。下面是 Loader 和 Plugin 的主要区别及其各自的作用:
Loader
定义和作用:
- Loader 在 Webpack 中主要用于转换某些类型的模块。它们允许你在
import
或“加载”模块时预处理文件。因此,Loader 类似于一个“转换器”,用于处理非 JavaScript 文件(如 TypeScript, SCSS, JSX 等)。
工作方式:
- Loader 可以链式传递,每个 Loader 单独完成一项转换工作。
- 它们处理输入的源文件并产出新的源文件,这些文件可以传递给链中的下一个 Loader,或者输出到最终的 bundles 中。
配置方式:
- 在 Webpack 配置中,Loader 是通过
module.rules
数组进行配置的,可以指定多个 Loader,并且有顺序之分。
示例:
1 | module: { |
Plugin
定义和作用:
- Plugin 用于扩展 Webpack 的功能。它们直接访问 Webpack 的整个编译生命周期,并且可以执行更广泛的任务,如打包优化、资源管理和环境变量注入等。
工作方式:
- Plugin 通过钩子机制工作,可以监听 Webpack 构建过程中的各种事件,然后在这些事件发生时执行特定的函数来实现功能扩展。
配置方式:
- Plugin 在 Webpack 配置中通过
plugins
数组配置,通常是通过实例化 Plugin 类(使用new
关键字)来配置的。
示例:
1 | const HtmlWebpackPlugin = require('html-webpack-plugin'); |
总结
Loader 和 Plugin 虽然都是 Webpack 的功能扩展方式,但它们的应用场景和实现方式大不相同:
- Loader 更多地关注于资源模块的加载和处理,它们对文件内容进行转换。
- Plugin 提供了一种工作在构建过程更深层次的方法,能够处理更多构建流程中的广泛任务。
在实际使用 Webpack 进行项目构建时,合理利用 Loader 和 Plugin 可以极大地提高构建的灵活性和功能性。
TCP连接三次握手流程
项目里的图片压缩是怎么做的?
删除一个单向链表的倒数第n个节点,讲一下思路
有a和b两个变量,想把a和b交换,不能用中间变量和es6的解构赋值,请你讲一下思路
一般通过什么方式学前端? 如果可以的话 最快什么时间到岗 能实习到什么时候
使用websocket
在简历中描述实时通信功能的实现,尤其是使用 WebSocket 技术,是一个很好的机会来展示你的技术能力和对现代Web技术的掌握。WebSocket 是一种在单个TCP连接上进行全双工通信的协议,允许服务器主动发送信息给客户端,非常适合需要实时数据更新的应用,如在线聊天、游戏、实时通知系统等。确实,WebSocket 能够在不刷新页面的情况下实现实时通信。
实现原理: 实现了客户端与服务端的双向通信,只需要连接一次,就可以相互传输数据,很适合实时通讯、数据实时更新等场景。
Websocket 协议与 HTTP 协议没有关系,它是一个建立在 TCP 协议上的全新协议,为了兼容 HTTP 握手规范,在握手阶段依然使用 HTTP 协议,握手完成之后,数据通过 TCP 通道进行传输。
Websoket 数据传输是通过 frame 形式,一个消息可以分成几个片段传输。这样大数据可以分成一些小片段进行传输,不用考虑由于数据量大导致标志位不够的情况。也可以边生成数据边传递消息,提高传输效率。
优点
: 双向通信。客户端和服务端双方 都可以主动发起通讯。 没有同源限制。客户端可以与任意服务端通信,不存在跨域问题。 数据量轻。第一次连接时需要携带请求头,后面数据通信都不需要带请求头,减少了请求头的负荷。 传输效率高。因为只需要一次连接,所以数据传输效率高。
缺点
: 长连接需要后端处理业务的代码更稳定,推送消息相对复杂; 兼容性,WebSocket 只支持 IE10 及其以上版本。 服务器长期维护长连接需要一定的成本,各个浏览器支持程度不一; 【需要后端代码稳定,受网络限制大,兼容性差,维护成本高,生态圈小】
BFC
技术描述
前端开发工程师
[公司名称] | [日期]
- 实时通信实现:设计并实现了基于 WebSocket 的实时通信系统,支持无需页面刷新即时更新数据,增强用户交互体验。利用
Socket.IO
在多用户环境下同步状态和通知,确保信息的即时传递和显示。 - 动态用户界面响应:开发了响应式通知和消息系统,通过 WebSocket 接收后端推送的实时更新,动态调整前端显示,包括警告提示、状态更新等,无需用户手动刷新页面。
- 高效数据处理与优化:优化 WebSocket 数据传输和事件处理逻辑,降低了网络延迟和服务器负载,提升了系统的响应速度和可靠性。
- 跨浏览器兼容性和错误处理:确保 WebSocket 实现在所有主流浏览器上稳定运行,通过优雅的错误处理和重连策略增强了应用的健壮性。
技术细节
你可以进一步细化描述,如果面试官对这部分感兴趣,以下是你可以讨论的一些技术细节:
- WebSocket vs. 传统轮询:解释为什么选择 WebSocket 而非 HTTP 长轮询或短轮询,强调 WebSocket 的低延迟和减少服务器负载的优势。
- 实现细节:讨论如何使用
Socket.IO
(如果你使用了这个库)处理连接的建立、数据的接收和发送、断线重连等。 - 安全措施:描述如何保障 WebSocket 通信的安全性,例如使用 WSS(WebSocket Secure),以及如何管理和验证客户端。
这样的描述不仅展示了你在实现关键功能方面的技术能力,还突出了你对提升用户体验和应用性能的关注,是求职简历中的亮点之一。
postmessage
postMessage
是一个允许不同窗口之间或一个窗口与其嵌入的 <iframe>
或其他浏览器上下文之间安全地进行脚本通信的方法。这个功能是 Web HTML5 API 的一部分,特别重要在于它提供了一种方式来绕过同源策略的限制,即通常情况下,不同源的窗口不能彼此通信。
功能和用途
跨源通信:
postMessage
允许不同源之间的窗口(页面间或页面与嵌入的<iframe>
、<object>
、<embed>
或在不同标签页中的页面)进行数据交换。这对于加载了第三方内容的网页尤为重要。数据传输:
通过postMessage
,你可以发送多种类型的数据,包括字符串、对象等。发送的数据被结构化克隆,这意味着复杂的对象结构可以在不同的窗口或框架间完整传递。安全性:
postMessage
方法包括一个targetOrigin
参数,指定了哪些域名可以接收到消息,这是一个重要的安全特性。如果设为"*"
,则表示接受所有域的消息,但这通常不推荐,因为这可能带来安全风险。
使用示例
在一个简单的应用中,假设有一个父窗口和一个嵌入的 <iframe>
:
父窗口代码:
1 | // HTML |
在 <iframe>
页面中的代码:
1 | // JavaScript |
总结
postMessage
是一个强大的 API,用于在浏览器上下文之间进行安全、有效的数据通信。正确使用时,它既能保证数据交换的灵活性,也能确保通信的安全。因此,在开发涉及多个源或多窗口交互的现代 Web 应用时,postMessage
是不可或缺的工具。
Web Workers 和 WebSocket 的主要区别
Web Workers
Web Workers 提供一种将计算密集或阻塞性任务从主线程(通常是 UI 线程)中移出的方法,以避免阻碍 UI 的响应性。Web Workers 运行在后台线程中,不会影响页面的性能,特别适用于执行需要大量计算的任务。
特点和用途:
- 并行处理:Web Workers 允许在背景并行线程中运行 JavaScript,不干扰主 UI 线程。
- 性能优化:适用于执行复杂计算,如图像处理、大数据分析等,从而提升应用的响应速度和用户体验。
- 无 DOM 访问:Workers 运行在一个与主页面隔离的全局上下文中,不能直接操作 DOM。
WebSocket
WebSocket 提供了全双工通信机制,允许客户端和服务器之间建立一个持久的连接,并通过这个连接实时地双向传输数据。这对于需要实时数据更新的应用非常重要,如在线游戏、聊天应用或实时交易平台。
特点和用途:
- 实时通信:WebSocket 使得客户端和服务器可以在任何时候开始发送数据,支持更高效的实时数据交换。
- 减少开销:与传统的 HTTP 连接相比,WebSocket 在建立后可以保持连接开放,减少了需要建立连接的频繁开销。
- 双向通信:客户端和服务器都可以主动发送数据给对方,而不是仅由客户端发起请求。
相关性与区别
- 处理数据的方式不同:Web Workers 用于执行在客户端进行的计算密集型或阻塞性任务,而 WebSocket 主要用于在客户端和服务器之间实时交换数据。
- 用途和目的:Web Workers 是为了改善用户界面的性能和响应性,而 WebSocket 是为了实现网络应用中的实时数据传输。
- 交互性:WebSocket 通过网络连接实现实时、双向的数据交流,适用于任何需要与服务器实时通信的场景;Web Workers 主要是单向的从主线程分配任务,并在任务完成后可能返回结果,主要用于处理本地的数据和任务。
总的来说,虽然 Web Workers 和 WebSocket 都与数据处理有关,但它们服务的上下文和目的有很大的不同。Web Workers 解决的是计算性能问题,而 WebSocket 解决的是实时网络通信问题。
跨域的解决办法
1. CORS(Cross-Origin Resource Sharing): CORS是一种跨域资源共享的标准,通过服务端设置响应头中的Access-Control-Allow-Origin字段来允许跨域请求。服务端在收到跨域请求时,检查Origin字段,如果请求的源在白名单内,则在响应头中添加Access-Control-Allow-Origin字段,并设置为允许的源,从而允许跨域请求。
2. JSONP(JSON with Padding): JSONP是一种利用script标签的src属性没有跨域限制的特性来实现跨域请求的技术。通过动态创建script标签,将跨域请求的数据封装在一个回调函数中,然后由服务端返回,并执行回调函数,从而实现跨域数据的获取。
3. 代理服务器: 在开发环境中,可以通过配置代理服务器来实现跨域请求。代理服务器接收前端请求,然后在后端发起真正的请求,获取数据后再返回给前端,由于请求是由后端发起的,所以不存在跨域问题。
4. iframe跨域通信: 使用iframe标签可以实现跨域通信。父页面和iframe页面属于不同的源,但是它们之间可以通过postMessage方法来进行跨域通信,实现数据的传递和交互。
前端缓存可以分为两种类型:浏览器缓存和 Web Storage。
- 浏览器缓存:浏览器缓存是指浏览器在本地保存一些静态资源的副本,以便在下次访问相同资源时可以直接从本地获取,而不需要再次从服务器下载。浏览器缓存主要包括以下几种类型:
- 强缓存:浏览器在请求资源时,会先检查该资源的缓存标识(如 Cache-Control 和 Expires),如果命中强缓存,则直接从缓存中获取资源,不会发送请求到服务器。
- 协商缓存:如果资源的缓存标识表示资源已经过期(如 Cache-Control 中的 max-age 或者 Last-Modified 和 ETag 等),浏览器会发送请求到服务器,服务器会根据请求头中的条件判断来决定是否返回资源内容,如果返回 304 状态码表示资源未发生改变,浏览器可以使用缓存中的资源。
- Web Storage:Web Storage 是 HTML5 提供的一种浏览器本地存储数据的方式,主要包括 localStorage 和 sessionStorage 两种。它们可以存储在浏览器中供网站使用,可以在页面会话结束后依然保留(localStorage),或者在页面会话结束后被清除(sessionStorage)。
- localStorage:localStorage 存储的数据没有过期时间,除非手动清除,否则会一直保存在浏览器中。
- sessionStorage:sessionStorage 存储的数据在页面会话结束时被清除,即当页面被关闭时数据也会被清除。
WebSocket 是一种在单个 TCP 连接上进行全双工通讯的协议。WebSocket 通信协议于2011年被IETF定为标准RFC 6455,并由RFC7230补充规范。WebSocket 是 HTML5 标准的一部分,可实现客户端和服务器之间的持久连接,使服务器可以实时推送数据给客户端。
以下是一个使用Python语言结合Flask框架创建WebSocket服务器的简单示例:
from flask import Flask, render_template
from flask_socketio import SocketIO
app = Flask(name)
app.config[‘SECRET_KEY’] = ‘secret!’
socketio = SocketIO(app)
@app.route(‘/‘)
def index():
return render_template(‘index.html’)
@socketio.on(‘message’, namespace=’/chat’)
def handle_message(message):
print(‘Received message: ‘ + message)
socketio.emit(‘message’, message, namespace=’/chat’)
if name == ‘main‘:
socketio.run(app, debug=True)
在这个例子中,我们使用了Flask和Flask-SocketIO库来创建一个简单的WebSocket服务器。我们定义了一个路由/,当用户访问这个路由时,他们会看到一个index.html页面。我们还定义了一个事件处理程序handle_message,它会在收到客户端发送的消息时触发。
在客户端,你可以使用JavaScript的WebSocket API或者利用类似于jQuery的JavaScript库来轻松地建立和管理WebSocket连接。
以下是一个使用JavaScript和jQuery创建WebSocket客户端的简单示例:
在这个HTML页面中,我们使用jQuery库来处理DOM元素,并通过WebSocket与服务器进行通信。当用户在文本输入框中输入消息并点击发送按钮时,我们通过WebSocket发送消息到服务器。同时,我们也监听来自服务器的消息,并在控制台打印出来。
以上就是一个简单的WebSocket服务器和客户端的实现。在实际应用中,你可能需要处理更多的逻辑,例如用户认证、错误处理、消息的可靠传递等。
JWT
JWT(JSON Web Tokens)是一种开放标准(RFC 7519),用于在网络应用环境间安全地传递信息。它主要以一种紧凑且自包含的方式,在各方之间作为 JSON 对象安全地传输信息。这些信息可以被验证和信任,因为它是数字签名的。JWT 常用于身份验证和信息交换,特别适用于分布式站点的单点登录(SSO)场景。
JWT 的结构
JWT 通常包含三部分:头部(Header)、载荷(Payload)和签名(Signature)。
头部(Header):
- 头部通常由两部分组成:token 的类型(即 JWT)和所使用的签名算法(如 HMAC SHA256 或 RSA)。
- 示例:
1
2
3
4{
"alg": "HS256",
"typ": "JWT"
}
载荷(Payload):
- 载荷包含声明(Claims)。声明是关于实体(通常是用户)和其他数据的语句。
- 有三种类型的声明:注册的声明、公共的声明和私有的声明。
- 示例:
1
2
3
4
5{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
签名(Signature):
- 为了创建签名部分,你必须取头部的编码、载荷的编码,然后用头部中指定的算法进行签名。
- 示例:
1
2
3HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret)
JWT 的使用方式
在身份验证的过程中,JWT 通常如下使用:
用户认证:
- 用户通过登录凭证(用户名和密码)登录系统。
- 系统验证凭证的正确性,并创建一个 JWT,其中包含用户的一些信息,并对其进行签名,以证明该 token 在传送过程中未被篡改。
发送 JWT:
- Token 通常通过 Bearer schema 通过 HTTP 授权头部发送给用户,格式如下:
Authorization: Bearer <token>
。
- Token 通常通过 Bearer schema 通过 HTTP 授权头部发送给用户,格式如下:
服务端验证:
- 服务端或者需要验证用户身份的应用将接收到的 token 进行解码,验证签名是否有效。
- 验证 token 是否过期或者符合其他的业务规则。
JWT 的优点
- 紧凑:JWT 可以通过 URL、POST 参数或在 HTTP 头中发送,因其小巧,网络传输效率高。
- 自包含:JWT 包含了所有用户需要的信息,避免了多次查询数据库。
JWT 的缺点
- 存储问题:JWT 一旦签发在有效期内就一直有效,这可能导致一旦 JWT 被盗用,直到过期都无法废止(除非使用黑名单机制)。
- 体积较大:相比于传统的 session ID,JWT 因包含较多信息,体积较大,如果包含过多信息则会影响传输效率。
- 安全性问题:如果不使用 HTTPS,JWT 由于容易被截取,因此安全性会降低。此外,如果使用不当(如签名密钥泄露),也可能导致安全风险。
JWT 是一种非常流行的在服务之间安全地传递信息的方式,但需要注意安全实践,如使用 HTTPS 和定期更新签名密钥,以保证信息安全。
小米
文件上传,localstorage保存本地
盒模型
css左右宽度固定,中间自适应,flex:1不同的属性是什么意思
var函数作用域
事件循环,看代码输出
树的转化,
useEffect函数配合return 输出顺序
虚拟列表的渲染?实现,视窗
文件上传localstorage,sessionstorage,cookie区别
微前端,iframe和qiankun的区别,样式隔离如何做的?别的微前端实现方式?
伪类和伪元素
string和new 创建的string一样吗?
apply bind call 修改this的指向如何实现?实现代码
HTML5+CSS
是的,Web Workers 和 WebSocket 都是 HTML5 提出的重要特性。它们各自解决了 web 应用开发中的不同问题,使得现代 web 应用能够更加高效和动态。
Web Workers
Web Workers 提供了一种在浏览器后台执行脚本的方法,而不会干扰主线程的用户界面。它允许开发者创建多线程环境,用于处理计算密集或耗时的任务,从而提升应用性能和响应速度。
特点:
- 并行处理:Web Workers 允许 JavaScript 运行在与主线程分离的后台线程中,这意味着可以并行处理复杂任务,不会造成界面卡顿。
- 非阻塞:由于运行在独立线程,即使执行复杂的计算,也不会阻塞 UI 更新或用户交互。
- 通信:主线程和 Workers 之间通过传递消息的方式进行数据交互,它们不共享同一内存空间,因此数据在互传过程中是复制的。
WebSocket
WebSocket 提供了一种在客户端和服务器之间建立持久连接的方法,通过这个连接,可以实现双向实时通信。与传统的 HTTP 连接不同,一旦 WebSocket 连接建立,服务器和客户端之间可以随时互发消息,无需重新建立连接。
特点:
- 实时通信:WebSocket 允许服务器主动向客户端发送消息,非常适合需要实时数据更新的应用,如在线游戏、实时交易平台、协作应用等。
- 减少开销:WebSocket 连接建立后,数据帧的传输开销比 HTTP 小得多,因为避免了频繁的握手和头部信息的重复传输。
- 全双工通信:WebSocket 支持全双工通信,客户端和服务器可以同时发送和接收信息。
HTML5 和现代应用
Web Workers 和 WebSocket 都是 HTML5 标准的一部分,是现代 web 应用常用的技术。它们的引入标志着 web 应用的一个重要进步,让 web 应用在性能和用户体验上更接近传统的桌面应用。
- Web Workers 使得开发者可以创建更加平滑和响应快速的用户界面,即使在执行重任务时也不会影响到主 UI 线程。
- WebSocket 则改变了客户端与服务器交互的方式,为实时多用户功能提供了基础,大大增强了应用的互动性。
这两种技术各有用武之地,选择使用哪一种,取决于应用的具体需求:是否需要后台处理能力,或是需要实时的网络通信。
盒模型
标准盒模型:content-box
怪异盒模型:border-box
左右布局
水平垂直居中布局
使用Flex布局
实现方式: 使用CSS的Flex布局可以轻松实现水平垂直居中。通过设置容器的display: flex; justify-content: center; align-items: center;
,即可实现容器内元素的水平垂直居中。
使用绝对定位和transform属性
实现方式: 使用CSS的绝对定位和transform属性也可以实现水平垂直居中。通过设置元素的position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
,即可实现元素的水平垂直居中。
使用表格布局
实现方式: 使用CSS的表格布局也可以实现水平垂直居中。通过设置容器和元素的display: table; display: table-cell; vertical-align: middle; text-align: center;
,即可实现元素的水平垂直居中。
实现方式: 使用Flex布局的margin:auto方法也可以实现水平垂直居中。通过设置容器的display: flex;
,然后在需要居中的元素上添加margin: auto;
,即可实现元素的水平垂直居中。
使用Flex布局的margin:auto方法
flex:1是什么属性的缩写?
移动端适配
viewport
rem
动画:transition、animation
在 CSS 中,动画可以通过两种主要方式实现:transition
和 animation
。这两种方法各有特点,适用于不同的场景。
CSS Transitions
CSS transition
属性用于在 CSS 属性值之间创建平滑的过渡效果。当一个 CSS 属性改变时,transition 会使这个属性值的变化更加平滑和渐进。
使用场景
- 适用于简单的从一种样式过渡到另一种样式的情况。
- 常用于响应用户的交互,如悬停、点击或聚焦。
基本语法
1 | .element { |
- property:指定要过渡的 CSS 属性(如
all
表示所有可过渡的属性)。 - duration:过渡效果花费的时间,通常以秒或毫秒表示。
- timing-function:定义速度曲线,常用的有
linear
、ease
、ease-in
、ease-out
、ease-in-out
。 - delay:延迟时间,即在过渡开始之前等待的时间。
示例
1 | div { |
在此示例中,div
的背景色会在鼠标悬停时在 2 秒内平滑地从蓝色过渡到红色。
2. CSS Animations
CSS animation
属性允许动画从一个样式配置过渡到另一个。与 transition
相比,animation
提供了对动画过程更细致的控制,包括多个关键帧和复杂的动画序列。
使用场景
- 适用于复杂的动画效果,需要在多个阶段或多个样式值之间进行过渡。
- 可以在无需触发事件的情况下自动开始。
基本语法
首先,你需要定义关键帧(@keyframes
):
1 | @keyframes example { |
然后,将动画应用于元素:
1 | div { |
- animation-name:引用
@keyframes
名称。 - animation-duration:动画持续时间。
- animation-timing-function:速度曲线。
- animation-delay:延迟开始的时间。
- animation-iteration-count:动画重复次数。
- animation-direction:动画是否应该反向播放。
- animation-fill-mode:在动画前后如何应用样式。
示例
1 | @keyframes changeSize { |
在这个示例中,div
将不断地在原始大小和放大 50% 之间变化。
总结
- CSS Transitions 最适合用于单一状态变化的简单动画效果。
- CSS Animations 更适合于需要多个状态或更复杂动画效果的场景。
使用这些工具,你可以为网页元素添加视觉上吸引人的动效,提高用户体验。
Javascript
事件代理
冒泡、目标、捕获
对很多个列表事件进行处理然后才需要最后的结果的时候,可以使用事件代理
事件循环
同步任务、宏任务、微任务
浏览器内存
Cookies、LocalStorage 、SessionStorage
这些技术各自有不同的用途和限制:
1. Cookies
Cookies 最初被设计用来存储少量的用户和服务器间的会话数据。它们由服务器发送到用户的浏览器,并且可以设置为在浏览器再次发起请求时被送回服务器。
特点:
- 大小限制:每个 Cookie 的大小限制约为 4KB。
- 数量限制:每个域的 Cookie 数量也有限制,通常为 20-50 个不等,具体取决于浏览器。
- 过期时间:可以设置失效日期,过期后浏览器不再存储。
- 安全性:支持设置 HttpOnly 属性,阻止 JavaScript 通过
document.cookie
访问,增强安全性。还可以设置 Secure 属性,使 Cookie 仅在 HTTPS 连接中被发送。
2. LocalStorage
LocalStorage 是 HTML5 引入的一种存储方式,用于长期存储数据,即使浏览器关闭后数据仍然保存。
特点:
- 存储容量:通常提供至少 5MB 的存储空间。
- 生命周期:数据存储在本地,没有过期时间,除非被用户或应用程序清除。
- 作用域:数据保存在浏览器中,且仅限于同源的页面(即相同协议、域名、端口)访问。
3. SessionStorage
SessionStorage 与 LocalStorage 类似,也是 HTML5 提供的一种机制,但它是为了存储仅在浏览器会话期间需要保持的数据。
特点:
- 存储容量:通常提供至少 5MB 的存储空间。
- 生命周期:数据仅在浏览器会话期间可用(浏览器窗口打开期间)。当用户关闭浏览器窗口或标签页时,数据被清除。
- 作用域:与 LocalStorage 类似,数据的访问也仅限于同源的页面。
比较与选择
- 用途:Cookies 更适合执行需要与服务器交互的身份验证任务;LocalStorage 适合长期存储大量不需要经常更改的数据,如用户偏好设置;SessionStorage 适合在单个会话中暂存数据,如表单填写状态。
- 性能:与 Cookies 相比,LocalStorage 和 SessionStorage 提供了更快的数据访问速度,因为它们的数据不需要每次与服务器通信。
- 安全性:考虑到安全性,应该避免在客户端存储敏感数据,尤其是未加密的情况下。如果必须使用,应该采取措施如加密数据以增强安全性。
根据应用的需要,开发者可以选择最合适的浏览器存储技术来优化用户体验和应用性能。
ES6的理解
数据类型和判断方式
箭头函数,map、set,块级作用域,
数据遍历的方式
基本类型的遍历
基本数据类型(如数字、字符串、布尔值等)通常不需要遍历,因为它们是单个值。不过,字符串类型可以类似于数组进行索引访问和遍历。
字符串遍历
使用传统的
for
循环:1
2
3
4let str = "hello";
for (let i = 0; i < str.length; i++) {
console.log(str[i]);
}使用
for...of
循环:1
2
3
4let str = "hello";
for (let char of str) {
console.log(char);
}
对象遍历
对于 JavaScript 对象(包括数组和更复杂的对象类型),有多种遍历方法:
1. for...in
循环
用于遍历对象的所有可枚举属性(包括继承的属性)。
1
2
3
4const obj = { a: 1, b: 2, c: 3 };
for (let key in obj) {
console.log(key, obj[key]);
}注意:使用
for...in
遍历对象时,最好检查属性是否是对象本身的属性(非继承属性):1
2
3
4
5for (let key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(key, obj[key]);
}
}
2. Object.keys()
, Object.values()
, Object.entries()
这些 ES6 方法提供了返回对象属性的数组,可以与 forEach()
或其他迭代方法配合使用:
Object.keys()
返回一个包含对象自身所有可枚举属性键的数组。1
2
3Object.keys(obj).forEach(key => {
console.log(key, obj[key]);
});Object.values()
返回一个包含对象自身所有可枚举属性值的数组。1
2
3Object.values(obj).forEach(value => {
console.log(value);
});Object.entries()
返回一个给定对象自身可枚举属性的键值对数组。1
2
3Object.entries(obj).forEach(([key, value]) => {
console.log(key, value);
});
3. for...of
循环与 Object.entries()
使用
for...of
遍历Object.entries()
返回的键值对数组是遍历对象属性的另一种实用方式:1
2
3for (let [key, value] of Object.entries(obj)) {
console.log(key, value);
}
总结
遍历数据的方法应根据数据的类型和需要操作的数据结构来选择。字符串虽然是基本类型,但可以像数组一样遍历。对象的遍历则可以使用 for...in
循环,但要注意只遍历对象自身的属性。ES6 的 Object.keys()
, Object.values()
, Object.entries()
提供了更现代的遍历对象的方法。选择合适的遍历方式可以使代码更简洁、更有效率。
new的过程
在 JavaScript 中,new
操作符是用来创建一个用户定义的对象类型的实例或具有构造函数的内置对象类型的实例。new
做的事情可以分解为以下几个步骤:
1. 创建一个新对象
当你使用 new
操作符时,JavaScript 首先会创建一个空的对象。
2. 设置原型链
接着,这个新对象的内部 [[Prototype]]
(也就是 __proto__
)会被赋值为构造函数的 prototype
属性。这意味着新对象可以访问构造函数原型上的属性和方法。
3. 绑定 this
关键字
在构造函数内部,this
关键字引用的是刚刚创建的新对象。如果构造函数中有代码修改了 this
,它会修改新对象的属性。
4. 执行构造函数的代码
构造函数内部的代码将会执行。通常情况下,构造函数会初始化对象的属性,也可能会调用其它方法。
5. 返回新对象
在构造函数执行完毕后,如果构造函数返回一个对象,那么这个对象会成为 new
表达式的结果,如果构造函数没有显式返回一个对象,则默认返回 this
,即新创建的对象。
示例:
1 | function Person(name, age) { |
在这个例子中,new Person('John', 30)
做了以下事情:
- 创建了一个新对象。
- 将新对象的
__proto__
指向了Person.prototype
。 - 将
this
绑定到新对象上。 - 执行
Person
函数体内的代码,将 ‘John’ 赋值给this.name
,将 30 赋值给this.age
并定义了this.greet
方法。 - 返回了这个新对象,赋值给了
john
变量。
结论
new
操作符在 JavaScript 中是一个强大的特性,它允许开发者基于构造函数创建复杂的对象。了解 new
的内部工作原理是理解 JavaScript 面向对象编程的一个重要部分。
promise的理解和静态方法
在 JavaScript 中,Promise
是一种用于异步编程的重要构造函数。它允许你组织和管理异步操作更加简洁和易于理解。Promise 代表了一个最终可能完成或失败的操作和其结果值。
一个 Promise
对象代表了一个异步操作的最终完成(或失败)及其结果值。一个 Promise 有以下三种状态:
- Pending(等待态):初始状态,既不是成功,也不是失败状态。
- Fulfilled(已成功):意味着操作成功完成。
- Rejected(已失败):意味着操作失败。
Promise 提供了一种逃避地狱回调(Callback Hell,即多层嵌套的回调函数)的方式,使异步代码更容易写和理解。
使用 Promise
创建一个 Promise并模拟异步操作:
1 | let promise = new Promise(function(resolve, reject) { |
使用 Promise:
1 | promise.then( |
Promise 的静态方法
Promise 提供了几个静态方法,这些方法有助于控制多个异步操作。
Promise.resolve(value)
- 返回一个状态由给定值解析后的 Promise 对象。如果该值是 thenable(即有
then
方法),返回的 Promise 将“跟随”这个 thenable 的状态;否则返回的 Promise 将以此值完成。
- 返回一个状态由给定值解析后的 Promise 对象。如果该值是 thenable(即有
Promise.reject(reason)
- 返回一个状态为拒绝的 Promise 对象。
Promise.all(iterable)
- 此方法通常用于处理多个 Promise 对象的情况。它返回一个新的 Promise 实例,该实例在 iterable 参数内的所有 Promise 都成功完成时完成。如果任意一个 Promise 失败,返回的 Promise 便立即失败。
1
2
3
4
5Promise.all([Promise1, Promise2]).then(function(results) {
// 所有 Promise 都成功
}, function(reason) {
// 有一个 Promise 失败
});Promise.race(iterable)
- 当 iterable 参数里的任意一个子 Promise 被成功或失败后,父 Promise 立即也会用子 Promise 的成功返回值或失败详情作为参数调用父 Promise 绑定的相应句柄,并返回该 Promise 对象。
Promise.allSettled(iterable)
- 返回一个在所有给定的 Promise 已被决议或拒绝后的 Promise,并带有一个对象数组,每个对象表示对应的 Promise 结果。
使用这些方法可以有效地组合和管理多个异步操作,提高代码的可读性和易维护性。
结论
Promise 是现代 JavaScript 中处理异步操作的核心组件之一。通过使用 Promise 和其静态方法,你可以写出更清晰、更可维护的异步代码。理解和掌握 Promise 是成为一名效率高、代码质量好的 JavaScript 开发者的关键步骤。
跨域
CORS(Cross-Origin Resource Sharing): CORS是一种跨域资源共享的标准,通过服务端设置响应头中的Access-Control-Allow-Origin字段来允许跨域请求。服务端在收到跨域请求时,检查Origin字段,如果请求的源在白名单内,则在响应头中添加Access-Control-Allow-Origin字段,并设置为允许的源,从而允许跨域请求。
JSONP(JSON with Padding): JSONP是一种利用script标签的src属性没有跨域限制的特性来实现跨域请求的技术。通过动态创建script标签,将跨域请求的数据封装在一个回调函数中,然后由服务端返回,并执行回调函数,从而实现跨域数据的获取。
代理服务器: 在开发环境中,可以通过配置代理服务器来实现跨域请求。代理服务器接收前端请求,然后在后端发起真正的请求,获取数据后再返回给前端,由于请求是由后端发起的,所以不存在跨域问题。
iframe跨域通信: 使用iframe标签可以实现跨域通信。父页面和iframe页面属于不同的源,但是它们之间可以通过postMessage方法来进行跨域通信,实现数据的传递和交互。
WebSocket协议: WebSocket是一种全双工通信协议,能够在不受同源策略限制的情况下实现跨域通信。通过WebSocket协议,客户端和服务端可以建立持久的连接,实现实时数据的传输和交互。
nginx
postmessage
垃圾回收
标记清除、引用计数
Typescript
interface、type和泛型
在 TypeScript 中,interface
和 type
都是定义类型的重要工具,它们在很多情况下可以互换使用,但也有一些关键的区别。了解这些区别有助于你在实际开发中更好地选择适合的语法。
相似点
- 定义形状:
interface
和type
都可以用来定义对象的形状,即指定对象应该有哪些属性以及属性的类型。 - 扩展其他类型:两者都可以扩展其他类型,
interface
使用extends
关键字,而type
使用交叉类型(&
)。
不同点
扩展机制:
- interface:可以声明多次,并且会被自动合并。这允许你在不同的地方扩展同一个接口。
- type:不能声明多次(即一旦被声明,不能再被重新声明来添加新的属性),但它可以通过交叉类型来扩展其他类型或别名。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// Interface 自动合并
interface User {
name: string;
}
interface User {
age: number;
}
// User 现在有 name 和 age 属性
// Type 不可以重复声明,但可以通过 & 扩展
type Animal = {
species: string;
}
type Bear = Animal & {
honey: boolean;
}使用
type
可以声明基本类型别名,联合类型,元组等:type
不仅仅限于对象类型。它可以用来声明几乎所有类型,包括基本类型别名、联合类型、元组等。interface
主要用于声明对象类型或函数类型。
1
2type StringOrNumber = string | number;
type TupleType = [string, number];实现细节:
- interface:更加面向对象的编程风格,支持实现(implements)和扩展(extends)。
- type:更灵活,支持使用联合、元组等更复杂的类型结构。
1
2
3
4
5
6
7
8
9
10
11
12interface IAnimal {
species: string;
}
class Dog implements IAnimal {
species = "Canis";
}
type Point = {
x: number;
y: number;
};
let drawPoint = (point: Point) => { /*...*/ };扩展/合并限制:
- interface:允许开放结束的扩展,因为你可以在程序的任何地方添加到现有接口。
- type:定义是封闭的,一次声明定义全部完成。
选择使用哪一个?
- 使用
interface
当你需要一个对象类型或者在面向对象风格的代码库中(如当与类一起使用时)。 - 使用
type
当你需要利用联合类型或元组类型等复杂类型,或者需要对类型进行条件选择。
在实践中,如果两者都可以满足需求,推荐优先选择 interface
,因为它的扩展性更好,更适合在大型项目中使用,能够提供更好的代码组织和更清晰的错误信息。然而,type
的灵活性在处理复杂的类型组合时是无可替代的。
对泛型属性约束:
1 | interface Lengthwise { |
Vue
2和3双向数据绑定的区别理解
object.propert
生命周期
watch和computed
watch:不缓存,实时更新
computed:缓存
v-if、v-show
if是条件渲染
show是通过css的display
组件通信
props和$emit,provide和inject,Event Bus,vuex,
vuex
state、getters、mutations、actions、modules
1. State
State 是 Vuex 存储的基础,是存储在 Vuex 中的数据。它是单一状态树 —— 所有组件的状态都被集中到一个共享的对象中。
1 | const store = new Vuex.Store({ |
在组件内部,你可以通过 this.$store.state
访问 Vuex state。
2. Getters
Getters 类似于 Vue 的计算属性,主要用于基于 Vuex store 的 state 计算出新的状态。Getters 可以被多个组件复用,且当它依赖的 state 变化时,会自动重新计算。
1 | const store = new Vuex.Store({ |
在组件内部,可以通过 this.$store.getters.doneTodos
访问到这个 getter。
3. Mutations
Mutations 是改变 Vuex store 中 state 的唯一方法。Mutation 必须是同步函数,这样能够确保每次状态变化都能被调试工具追踪到。
1 | const store = new Vuex.Store({ |
在组件中使用 this.$store.commit('increment')
来触发 mutation。
4. Actions
Actions 类似于 mutations,但是不同于 mutations 的是,actions 可以包含任意异步操作。Actions 提交的是 mutation,而不是直接变更状态。Actions 可以包含任意异步操作。
1 | const store = new Vuex.Store({ |
在组件中使用 this.$store.dispatch('incrementAsync')
来触发 action。
5. Modules
当 Vuex 应用变得非常复杂时,store 对象可能变得非常臃肿。Modules 可以将 store 分割成模块,每个模块拥有自己的 state、mutations、actions、getters,甚至是嵌套子模块。
1 | const moduleA = { |
每个 Vuex 模块的 state 会被合并到 Vuex 的全局 state 中,并根据模块注册的路径分割命名空间。
这些核心概念共同定义了 Vuex 的结构和机制,使得状态管理更加清晰、高效,并且更易于维护和扩展。
路由
history和哈希模式
React
Webpack
loader和plugin的区别
Loader
Loader 用于转换特定类型的模块或文件。Webpack 本身只理解 JavaScript,Loader 可以让 Webpack 有能力去处理其他类型的文件,并将它们转换为有效的模块,以供应用程序使用以及被添加到依赖图中。
主要特点:
- 文件转换:Loader 可以把文件从不同的语言(如 TypeScript)转换为 JavaScript,或将内联图像转换为 data URL。Loader 甚至允许你直接在 JavaScript 模块中 import CSS文件。
- 链式调用:Loader 可以链式调用。意味着可以应用多个 loader,它们会按顺序从右到左(或从下到上)应用。
- 每个文件单独处理:Webpack 为每个文件根据配置的 loader 规则进行处理。
示例:使用 css-loader
和 style-loader
处理 CSS 文件。
1 | module: { |
Plugin
Plugin 用于扩展 Webpack 的功能。它们直接作用于整个构建过程,可以执行范围更广的任务,如打包优化、资源管理和环境变量注入等。
主要特点:
- 扩展性强:插件可以用于执行几乎任何任务,包括打包优化、定义环境变量、压缩、等等。
- 直接作用于整个构建流程:Plugin 可以在构建的任何阶段执行操作,提供了完整的构建流程控制。
- 自定义功能:开发者可以编写自己的 Webpack 插件,以实现更具体的需求。
示例:使用 HtmlWebpackPlugin
,它可以生成一个 HTML 文件,或者利用模板生成 HTML 文件,并自动注入生成的资源。
1 | const HtmlWebpackPlugin = require('html-webpack-plugin'); |
区别总结
- Loader 主要用于对模块的源代码进行转换。Loader 可以使你在 import 或”加载”模块时预处理文件。因此,loader 类似于“任务”是文件级别的处理工具。
- Plugin 影响的是整个构建流程。Plugin 可以用于执行广泛的任务,比如打包优化、资源管理和环境变量注入等,影响构建结果。
在实际应用中,理解 Loader 和 Plugin 的工作方式及其区别,可以帮助你更好地控制 Webpack 的行为,优化构建过程,提高应用性能。
Vite和webpack的区别
在现代前端开发中,构建工具扮演着至关重要的角色,它们帮助开发者处理模块化、编译、打包、优化等任务。Webpack
是最流行的构建工具之一,但除此之外还有许多其他的工具,如 Vite
、Parcel
、Rollup
等。每种工具都有其特定的用途和优势。下面我将简要介绍这些工具,并突出 Vite
的优势。
Webpack
Webpack 是一个模块打包器(bundler)。它适用于大型复杂的网页应用,通过 loader 和 plugins 提供了强大的整合能力。
特点:
- 处理依赖:可以将几乎任何类型的文件作为模块处理。
- 丰富的插件系统:通过插件,可以扩展 webpack 的功能。
- DevServer:提供一个简单的 web 服务器和实现热重载(HMR)。
- Splitting:代码分割支持,有助于优化加载时间。
Vite
Vite 是一种新兴的前端构建工具,由 Vue.js 的创作者开发。它利用现代浏览器的原生 ES 模块导入(ESM)功能来提供快速的开发环境启动。
优势:
- 快速的冷启动:不需要打包操作,可以快速启动开发环境。
- 即时的模块热更新(HMR):相比传统的打包系统,HMR 更快,因为没有打包过程。
- 按需编译:只有当请求时才编译模块,没有额外的开销。
- 内置的优化:构建生产环境时,Vite 使用 Rollup 打包,有效利用了 Rollup 的高效代码分割和动态导入功能。
Parcel
Parcel 是另一种构建工具,以其零配置为特色,非常适合简单的项目和快速原型开发。
特点:
- 零配置:默认配置处理广泛的开发场景。
- 自动转换:支持许多文件类型,自动安装需要的转换器。
Rollup
Rollup 是专注于 JavaScript 库的打包,强调的是输出更少的代码和更有效的摇树优化(Tree-shaking)。
特点:
- 高效的 Tree-shaking:减少冗余代码,输出更小的文件。
- 适合库的打包:输出的文件更适合作为库分发。
改动配置
对于这些构建工具,实际开发中通常需要修改其默认配置以适应特定项目的需求。例如:
- Webpack:可能需要添加 loader 处理新的文件类型,或修改 plugins 来优化打包结果。
- Vite:可能需要配置基础公共路径(base),或者插件支持更多框架和文件处理。
- Parcel & Rollup:虽然 Parcel 通常不需要配置,但有时可能需要指定打包方式或代理配置;Rollup 则可能需要配置插件来支持 CommonJS 模块转换或其他语言特性。
结论
选择哪个构建工具取决于项目的需求、团队的熟悉度以及期望从工具中获得的支持。Vite 的优势在于其极速的开发启动时间和按需编译,特别适合用在现代 web 开发环境中。而传统的工具如 Webpack 仍然在功能丰富性和适用于大型复杂项目方面占有一席之地。
vue的template如何进行转化成dom节点的?
给定许多个button如何设置只选择一个节点,当我点击一个按钮的时候,上一个按钮的事件消失,这个按钮触发点击事件
如何虚拟优化
如何查看窗口的大小
如何获取div窗口的大小
ts:interface、type,enum的转化是什么?
ES6知道什么
new
箭头函数
原型和原型链
promise的状态
事件循环的理解
$nexttick
diff算法
watch和computed
数据的双向绑定理解跨域
template渲染的过程?
vue3的setup,ref和reactive
js的垃圾回收,v8的垃圾回收
JavaScript 的 V8 引擎是谷歌开发的开源 JavaScript 引擎,主要用于 Chrome 浏览器和 Node.js 环境中。V8 引擎的内存回收机制是理解其性能管理的关键部分。这种机制帮助确保非活动的或不再需要的对象能够被有效地清除,从而释放内存资源。以下是 V8 内存回收的几个关键概念:
垃圾收集器的工作原理:
- V8 使用了一种名为“分代式垃圾回收”(Generational Garbage Collection)的方法。这种方法基于一个观察:大多数对象在内存中存活的时间很短。
- V8 将内存分为两个主要区域:新生代(New Space)和老生代(Old Space)。新生代存放生命周期短的对象,而老生代则存放生命周期长或从新生代中晋升的对象。
新生代的垃圾回收:
- 新生代区域相对较小,通常只占总内存的一小部分。V8 使用 Scavenge 算法来进行新生代的垃圾回收,这是一种简单高效的复制算法。
- 当进行新生代垃圾回收时,V8 把新生代内存分为两半:活动半区和闲置半区。活动的对象被复制到闲置半区,而非活动的对象则被释放。
老生代的垃圾回收:
- 老生代的垃圾回收更复杂,因为这些对象通常更大,且数量也更多。
- V8 主要使用 Mark-Sweep(标记-清除)和 Mark-Compact(标记-整理)算法来进行老生代的垃圾回收。标记-清除算法用于标记活动的对象并清除未标记的对象,而标记-整理算法在此基础上还会移动对象,以减少内存碎片。
增量标记:
- 为了避免长时间的垃圾回收导致的应用停顿,V8 实现了增量标记(Incremental Marking)。在这种模式下,垃圾回收的标记过程会被分割成多个小部分,穿插在 JavaScript 应用逻辑的执行中。
延迟清理和并行回收:
- V8 也引入了延迟清理(Lazy Sweeping)和并行回收,这样可以在多个后台线程中进行垃圾回收,进一步减少对主线程的影响。
了解这些内存回收的机制可以帮助开发者优化 JavaScript 应用的内存使用,避免内存泄漏,并提高应用的性能和响应速度。在编写高性能的应用时,理解和适应 V8 的内存回收策略是非常重要的。
webpack和vite
快排
链表
如何不使用递归实现斐波那契数:迭代
内存–分配、使用、回收-引用计数、标记清除–标记清楚、标记整理、增量标记
理解计算机内存涉及多个层面,包括硬件级别的物理内存和操作系统以及应用程序(如浏览器)在软件级别对内存的管理。下面详细解释这些层面。
物理内存(硬件)
物理内存,通常称为随机存取存储器(RAM),是计算机的主要存储媒介之一。它用于临时存储正在运行的程序和当前使用的数据。内存是易失性的,意味着当计算机关闭电源时,存储在内存中的数据会丢失。
操作系统的内存管理
操作系统的内存管理是确保有效、高效利用物理内存的关键机制。它包括以下几个方面:
内存分配:
操作系统负责为每个运行的程序分配内存空间,操作系统需要在多个程序之间分配有限的物理内存。
静态分配:在程序运行前就分配固定的内存空间,这种方式简单,但不够灵活。
动态分配:允许程序在运行时根据需要申请和释放内存。动态分配方法包括:
- 堆分配:由操作系统维护一块内存区域(堆),程序可以动态地从中申请和释放内存。
- 栈分配:操作系统为每个线程维护一个栈,用于存储局部变量和函数调用信息。栈的大小可能是固定的,或者有限度地动态增长。
静态分配和动态分配:这包括为程序代码、数据和堆栈分配内存。
虚拟内存:
虚拟内存是一种内存管理技术,它通过使用硬盘空间来扩展物理内存。操作系统创建一个文件(通常称为交换文件或分页文件),用于存储临时从内存中移出的数据。
虚拟内存是现代操作系统用来扩展可用物理内存的技术。它允许程序使用比实际物理内存更多的地址空间,通过以下机制实现:
- 分页:将虚拟内存分成固定大小的页,独立于物理内存的大小。操作系统维护一个页表,将虚拟页映射到物理页。
- 分段:将内存分为逻辑上的段,如代码段、数据段等。分段可以基于程序的逻辑结构优化内存使用。
操作系统通常通过分页机制管理内存。系统内存被分割成固定大小的页。分页允许操作系统只加载程序需要的内存页,减少内存消耗。分页是虚拟内存实现的核心,具体包括:
页表:维护虚拟地址到物理地址的映射。每个进程都有自己的页表。
TLB(Translation Lookaside Buffer):是一种缓存,用于加速虚拟地址到物理地址的转换过程。
页替换算法:当内存满时,如何选择一个页来替换,常见算法有LRU(最近最少使用)、FIFO(先进先出)和OPT(最优替换)。
分段是另一种内存管理技术,它允许程序和数据被分割成逻辑上的段。
内存保护:
作系统通过内存保护确保一个程序不会干扰其他程序的内存空间。这通过以下方式实现:
- 访问控制:页表中的每一项可以包含访问权限标记,如可读、可写和可执行。
- 基址和界限寄存器:用于检查每个内存访问是否在允许的地址范围内。
内存碎片
内存碎片分为两类:
内部碎片:分配给程序的内存块比需要的大,剩下的部分未被使用。
外部碎片:随着时间推移,内存的释放和分配导致空闲内存块之间散布着未使用的小块内存。
- 内存压缩
内存压缩是解决内存碎片问题的一种方法,通过移动已分配的内存块,来整合连续的空闲内存块,从而优化内存的使用。
- 大页支持
一些操作系统提供大页支持(如HugePages in Linux),允许应用程序使用比标准更大的页尺寸。这减少了页表项的数量,降低了TLB miss的概率,提高了内存访问的效率。
浏览器的内存管理
浏览器作为应用程序,也需要进行内存管理,以确保快速响应和有效利用系统资源。浏览器的内存管理包括:
JavaScript 内存管理:
- JavaScript 在浏览器中通过自动垃圾回收机制来管理内存。垃圾回收器定期检查那些不再被应用程序代码引用的对象,并释放它们的内存。
内存隔离:
- 现代浏览器采用进程和线程的隔离策略,确保一个标签页或插件崩溃不会影响到其他标签页。这意味着每个标签页都可以有自己独立的内存空间。
内存限制:
- 浏览器对每个标签页或插件的内存使用进行限制,以防单个网页消耗过多内存影响整个系统的性能。
性能监控和优化:
- 开发者可以使用浏览器提供的开发者工具(如 Chrome 的 DevTools)来监控网页的内存使用情况,识别内存泄漏等问题,并进行优化。
总结
计算机内存的管理是一个复杂的过程,涉及硬件资源的直接管理和通过操作系统及应用程序(如浏览器)的高级管理。有效的内存管理策略可以提高计算机的性能和响应速度,确保系统的稳定运行。对于开发者来说,理解和优化内存的使用是提高应用性能的关键。
虚拟内存是一种计算机系统内存管理的技术,它使得应用程序认为它拥有连续的可用内存,而实际上这些内存可能会被分散存储在物理内存和磁盘存储(通常是硬盘上的交换空间或分页文件)中。虚拟内存的引入主要是为了解决物理内存容量有限的问题,它通过有效地利用磁盘空间来扩展可用内存,从而提供更大的内存空间给程序使用。
虚拟内存的工作原理
内存分页:虚拟内存将内存分成称为“页”的小块数据。操作系统维护一个页表来记录虚拟页和物理页之间的映射关系。
页替换算法:当程序访问的数据不在物理内存中时,会发生“缺页中断”(Page Fault)。操作系统选择一个物理内存中的页来替换,将需要的数据从磁盘读入物理内存。选择哪个页来替换,是由页替换算法决定的,如最近最少使用(LRU)算法、先进先出(FIFO)算法等。
交换空间:交换空间是硬盘上预留的一部分,用于存放被换出的页。这个空间可以是一个专门的交换分区或一个文件。
地址转换:每当应用程序访问内存时,它使用的是虚拟地址。CPU中的内存管理单元(MMU)将这些虚拟地址转换为物理地址。如果所需的虚拟页在物理内存中,转换就很快完成;如果不在,操作系统将从磁盘中调入数据,更新页表,然后继续地址转换。
虚拟内存的优点
- 扩展内存:允许程序使用比实际物理内存更多的内存。
- 安全和隔离:每个程序都有自己的虚拟地址空间,这意味着一个程序无法访问或干扰另一个程序的数据。
- 内存管理简化:程序员可以认为每个程序都有一大块连续的内存,而无需担心物理内存的分配问题。
虚拟内存的缺点
- 性能开销:虚拟内存的使用会引入额外的性能开销,因为涉及到磁盘I/O操作和地址转换。特别是当系统频繁进行页交换时,性能会显著下降,这种现象称为“抖动”(Thrashing)。
- 管理复杂性:虽然对程序员来说内存管理简化了,但操作系统需要处理更复杂的内存管理任务,如页替换、缺页中断处理等。
虚拟内存是现代计算机系统中不可或缺的一部分,尤其在多任务操作系统和需要运行大型应用程序的环境中尤为重要。
在现代浏览器中,是否将每个标签页作为一个独立进程处理取决于浏览器的架构。Google Chrome 是最早采用每个标签页一个进程策略的主流浏览器之一,这种设计被称为“多进程架构”。
多进程架构的优点
- 隔离性:每个标签页在各自的进程中运行,这意味着一个标签页的崩溃不会影响到其他标签页。这提高了浏览器的稳定性和安全性。
- 安全性:通过进程隔离,可以更有效地实施安全策略,防止恶意网站或脚本影响其他标签页或整个系统。
- 响应性:多进程模型可以利用现代多核处理器的能力,通过并行处理提高响应速度。
多进程架构的缺点
- 内存使用:每个进程都会有一定的内存开销,因此多进程架构的浏览器通常会消耗更多的内存。
- 资源管理:需要更复杂的进程管理和通信机制,特别是在进程之间共享和同步数据时。
浏览器的实际实现
- Google Chrome:采用了一种名为“每个站点一个进程”的策略,即通常情况下每个标签页运行在单独的进程中,但来自同一站点的标签页可能共享一个进程。
- Mozilla Firefox:Firefox 采用了“Electrolysis”(简称e10s)架构,它同样支持多进程,但其默认策略并非为每个标签页分配独立进程。Firefox可以配置为运行多个内容进程,这些内容进程中可以承载一个或多个标签页。
- Microsoft Edge:最新版的Edge基于Chromium,与Chrome类似,采用多进程架构。
- Safari:也支持多进程架构,但其进程管理的具体策略可能与Chrome和Firefox有所不同。
总的来说,现代浏览器趋向于使用多进程架构以提高安全性和稳定性,尽管这可能导致更高的内存消耗。每个浏览器的具体实现可能会根据性能、内存使用和安全性之间的权衡做出不同的设计选择。
对于JavaScript及其在浏览器中的内存管理和存储机制的理解,可以从几个方面详细进行分析和总结:
1. 浏览器缓存
强缓存
强缓存不会向服务器发送请求,直接从缓存中读取资源。可以通过设置HTTP响应头Cache-Control
和Expires
来控制。Cache-Control: max-age=3600
表示资源可以被缓存,并且是有效的,直到3600秒后。Expires
则提供一个具体的时间点,告诉浏览器资源有效期。
协商缓存
当强缓存失效后,浏览器会向服务器发送请求,通过Last-Modified
/If-Modified-Since
或ETag
/If-None-Match
来检查资源是否更新。如果资源未更新,服务器返回304状态码,告诉浏览器继续使用缓存。
3. JavaScript内存管理
JavaScript的内存管理是自动的,主要通过垃圾回收机制来实现。
垃圾回收
- 标记清除:这是最常用的垃圾回收算法。当变量进入环境时标记为“进入环境”,当变量离开环境时标记为“离开环境”。标记为“离开环境”的变量将被视为垃圾,并被收集和回收。
- 引用计数:这种方法跟踪每个值被引用的次数。当引用次数变为0时,值会被标记为可回收。然而,引用计数方法不能解决循环引用的问题。
4. V8引擎的垃圾回收
V8引擎用于Google Chrome浏览器,使用了更复杂的垃圾回收策略,包括新生代和老生代的分代回收:
- 新生代:from、to存放生命周期短的小对象,使用Scavenge算法,其中主要用到复制方式,将活动对象从一个区域复制到另一个区域。
- 老生代:存放生命周期长或常驻内存的对象,使用标记-清除和标记-整理算法来减少碎片。
结论
JavaScript的内存管理和浏览器存储是现代web开发中不可或缺的一部分,了解这些机制可以帮助开发者更好地优化其应用性能和用户体验。通过合理使用各种存储方式,并理解垃圾回收的工作原理,可以有效地管理应用的内存使用,避免内存泄漏和过度占用资源的问题。
关于JavaScript和浏览器存储及内存管理,还有一些其他重要的方面可以补充,这些包括内存泄漏的识别与预防,Web Workers的使用,以及内存性能监控工具的应用。
5. 内存泄漏的识别与预防
内存泄漏在JavaScript中是一种常见的性能问题,通常由未被垃圾回收机制清除的持续占用内存的对象引起。一些常见的内存泄漏原因包括:
- 全局变量:无意中创建的全局变量可能不会被回收。
- 定时器和回调函数:未被清除的定时器或回调函数可以持续占用内存。
- DOM 引用:被删除的DOM元素如果仍被JavaScript引用,也会导致内存泄漏。
- 闭包:不恰当的闭包使用可能导致父函数作用域内的变量无法被释放。
预防内存泄漏的策略包括:
- 限制全局变量的使用:使用严格模式(
use strict
)来避免意外创建全局变量。 - 及时清理:定时器应当在不需要时清除,事件监听器在元素被移除前应当被移除。
- 使用弱引用:如
WeakMap
和WeakSet
允许其内容在没有其他引用时被垃圾回收。
6. Web Workers的使用
Web Workers提供了一种将执行过程从主线程中分离出来的方法,使得可以在后台线程中执行复杂计算,避免阻塞UI。这对于提升大数据处理或复杂计算的应用的响应性非常有帮助。使用Web Workers时,需要注意数据是通过复制而非共享方式传递,这意味着原始数据和Worker之间不会相互影响。
7. 内存性能监控工具
为了有效地监控和分析JavaScript的内存使用情况,可以使用以下工具:
- Chrome DevTools:提供了强大的内存分析工具,包括堆快照(Heap Snapshot)、记录堆分配(Record Heap Allocations)和时间线(Timeline)视图等,可以帮助开发者识别内存泄漏和优化性能。
- Performance API:这是一个浏览器API,允许收集和分析在应用运行时的性能数据。
结论
通过以上的详细介绍,我们可以更全面地理解JavaScript的内存管理、浏览器缓存和本地存储机制。适当地应用这些知识和工具,可以显著提高Web应用的性能和用户体验。同时,防止内存泄漏和合理利用多线程计算资源也是提升现代Web应用质量的重要策略。
当谈到在Web开发中监控JavaScript性能的实例数据,我们通常关注以下几个关键指标:
1. 时间线数据 (Timeline Data)
在Chrome DevTools中,时间线工具可以记录和展示一个页面在加载和运行过程中的详细活动。这包括了JavaScript执行时间、样式计算、布局、绘制等。一个示例性的时间线数据可能如下:
- 加载事件:页面开始加载到onLoad事件触发的时间。
- DOMContentLoaded:DOM解析完成的时间。
- 脚本执行时间:单个脚本或脚本总和的执行时间。
- 渲染时间:页面进行渲染的时间点和持续时间。
- 帧速率:动画或页面滚动的帧速率。
2. 堆快照 (Heap Snapshot)–控制台的memory
heap snapshot
堆快照显示了在某一时刻JavaScript的所有对象及其关系的内存分布情况。这可以帮助开发者发现内存泄漏。示例数据可能包括:
- 对象数:当前内存中存在的对象总数。
- 分配大小:每种对象类型占用的内存大小。
- 保留大小:对象及其依赖对象总共占用的内存大小。
- 垃圾可回收:已被标记为可回收但尚未清理的内存。
3. 性能记录 (Performance Record)
使用Chrome DevTools的Performance panel可以记录一段时间内页面的运行情况,包括JS执行、样式计算、布局、绘制等。性能记录的数据可能包括:
- 函数调用:函数调用的次数和耗时。
- **FPS (帧率)**:在记录期间的帧率,理想状态下接近60fps。
- CPU利用率:JavaScript执行对CPU的占用情况。
4. 网络请求 (Network Requests)
监控网络请求可以帮助识别加载资源的瓶颈。性能监控数据可能包括:
- 请求时间:每个资源从请求到完成的时间。
- 资源大小:下载的每个文件的大小。
- 缓存状态:资源是否被缓存。
示例:性能监控报告
假设有一个页面加载分析报告,内容可能如下:
- 总加载时间:5秒
- 脚本执行总时间:1.2秒
- 最大DOM深度:22层
- 重绘次数:15次
- 内存使用峰值:150MB
- 网络请求:50个请求,总共传输数据1MB
结论
这些性能监控数据帮助开发者深入理解Web应用的行为和性能瓶颈。通过定期检查这些指标,可以持续优化应用,改善用户体验。在现代Web开发中,有效地利用这些工具和数据是至关重要的。
进程和线程的区别
kmp算法
微前端路由拦截
拦截的浏览器路由API
History API:
history.pushState()
history.replaceState()
history.go()
history.back()
history.forward()
这些方法是HTML5 History API的一部分,用于在不重新加载页面的情况下操作浏览器的历史记录和路由状态。
Hash Changes:
- 监听
hashchange
事件
对于基于哈希的路由(通常在旧的单页应用中使用),监听
hashchange
事件是必要的,以便在URL的哈希部分变化时触发路由逻辑。- 监听
如何进行路由拦截
在微前端架构中,通常会有一个中心化的路由管理器或框架负责处理这些拦截。以下是一些常见的实现方法和技术:
使用 single-spa:
- Single-spa 提供了一个路由前缀的概念,通过配置哪些前缀或路径激活特定的子应用。它通过监听浏览器的
popstate
事件和拦截pushState
和replaceState
方法来实现。
示例代码如下:
1
2
3
4
5
6window.addEventListener('popstate', handlePopState);
const originalPushState = history.pushState;
history.pushState = function() {
originalPushState.apply(this, arguments);
handleRouteChange();
};- Single-spa 提供了一个路由前缀的概念,通过配置哪些前缀或路径激活特定的子应用。它通过监听浏览器的
手动拦截 History API:
- 在主应用的入口文件中,可以手动覆盖
history.pushState
和history.replaceState
方法,以便在路由改变时执行额外的逻辑,比如加载或卸载子应用。
1
2
3
4
5
6
7
8
9
10
11
12
13const originalPushState = history.pushState;
history.pushState = function(state, title, url) {
originalPushState.call(history, state, title, url);
dispatchRouteChange();
};
window.addEventListener('popstate', function(event) {
dispatchRouteChange();
});
function dispatchRouteChange() {
// 在这里处理子应用的加载和卸载
}- 在主应用的入口文件中,可以手动覆盖
框架特定的路由守卫:
- 对于使用 Vue 或 React 等框架的应用,可以利用框架提供的路由守卫功能(如 Vue 的
beforeEach
路由守卫或 React Router 的history.listen
)。
在 Vue 中使用
vue-router
的示例:1
2
3
4router.beforeEach((to, from, next) => {
// 在这里根据路由切换逻辑加载或卸载子应用
next();
});- 对于使用 Vue 或 React 等框架的应用,可以利用框架提供的路由守卫功能(如 Vue 的
在 React 中使用 React Router 的示例:
1
2
3
4
5
6
7
8
9
10
11
12
13import { useHistory } from 'react-router-dom';
function useRouteChange() {
const history = useHistory();
useEffect(() => {
const unlisten = history.listen((location, action) => {
// 在这里处理路由变化
});
return () => {
unlisten();
};
}, []);
}
结论
微前端的路由拦截主要是通过拦截和管理浏览器的 History API 实现的,确保在主应用和子应用之间正确地管理和协调路由状态。这要求在设计微前端架构时,要有一个清晰的路由管理策略,以保证应用的整体性和用户体验的流畅性。
9-20-阳光能源
数据类型
栈和队列
强缓存设置的max-age为一年,现在不使用强缓存,如何获取最新的数据
防抖和节流
bigint
后端返回json为空如何判断
如何判断对象的属性相等
块级作用域
MVVM是什么?如果在input触发事件,这是哪个层面
发布订阅
商汤科技
Map和object的区别
小米
项目
微前端的选型,有遇到什么问题吗
项目的权限
pinia
内容的持久化存储
分片上传和断点续传
性能优化:图片预加载+懒加载,CDN,
算法题
一个以字符串表示的非负整数 num 和一个整数k,移除这个数中的k位数字,使得剩下的数字最小。请你以字符串形式返回这个最小的数字。
示例1:
输入:num=”1432219”,k=3
输出:”1219”
解释:移除掉三个数字 4,3,和 2 形成一个新的最小的数字 1219
示例 2:
输入:num=”10200”,k=1
输出:”200”
解释:移掉首位的1剩下的数字为 200.注意输出不能有任何前导零
示例 3
输入:num=”10”,k=2
输出:”O”
解释:从原数字移除所有的数字,剩余为空就是0
1 | function removeKDigits(num, k) { |
题目描述给定一个经过编码的字符串,返回它解码后的字符串.编码规则为: k[encoded string],表示其中方括号内部的encoded string 正好重复k次。注意k保证为正整数。你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数k,例如不会出现像 3a 或 2[4]的输入。示例 1: 输入:s=”3[a]2[bc] 输出:”aaabcbc” 示例 2: 输入:s =”3[a2[c]]’ 输出:”accaccacc 示例 3: 输入:s=”2[abc]3[cd]ef” 出:”abcabccdcdcdef’ 示例 4: 输入:s=”abc3[cd]xyz 输出:”abccdcdcdxyz” 请用js进行代码的编写。
1 | function decodeString(s) { |
sandbox
组件通信
白屏怎么处理
http状态码
虚拟列表
得物
微前端的比较难的问题,性能优化
前端性能优化中,如果有10mb的图片,使用懒加载怎么更好?
dns
对称加密和非对称加密的区别
怎么避免中间人攻击,非对称加密?
Etag生成的策略?
Etag(Entity Tag)是HTTP协议中的一个机制,用于标识资源的版本或状态。它通常被用来进行缓存控制和并发控制,帮助服务器判断资源是否已经发生了变化,从而决定是否返回新的数据或使用缓存。Etag的生成策略可以根据具体的需求和应用场景有所不同,下面是几种常见的生成Etag的策略。
1. 基于文件内容的哈希值
最常见的Etag生成策略是通过对资源的内容进行哈希计算来生成唯一标识符。当资源内容发生变化时,其哈希值也会随之变化,这样就能确保Etag始终反映最新的资源状态。
实现方式:
- 可以使用MD5、SHA-1、SHA-256等哈希算法对资源内容进行计算。
- 生成的哈希值可以是一个固定长度的字符串,用作Etag。
优点:
- 当资源的内容发生任何细微变化时,Etag都会更新,非常准确。
缺点:
- 对于大文件或大数据资源,每次生成哈希值时的计算开销可能较大,特别是在资源经常更新的情况下。
示例:
1 | Etag: "5d41402abc4b2a76b9719d911017c592" (基于文件内容生成的MD5哈希值) |
2. 基于资源的版本号
另一种常见策略是通过为资源分配一个版本号,当资源发生变化时,增加或更新版本号。这个版本号可以作为Etag的内容。
实现方式:
- 每当资源更新时,版本号递增。例如数据库记录更新时,自动生成一个新的版本号。
优点:
- 实现简单,不需要计算哈希,适合那些可以明确跟踪资源版本的场景。
- 计算开销小,特别适合动态生成内容的场景。
缺点:
- 版本号只反映资源的逻辑变化,不能精确描述内容的实际变化。如果更新未导致内容实际变化,版本号仍然会变化。
示例:
1 | Etag: "v123456" (基于资源的版本号) |
3. 基于文件的最后修改时间
Etag也可以根据资源的最后修改时间生成。通过时间戳来表示资源的最后更新时间,如果资源发生变化,其时间戳也会更新。
实现方式:
- 读取资源的最后修改时间,使用该时间作为Etag的内容。
优点:
- 简单高效,特别适用于文件系统中的静态资源。
缺点:
- 可能不够精确,如果修改时间精度不够高,可能无法检测出一些细微的变化。
- 如果修改时间被手动更改,可能导致Etag失效。
示例:
1 | Etag: "W/\"1633036800\"" (基于Unix时间戳的Etag) |
4. 强Etag与弱Etag
强Etag:强Etag要求资源的每一个字节都精确一致,任何细微变化都会生成不同的Etag。例如基于内容哈希值生成的Etag是强Etag的一种。
弱Etag:弱Etag允许某些小的资源变化不影响Etag,例如不同的服务器时间、不同的文件系统属性变化等。弱Etag在值前面会带有
W/
前缀,表示它是弱校验。- 弱Etag常常用于那些对资源精确度要求不高的场景,例如网页的样式表等。如果只是一些格式变化或者无关紧要的元数据变化,弱Etag不会更新。
示例:
1 | Etag: W/"5d41402abc4b2a76b9719d911017c592" (弱Etag) |
5. 基于资源大小和最后修改时间的组合
在某些情况下,服务器可能会采用文件大小和最后修改时间的组合来生成Etag。比如,文件大小没有变化,而最后修改时间发生了变化,则Etag可能不变,反之亦然。这种方法有助于平衡计算开销和准确性。
实现方式:
- 获取资源的大小和最后修改时间,然后将它们组合成一个字符串或使用它们生成哈希值作为Etag。
优点:
- 性能较好,因为无需对内容进行复杂的哈希运算。
- 可用于检测资源是否进行了实际修改。
缺点:
- 如果资源大小和修改时间相同,但内容不同,则无法检测变化。
示例:
1 | Etag: "1000-1633036800" (基于大小和时间戳组合) |
6. 自定义生成策略
对于一些复杂应用,Etag可以基于应用的特定逻辑生成。例如,某些动态生成的内容可能基于数据库的查询结果或某些用户特定的信息生成。这时,Etag可以基于数据的标识符、查询条件等动态元素来生成。
实现方式:
- 根据应用的逻辑,生成与内容变化相关的Etag,例如:用户ID + 数据更新时间。
优点:
- 更加灵活,适应应用的特定需求。
缺点:
- 实现复杂,可能需要额外的逻辑来确保Etag的准确性。
总结
Etag的生成策略取决于应用场景和性能要求,以下是几种常用策略的对比:
策略 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
基于内容哈希 | 高精度,任何细微变化都会更新Etag | 计算开销较大,尤其是对于大文件或复杂资源 | 静态文件、需确保内容完全一致的场景 |
基于版本号 | 简单,开销小 | 无法精确反映实际内容的变化 | 动态生成的内容、版本管理的资源 |
基于修改时间 | 高效,适用于文件系统中的静态资源 | 时间精度问题,无法捕捉细微的内容变化 | 静态文件或不频繁更新的文件 |
强Etag vs 弱Etag | 强Etag适合精确变化跟踪,弱Etag适合性能优化 | 弱Etag无法检测所有变化,强Etag计算量大 | 精确或容许小变动的场景 |
大小 + 修改时间组合 | 性能较好,精确度适中 | 不能检测大小和时间相同时的内容变化 | 文件较大且更新频繁的资源 |
自定义策略 | 灵活适应特定应用需求 | 实现复杂,需要额外的逻辑处理 | 复杂动态内容或特定业务逻辑的资源管理场景 |
最终,选择哪种Etag生成策略,取决于性能需求、资源更新频率、以及内容一致性的重要性等因素。在实际应用中,结合缓存策略来优化资源的传输效率也是十分重要的。
如果内容有静态资源,怎么去进行优化?
滴滴
画三角形
盒模型
css的动画:keyframes
重绘和回流
样式visible设置为hidden会触发回流吗
js的this有哪几种方式改变,在构造函数和普通函数的区别?bind和apply区别
new的流程
原型链上object和function的关系
vue2和vue3的区别,主要升级的功能
Object.defineProperty除了set和get方法还有什么属性?
如果是可枚举的或者不可枚举的这中场景下,怎么去遍历属性的值?
vue-router设置keep-alive,怎么知道他回到了这个地方?
vue2的diff算法,在同层比较。老树的最后一个节点和新树的第一个节点相同,接下来指针会怎么走?
实现图片懒加载
虚拟加载的原理
判断可视区域的方法
qiankun集成样式不兼容,怎么解决
文件分片上传,切片的原理,怎么去组合的?
服务端渲染,在开发组件的时候,要使用服务端渲染要注意什么?
多端开发遇到的问题?
vue3比较于vue2有哪些优势
Vue 3的diff 算法有哪些变化?
为什么v-if和v-for不推荐一起使用?
promise实现图片的并发控制,for循环传进来的是有序的吗?
3
对qiankun的考虑是什么?和qiankun相关的横向方案呢
封装富文本编辑器会遇到哪些问题?
AI聊天会遇到哪些问题?
和多个npc聊天,阻断的依据是什么?
帧的稳定性
uni-app判断是h5还是web
userAgent
除了区分平台,还可以用来做什么?
切片怎么保证切的是正确的?