面试题五
1.vite有什么缺点
缺点:
1、生态系统相对不成熟:相对于 Webpack 这样的老牌构建工具,Vite 是一个较新的项目,其生态系统还不如 Webpack 那么成熟。这意味着在使用 Vite 时,可能还会遇到一些缺乏文档、插件支持不完善或不稳定等问题。
2、对部分环境不友好:由于 Vite 使用了 ES Module 作为默认模块系统,导致它在处理一些旧版本浏览器或 Node.js 环境时可能存在一些兼容性问题。尽管 Vite 提供了一些配置选项来解决这些问题,但在某些情况下需要额外的工作。
3、构建速度慢:Vite 的快速开发体验主要体现在开发阶段,通过利用浏览器的原生 ES Module 支持,Vite 可以实现非常快的冷启动和热更新。然而,在项目打包阶段,Vite 的构建速度可能不如 Webpack 这样的传统打包工具。这是因为 Vite 倾向于将模块打包为原生的 ES Module,而不是像 Webpack 那样将代码封装为通用的 CommonJS 或 AMD 模块。
需要注意的是,Vite 的这些缺点并不意味着它是一个不好的选择,它仍然是一个非常有竞争力和具有前瞻性的构建工具。
2.qiankun解决了single-spa什么问题
解决了以下问题:
1、共享依赖解决方案:在微前端架构中,不同的子应用可能会使用相同的依赖库,而传统的单体应用打包会将所有依赖库都打包在一起,导致重复加载和资源浪费。qiankun 提供了共享依赖解决方案,能够将公共的依赖库提取出来,在主应用和子应用之间共享使用,减少重复加载和资源冗余。
2、资源隔离和沙箱环境:在微前端架构中,每个子应用都应该运行在独立的沙箱环境中,以确保子应用之间的代码和样式互相隔离,避免相互影响。qiankun 提供了对子应用的隔离和沙箱化支持,每个子应用都运行在独立的 iframe 中,确保各个子应用之间彼此独立,并提供了沙箱机制,防止子应用之间的全局变量污染。
3、状态管理和通信机制:在微前端架构中,不同的子应用之间可能需要进行状态共享和通信。qiankun 提供了一套完善的状态管理和通信机制,可以实现子应用之间的状态共享、事件发布订阅等功能,使得子应用之间可以进行数据的交互和通信。
4、动态加载和按需加载:在微前端架构中,子应用的加载应该是动态的,只有在需要时才进行加载,以提升整体性能和用户体验。qiankun 支持子应用的动态加载和按需加载,可以根据需求来动态加载子应用,并且能够实现子应用的懒加载,提高启动速度和页面响应性。
总之,qiankun 在 single-spa 的基础上进行了改进和优化,解决了微前端架构中的共享依赖、资源隔离、状态管理和通信机制、动态加载等问题,提供了更好的开发体验和性能优化。
qiankun源码实现
-注册微应用时通过fetch请求HTML entry,然后正则匹配得到内部样式表、外部样式表、内部脚本、外部脚本
-通过fetch获取外部样式表、外部脚本然后与内部样式表、内部脚本按照原来的顺序组合组合之前为样式添加属性选择器(data-微应用名称);将组合好的样式通过style标签添加到head中
-创建js沙盒:不支持Proxy的用SnapshotSandbox(通过遍历window对象进行diff操作来激活和还原全局环境),支持Proxy且只需要单例的用LegcySandbox(通过代理来明确哪些对象被修改和新增以便于卸载时还原环境),支持Proxy且需要同时存在多个微应用的用ProxySandbox(创建了一个window的拷贝对象,对这个拷贝对象进行代理,所有的修改都不会在rawWindow上进行而是在这个拷贝对象上),最后将这个proxy对象挂到window上面
-执行脚本:将上下文环境绑定到proxy对象上,然后eval执行
3.如何在严格模式使用with语句
严格模式下禁用 with 语句的原因是因为它引入了潜在的问题和不可预测的行为。with 语句会将一个对象引入作用域链中,从而导致代码的可读性和维护性降低。它也会影响 JavaScript 引擎的优化能力,因为引擎无法在编译时确定变量的具体位置,从而影响性能。
4.用setInterval存在什么问题,如何更精确做倒计时
为了更精确地实现倒计时,可以使用 setTimeout 加上时间戳来计算剩余时间,并在每次执行定时器回调函数时重新计算剩余时间。
5.immer实现原理
1、代理对象:Immer 使用 JavaScript 的 Proxy 对象来代理原始数据对象。当你访问或修改代理对象时,会触发相应的代理方法。
2、隐式批处理:Immer 使用隐式的批处理机制来捕获对代理对象的所有修改操作,而非每次修改都立即执行。这样可以在一次事务中收集所有的修改操作,最终仅应用一次,并生成一个新的不可变数据结构。
3、结构共享:为了避免生成过多的中间对象,Immer 采用了结构共享的技术。当对代理对象进行修改时,不会立即创建新的对象,而是根据需要在修改路径上复制原始数据结构的一部分,并创建一个差异对象。
4、不可变性检查:Immer 在执行修改操作时,会检查当前操作是否会导致原始数据被修改。如果检测到有不可变性被破坏的行为,会抛出错误,以确保数据的不可变性。
通过以上的机制,Immer 在实现的过程中可以提供一个简洁而自然的 API 来修改不可变数据,而无需手动进行深拷贝和繁琐的状态管理。它使得开发者可以像直接操作可变数据一样操作不可变数据,同时保持了不可变性的优势。
6.fixed布局有时为什么会失效
1、父级元素使用了 transform 属性:如果父级元素使用了 transform 属性,例如 transform: translateZ(0),那么会导致子元素的固定定位失效。这是因为 transform 属性会创建一个新的层叠上下文,固定定位的元素只能相对于最近的非 transform 祖先进行定位。
2、父级元素的溢出属性不符合要求:如果父级元素具有溢出属性(例如 overflow: hidden),且宽度或高度不足以容纳固定定位的元素,则可能导致固定定位失效。在这种情况下,可以通过调整父级元素的溢出属性或尺寸来解决。
3、父级元素有 CSS 变换:如果父级元素应用了 CSS 变换(例如 rotate、scale、skew 等),则固定定位的元素可能会受到父级元素变换的影响,导致失效。可以尝试将固定定位的元素提升到变换之外,或者将变换应用于父级元素而不是固定定位的子元素。
4、元素被其他元素遮挡:如果固定定位的元素被其他元素遮挡,可能导致看起来固定定位失效。在这种情况下,可以通过调整 z-index 属性来改变元素的层叠顺序,确保固定定位的元素在上层。
5、浏览器兼容性问题:某些较老的浏览器可能对固定定位存在兼容性问题。这些问题通常与具体的浏览器版本和 CSS 属性有关。在开发过程中,可以通过浏览器兼容性测试工具来检查固定定位在不同浏览器上的表现。
7.ts里面is关键字的作用
在 TypeScript 中,is 是一个表达式形式的关键字,用于进行类型判断和类型保护。它通常与条件语句(if 语句)一起使用。
当使用 is 关键字时,它用于定义一个自定义的类型谓词(Type Predicate),用于检查一个值是否属于特定的类型。类型谓词定义了一个返回布尔值的函数,其返回类型被 TypeScript 推断为 value is Type 的形式,其中 value 是要判断的值,Type 是目标类型。
通过使用 is 关键字,我们可以在条件语句中进行类型保护,以根据类型的判断结果来执行相应的代码逻辑。当类型谓词返回 true 时,TypeScript 会将变量的类型缩小为目标类型,这样我们就可以安全地访问目标类型的属性和方法。
8.双token机制作用
双 Token 机制在前端开发中通常用于增强安全性,提供更可靠的身份验证和授权功能。它由两个不同的 Token 组成,分别是访问令牌(Access Token)和刷新令牌(Refresh Token)。它们的作用如下:
1、Access Token(访问令牌):
Access Token 是一个短期的令牌,用于对用户身份进行验证和对资源进行访问授权。它通常包含有关用户身份和权限的信息,并具有较短的有效期限。当用户进行登录或认证后,后端服务器会生成一个 Access Token 并返回给前端,前端可以将该令牌附加在每次请求的 Header 或参数中发送给后端,用于验证用户身份和授权。Access Token 的有效期较短可以减小令牌被滥用的风险。
2、Refresh Token(刷新令牌):
Refresh Token 是一个长期有效的令牌,用于更新 Access Token,使用户保持登录状态。它通常与 Access Token 一起返回给前端,但需要存储在安全的地方,如 HTTP-Only 的 Cookie 中或本地存储中。当 Access Token 过期时,前端可以使用 Refresh Token 向后端发送请求以获取新的 Access Token,而无需用户重新进行登录认证。Refresh Token 的有效期相对较长,用于提供持久的登录状态。
双 Token 机制的作用在于提高安全性和用户体验,具有以下优势:
安全性增强:由于 Access Token 的有效期较短且存储在前端,降低了令牌泄露的风险。同时,使用 Refresh Token 进行续签操作,可以避免在每次访问都要进行完整的身份验证和授权。
减轻服务器压力:Access Token 的有效期较短,会减少服务器验证令牌的频率,从而减轻服务器的负载压力。
提供持久登录:使用 Refresh Token 可以实现长期的登录状态,提供更好的用户体验。用户不需要频繁地重新输入用户名和密码进行认证,只需在过期时使用 Refresh Token 更新 Access Token 即可。
需要注意的是,在实现双 Token 机制时,要确保合理设置 Token 的有效期限,加密传输,以及将敏感信息存储在安全的位置。此外,还需要谨慎处理令牌的刷新逻辑,防止令牌被滥用或劫持。
9.设计一个时间旅行功能
// 存储历史状态的数组 var history = []; // 当前状态的索引 var currentIndex = -1; // 添加状态到历史记录 function addToHistory(state) { // 如果当前状态不是最新状态,则删除后续状态 if (currentIndex < history.length - 1) { history = history.slice(0, currentIndex + 1); } // 添加新状态到历史记录 history.push(state); // 更新当前状态索引 currentIndex++; } // 返回到上一个状态 function goBack() { if (currentIndex > 0) { currentIndex--; updateUI(); } } // 前进到下一个状态 function goForward() { if (currentIndex < history.length - 1) { currentIndex++; updateUI(); } } // 更新界面显示 function updateUI() { var state = history[currentIndex]; // 更新界面显示逻辑... } // 定时器,每隔一段时间添加一个新状态 setInterval(function() { // 获取当前时间作为新状态 var state = new Date(); // 添加新状态到历史记录 addToHistory(state); // 更新界面显示 updateUI(); }, 1000);
10.css in js的认识,优缺点
认识:
1、将样式与组件绑定:使用 CSS-in-JS,可以将样式与组件直接关联起来,使样式定义更贴近组件,提高代码的可维护性和可重用性。
2、运行时生成样式:在浏览器运行时,CSS-in-JS 会动态生成样式规则,并将其应用到组件上,避免了样式冲突和层叠样式表(CSS)的全局污染问题。
3、提供样式的封装和复用:CSS-in-JS 支持样式的封装和组件化,可以将样式逻辑封装在组件内部或单独的模块中,并在多个组件之间进行复用。
优点:
1、更好的可维护性:由于样式与组件绑定,易于理解和维护。相关的样式和组件代码被放在同一个地方,减少了样式冲突和维护成本。
2、更好的可重用性:CSS-in-JS 支持样式的封装和组件化,使样式更易于复用。样式可以被多个组件共享,提高了开发效率。
3、动态性和可扩展性:CSS-in-JS 的样式规则是在运行时动态生成的,因此可以基于组件的状态、属性或其他动态信息来生成不同的样式规则,实现更高度的灵活性和可扩展性。
缺点:
1、学习成本:相对于传统的 CSS 开发方式,使用 CSS-in-JS 需要学习新的语法和工具。对于团队中没有经验的开发人员,可能需要一定的学习成本。
2、过度使用的问题:有时候,过度使用 CSS-in-JS 可能导致样式与组件的逻辑过度耦合,增加了代码的复杂性。因此,在使用 CSS-in-JS 时需要谨慎权衡,避免滥用。
11.react里面useReducer和useState区别
useState 和 useReducer 都是用于管理组件内部的状态的 Hook。
1、API 使用:
useState:useState 是 React 提供的最基本的状态管理 Hook,通过调用该 Hook,可以在函数组件中定义和更新状态。它返回一个包含状态值和更新状态的函数的数组。更新状态的函数通常命名为 setXxx(例如,setCount)。
useReducer:useReducer 是另一个状态管理 Hook,它提供了一种将状态和状态更新逻辑封装在一起的方式。它接受一个 reducer 函数和初始状态,并返回一个包含当前状态和派发(dispatch)函数的数组。
2、状态管理:
useState:useState 适用于简单的状态管理,每次更新状态都是独立的,互不影响。调用 setXxx 函数会触发组件的重新渲染,并将新的状态值应用到对应的状态变量上。
useReducer:useReducer 适用于更复杂的状态管理和逻辑处理。它将状态和状态更新逻辑放在 reducer 函数中,reducer 函数接收当前状态和一个 action 对象,并返回新的状态。通过调用派发函数(dispatch),可以触发相应的状态更新逻辑。
3、状态更新的触发:
useState:useState 的状态更新是通过调用 setXxx 函数来触发的。每次调用 setXxx 都会重新渲染组件,这是因为 useState 返回的更新状态的函数在内部使用了浅比较,以判断状态是否发生了改变。
useReducer:useReducer 的状态更新是通过调用派发函数来触发的,该函数接收一个 action 对象作为参数。调用派发函数不会自动重新渲染组件,需要在 reducer 函数中返回新的状态才能触发重新渲染。
useState 适用于简单的状态管理,适合于单一的状态值和更新逻辑。而 useReducer 则适用于更复杂的状态管理和逻辑处理,通过 reducer 函数将状态和状态更新逻辑封装在一起。
12.react后面本地启动严格模式为什么useEffect执行两次
严格模式(Strict Mode)是一种用于检测并警告潜在问题的开发工具。它会对组件进行两次渲染,以捕获可能产生副作用的代码。useEffect 是一个副作用 Hook,用于处理与组件渲染无关的操作,例如数据获取、订阅或 DOM 操作。
当启用严格模式后,React 在第一次渲染时会执行正常的渲染流程,然后在第二次渲染时会检测在首次渲染期间是否有任何副作用产生了变化。如果有变化,React 会发出警告,以帮助你发现可能导致 bug 的问题。
这就是为什么在严格模式下,useEffect 可能会执行两次的原因。第一次渲染是为了收集可能的副作用,并调用 useEffect 中定义的回调函数,第二次渲染是为了检查是否有意外的变化。
需要注意的是,在生产环境中使用 React,严格模式是被禁用的,因此在生产环境中使用 useEffect 不会出现两次执行的情况。
13.vue3里面ref和reactive原理和区别
1、原理:
ref:ref 是一个函数,它接收一个参数作为初始值,并返回一个包含一个可变值的对象。内部会使用 JavaScript 的原始数据类型(如 Number、String、Boolean 等)进行封装。通过 value 属性可以访问和修改这个值,当修改时会触发组件的重新渲染。
reactive:reactive 是一个函数,它接收一个普通 JavaScript 对象作为参数,并返回这个对象的响应式代理对象。通过代理对象访问属性时,如果属性被修改,也会触发组件的重新渲染。
2、使用方式:
ref:通常用于处理单个简单的值类型,例如数字、字符串等。可以通过读取和修改 value 属性来操作数据。在模板中使用时,可以直接在表达式中使用 ref 对象,自动解引用并进行响应式追踪。
reactive:适用于处理复杂的数据结构,如对象或数组。可以直接对其进行属性的读取和修改,而不需要特定的 value 属性。但是,在模板中使用时,需要使用 <template> 标签中的 {{ }} 进行解引用。
3、引用透明性:
ref:ref 对象具有引用透明性,即可以像操作普通的 JavaScript 数据类型一样操作 ref 对象。这意味着可以对 ref 进行解构、传递给子组件等操作,而不需要额外的处理。
reactive:reactive 返回的响应式对象在传递给其他组件时,需要通过 toRefs 函数或解构的方式将其转换为普通的 JavaScript 对象,以确保引用透明性。
总之,ref 和 reactive 都是用于创建响应式数据的 API,但在使用上有一些区别。ref 更适合处理简单的值类型,而 reactive 更适合处理复杂的对象或数组。在模板中使用时,ref 可以直接在表达式中使用,而 reactive 需要使用 <template> 标签中的 {{ }} 进行解引用。此外,ref 对象具有引用透明性,而 reactive 返回的响应式对象需要特殊处理才能保持引用透明性。
14.git里面merge,rebase,squash区别和场景
1、Merge(合并):
合并是将两个或多个分支的更改集成到一起的过程。通过执行 git merge 命令,可以将一个分支的更改合并到另一个分支中。
合并操作会创建一个新的合并提交,将两个分支的更改整合在一起。这个合并提交会保留合并历史,可以清晰地看到哪些更改来自于哪个分支。
适用场景:当多个分支有不同的目的或功能,需要将它们的修改合并到一个共同的目标分支时,可以使用合并操作。
2、Rebase(变基):
变基是将一条分支的更改应用到另一条分支上的过程。通过执行 git rebase 命令,可以将一个分支的更改放在另一个分支的最后面。
变基操作会将当前分支的修改复制到目标分支上,并且不会创建新的合并提交。这使得提交历史变得更加线性和简洁。
适用场景:当你想要在当前分支上应用其他分支的更改,并希望保持提交历史的整洁和线性时,可以使用变基操作。
3、Squash(合并压缩):
合并压缩是将多个连续的提交压缩为一个更大的提交的过程。通过执行 git merge --squash 或者使用交互式变基(git rebase -i)进行压缩来实现。
合并压缩操作会创建一个新的提交,包含了原始提交的所有更改,并将其合并为一个提交。这个操作能够产生更整洁、易于理解的提交历史。
适用场景:当你希望将多个小的连续提交合并为一个有意义的大提交时,可以使用合并压缩操作。
15.什么时候用monorepo方案开发,怎样管理
Monorepo 是一种开发模式,它将多个项目或组件存储在同一个代码仓库中。使用 Monorepo 方案的时机和管理方式如下所示:
1、适用场景:
共享代码:当多个项目或组件之间需要共享代码时,可以使用 Monorepo 方案。这样可以避免代码重复,提高代码复用性,并方便进行代码的维护和更新。
简化构建和部署:当项目或组件之间存在相互依赖关系时,使用 Monorepo 可以简化构建和部署流程。可以通过在同一个仓库中管理所有项目的依赖关系,统一构建和发布过程,减少配置和管理的复杂度。
提高协作效率:当团队成员需要共同合作开发多个项目或组件时,使用 Monorepo 可以提高协作效率。团队成员可以更容易地在同一个仓库中进行代码的查看、修改和合并。
2、管理方式:
构建工具:为了管理 Monorepo 中的多个项目或组件,可以使用各种构建工具来进行任务自动化。常见的工具包括 Lerna、Yarn Workspaces 和 Rush 等。构建工具可以帮助你管理依赖关系、执行构建脚本、发布版本等操作。
版本控制:使用版本控制系统(如 Git)来管理 Monorepo 的代码。可以根据需要创建分支、提交更改,并使用合适的分支策略进行开发和发布。同时,也要注意合理设置忽略文件和推送规则,以避免不必要的提交和冲突。
代码结构组织:在 Monorepo 中,需要合理组织代码结构和模块之间的关系。可以通过目录结构、命名约定和模块化设计来确保代码的可维护性和可扩展性。可以将共享的代码提取为独立的库,并在各个项目中引用。
总结起来,Monorepo 方案适用于需要共享代码、简化构建和部署、提高协作效率的场景。在管理方面,可以使用构建工具进行任务自动化,使用版本控制系统管理代码,并合理组织代码结构和模块之间的关系。选择合适的工具和规范可以帮助你更好地管理 Monorepo 。
16.如何预防xss攻击
1、输入验证和过滤:
对用户输入的数据进行验证和过滤,确保只接受符合预期的数据格式。可以使用正则表达式或专门的输入验证库来检测和过滤恶意代码或特殊字符。
2、输出编码:
在将用户输入插入到 HTML、CSS 或 JavaScript 中之前,使用适当的编码进行转义。例如,对于 HTML 内容可以使用 HTML 实体编码(如 < 编码为 <)来防止标签注入。
3、使用安全的 DOM 操作:
避免使用 innerHTML 直接插入用户提供的内容,而应该使用更安全的 DOM API,例如 textContent 或 createTextNode。
4、设置 HTTP 头部的 Content Security Policy(CSP):
CSP 是一种安全策略,通过限制页面加载资源的来源和执行方式来减少 XSS 的风险。通过设置合适的 CSP,可以阻止不可信的脚本执行