前端面试题(一)

301 阅读24分钟

1、ssr,ssg,csr

SSR(Server-Side Rendering):在 SSR 中,页面的初始渲染是在服务器端完成的。当用户请求页面时,服务器会生成完整的 HTML 内容, 并将其发送给客户端。客户端接收到 HTML 后,可以直接显示页面内容,无需等待额外的数据加载或渲染过程。这种方式可以提供更快的首次 加载速度和更好的搜索引擎优化(SEO),但在交互性方面可能稍逊于 CSR。 SSG(Static Site Generation):SSG 是一种在构建时预先生成静态 HTML 文件的技术。在构建过程中,页面的内容会被提前生成为静态文 件,然后在用户请求时直接提供这些静态文件。这种方式结合了 SSR 和静态文件的优点,可以提供快速的加载速度和良好的性能。然而,SSG 适用于内容相对静态的网站,对于频繁更新的内容或动态交互性较强的页面,可能需要其他技术的支持。 SPA(Single-Page Application):是一种前端应用程序的架构模式,与传统的多页面应用程序(MPA)相对应。在 SPA 中,整个应用程序加载 一次,之后在单个页面上进行动态的内容更新和交互,而无需每次页面切换时重新加载整个页面。 CSR(Client-Side Rendering):在 CSR 中,页面的初始渲染是在客户端(浏览器)中完成的。当用户请求页面时,服务器会返回一个基本的 HTML 骨架和一些 JavaScript 代码。然后,浏览器会下载并执行这些 JavaScript 代码,以动态地生成和渲染页面内容。这种方式可以提供更 丰富的交互性和动态性,但可能会导致较慢的首次加载速度和对搜索引擎的不利影响。

2、 react hooks 为什么不能写在循环或条件判断语句中

React团队为Hooks设定了两个基本规则:

  1. 只在最顶层使用Hooks:不要在循环、条件或嵌套函数中调用Hooks。
  2. 只在React函数中调用Hooks:不要在普通的JavaScript函数中调用Hooks。 为什么避免在条件语句和循环中使用Hooks
  3. 维持调用顺序 React的函数组件是声明式的;它们可能会因为父组件的变化或者自身的状态更新而多次执行。React Hooks必须在每次组件渲染时以相同的顺序被调用,以便React能够正确地追踪每个Hook的状态。如果我们在条件语句或循环中使用Hooks,就可能打乱这个顺序,导致状态管理出现问题。
  4. 避免逻辑错误 在条件语句中使用Hooks可能会导致组件在不同的渲染周期中创建不同数量的Hooks,这违反了React的规则。例如,如果一个Hook只在特定条件下被调用,那么在条件改变时,React将无法找到对应的状态,从而引发错误。
  5. 代码的可读性和可维护性 将Hooks放在条件语句或循环中会使得代码更难阅读和维护。开发者在阅读代码时,可能会对组件的状态来源和逻辑流程感到困惑。遵守Hooks的规则有助于保持代码的清晰和一致性。

3、useref 在实际开发中的应用

  1. 获取上一次的值
  2. 使用useRef来保存不需要变化的值
  3. 让父组件调用子组件的方法

访问 DOM 元素 保存状态但不触发渲染 保存上一次的 props 或 state 保存全局状态 实现组件间的通信

4、父组件调用子组件方法

  1. 使用Refs调用子组件的方法
  2. 使用回调函数作为props(钩子函数)
  3. 使用Context
  4. 使用Redux或MobX等状态管理库

5、useEffect 和 useLayoutEffect 有什么区别

  1. 触发时机: useEffect:在浏览器绘制完成后异步执行。它不会阻塞浏览器的渲染过程,因此可能在用户看到最终渲染效果后,相关的副作用操作才开始执行。 useLayoutEffect:在浏览器绘制之前同步执行。这意味着在DOM更新完成后,useLayoutEffect会立即执行其副作用操作,这有助于在DOM更新后立即执行与布局相关的操作,避免潜在的渲染闪烁。
  2. 执行时机: useEffect:适用于大多数副作用操作,如数据获取、订阅事件等。由于它不会阻塞渲染,因此可能导致用户在页面渲染完成后才看到最终效果。 useLayoutEffect:更适用于需要在DOM更新后立即执行且与布局相关的副作用操作。由于它在绘制之前同步执行,因此可以确保副作用的执行不会引起渲染跳跃,提供更流畅的用户体验。
  3. 阻塞性: useEffect:非阻塞性,不会阻止屏幕更新。 useLayoutEffect:阻塞性,如果其执行的操作非常耗时,可能会导致页面响应变慢,因为它在绘制之前同步执行。
  4. 使用场景: useEffect:更通用,适用于大多数副作用操作场景。 useLayoutEffect:更适用于需要在DOM更新后立即进行与布局相关的操作,例如获取DOM元素的尺寸、位置等。
  5. 清理副作用: 两者都支持在组件卸载或重新渲染时清理副作用。useEffect和useLayoutEffect都可以接收一个清除函数作为副作用函数的返回值,该函数会在组件卸载或下一次副作用函数执行前被调用。
  6. 调用顺序: 当组件挂载或重新渲染时,useLayoutEffect会在DOM更新后立即同步执行,然后浏览器才会进行绘制操作。而useEffect会在浏览器绘制完成后异步执行。

总结来说,useEffect和useLayoutEffect的主要区别在于触发时机和执行方式。useEffect更适用于大多数副作用操作场景,而useLayoutEffect则更适用于需要在DOM更新后立即执行且与布局相关的操作。在大多数情况下,使用useEffect即可满足需求,而在需要确保DOM更新后立即执行副作用操作的情况下,可以考虑使用useLayoutEffect。但请注意,由于useLayoutEffect的阻塞性特性,如果其执行时间过长,可能会影响页面性能。

6、在react里面,我们可以做哪些页面优化

  1. 减少不必要的渲染 使用shouldComponentUpdate或React.memo:React组件在状态或属性发生变化时会重新渲染。通过实现shouldComponentUpdate方法或在函数组件中使用React.memo,可以控制组件的渲染行为,避免不必要的渲染。 使用PureComponent:PureComponent是React提供的一个基类,它会自动实现shouldComponentUpdate方法,通过浅比较props和state来避免不必要的渲染。但请注意,浅比较可能不适用于所有场景,特别是当props或state包含复杂数据结构时。
  2. 优化状态管理 使用不可变数据:在React中,当组件的props或state发生变化时,组件会重新渲染。为了避免不必要的渲染,应该尽量使用不可变数据。这意味着当数据发生变化时,创建一个新的对象或数组,而不是修改现有对象或数组。 使用useReducer管理复杂状态:当组件的状态逻辑变得复杂时,使用useReducer代替useState可以更有效地管理状态。useReducer提供了一种更结构化的方式来处理状态更新,使得代码更易于理解和维护。
  3. 优化列表渲染 使用key属性:在渲染列表时,为每个列表项指定一个唯一的key属性可以帮助React识别哪些项发生了变化,从而避免不必要的渲染。 使用虚拟化(Windowing):当列表项数量非常大时,一次性渲染所有项会导致性能问题。虚拟化是一种只渲染可见部分列表项的技术,可以显著提高性能。
  4. 代码拆分和懒加载 使用React.lazy和Suspense:对于大型应用,将代码拆分成多个小模块可以显著提高加载速度。 React.lazy使我们能够动态加载组件,而Suspense则提供了一种在组件加载过程中显示占位符的方式。
  5. 缓存 使用React.memo缓存组件:React.memo可以缓存函数组件,只有在传入组件的状态值发生变化时才会重新渲染。 使用useMemo缓存计算:大型计算函数,可以使用useMemo来“记忆”这个计算函数的计算结果。这样只有传入的参数发生变化后,计算函数才会重新调用计算新的结果。
  6. 优化React应用的启动速度 使用CSS的display: none:将一些不常用的组件设置为display: none,在需要时再显示,可以减少初始页面的渲染时间。
  7. 提交阶段优化 使用requestIdleCallback:在浏览器空闲时执行高开销的任务,以避免阻塞主线程。

7、hash路由和browse路由有什么区别?

  1. 原理上 Hash路由(HashRouter):
    • 在路径中包含了#,相当于HTML的锚点定位。
    • 主要原理是onhashchange()事件,当页面的hash值发生变化时,无需向后端发起请求,window就可以监听事件的改变,并按规则加载相应的代码。
    • 浏览器兼容性较好,低版本的IE浏览器也支持这种模式。 Browse路由(BrowserRouter):
    • 使用的是HTML5的新特性History API,没有HashRouter(锚点定位)那样通用,低版本浏览器可能不支持。
    • 使用了HTML5为浏览器全局的history对象新增的两个API,包括history.pushState、history.replaceState,实现URL的变化和管理。
    • 需要后台配置支持,如果后台没有正确配置,访问时会返回404。
  2. 用法上 Hash路由:
    • 路径中带有#,例如:www.abc.com/#/vue。
    • 不能直接传递参数实现组件间的通信(除非手动拼接URL字符串),一般配合Redux使用实现组件间的数据通信。 Browse路由:
    • 路径中不带#,例如:abc.com/user/id。
    • 进行组件跳转时可以传递任意参数实现组件间的通信。
  3. 生产实践上 Hash路由:
    • 相当于锚点定位,因此不论#后面的路径怎么变化,请求的都相当于是#之前的那个页面。
    • 可以很容易地进行前后端不分离的部署,因为请求的链接都是ip地址:端口/#/xxxx,请求的资源路径永远为/,相当于index.html,不会和后端API接口冲突。
    • 缺点是路径中带有#,可能不够优雅。 Browse路由:
    • 请求的链接都是ip地址:端口/xxxx/xxxx,相当于每个URL都会访问一个不同的后端地址。
    • 如果后端没有覆盖到路由就会产生404错误,需要通过配置服务器或中间件来处理。
    • 适用于前后端分离的部署方式,如前端地址ip1:端口1,后端接口地址ip2:端口2,使用Nginx等反向代理服务器进行请求分发。

8、js中的普通函数和箭头函数的区别

  1. 写法不同
  2. 箭头函数都是匿名函数
  3. 箭头函数不能作为构造函数
  4. this指向不同。(箭头函数的 this 永远指向其上下文的 this ,任何方法都改变不了其指向,如 call() , bind() , apply();普通函数的this指向调用它的那个对象)
    • 普通函数的this总是指向调用它的对象,如果用作构造函数this只想创建的对象实例
    • 箭头函数本身不创建this,它在申明时可以捕获其所在上下文this供自己使用
  5. 每一个普通函数调用后都具有一个arguments对象存储实际传递的参数。但是箭头函数并没有此对象

9、闭包、高阶函数和函数柯里化的概念和作用

  1. 闭包
    • 定义: 闭包就是能够读取其他函数内部变量的函数。在JavaScript中,闭包可以理解为“定义在一个函数内部的函数”。闭包包含自由(未绑定到特定对象)变量,这些变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(局部变量)。
    • 作用: 读取函数内部的变量。 让这些变量的值始终保存在内存中,因为闭包的执行依赖外部函数中的变量,只有闭包执行完,才会释放变量所占的内存。
    • 注意: 由于闭包会使得函数中的变量都被保存在内存中,内存消耗大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能会造成内存泄露。 闭包会在父函数外部,改变父函数内部变量的值。如果把父函数当做对象使用,把闭包当做公用方法,把内部变量当做私有属性,此时不要随便改变父函数内部变量的值。
  2. 高阶函数
    • 定义: 高阶函数是指能够接受一个或多个函数作为参数、或者返回一个函数作为结果的函数。
    • 作用: 增强代码的灵活性和可重用性,从而提高开发效率。 实现更加通用的算法、惰性执行、闭包等功能。 方便地进行函数合成、函数柯里化等操作。 实现函数式编程的思想,使代码更加简洁、清晰。
  3. 函数柯里化
    • 定义: 柯里化(Currying)是一种函数转换技术,它将接受多个参数的函数转换为一系列只接受一个参数的函数。
    • 作用: 增加函数的灵活性和可复用性。 将一个函数拆分为多个函数,每个函数只负责处理一个参数。这使得我们可以根据需要逐步传递参数,而不是一次性传递所有参数。 可以通过组合不同的函数来创建新的函数,从而实现代码的复用。

10、dom事件流,如点击网页里的一个按钮,它的点击事件是怎样的流程

  1. 捕获阶段(Capturing Phase): 从最外层的祖先元素(通常是document对象)开始,向目标元素(即被点击的按钮)进行事件的捕获。 在捕获阶段,事件会从根元素(document)通过DOM树向下传播到目标元素,但是在这个阶段,默认情况下并不会触发事件处理函数。 如果想要在捕获阶段就触发事件处理函数,可以通过调用addEventListener方法并设置其第三个参数为true来实现。
  2. 目标阶段(Target Phase): 当事件流传播到目标元素(即被点击的按钮)时,捕获阶段结束,开始在目标元素上触发事件。 在这个阶段,事件处理函数会针对目标元素执行。
  3. 冒泡阶段(Bubbling Phase): 事件从目标元素(即被点击的按钮)开始,向其祖先元素传递,依次触发祖先元素上的事件处理函数。 在冒泡过程中,最内侧元素(即目标元素)的事件会首先被处理,然后是更外侧的祖先元素。 如果不希望发生事件冒泡,可以通过事件对象来取消冒泡,在遵从W3C标准的浏览器中,可以调用事件对象上的stopPropagation()方法;

11、如何取消事件的默认行为

  1. 使用event.preventDefault()方法: 这是最常用的方法,用于取消事件的默认行为。首先,你需要获取到事件对象,这通常是通过事件处理函数的参数传递的,通常命名为event或e。接着,在事件处理函数中调用event.preventDefault()方法即可取消默认行为。
  2. 使用return false语句: 在一些特定的事件处理函数中,可以直接使用return false来取消事件的默认行为。 但需要注意的是,return false不仅会取消默认行为,还会阻止事件的冒泡传播。
  3. 结合使用event.preventDefault()和event.stopPropagation(): 如果需要同时取消事件的默认行为和阻止事件冒泡,可以结合使用这两个方法。

12、target 和 currentTarget

target和currentTarget的区别主要体现在事件处理过程中它们所指向的元素和所起的作用上。以下是对这两个概念的详细解释和比较: 一、定义与指向 - target: 定义:target是事件的调用对象,即触发事件的元素。换句话说,它是用户直接操作的那个元素,比如用户点击了一个按钮,那么这个按钮就是target。 指向:它总是指向事件的实际目标,即事件最初发生的元素。 - currentTarget: 定义:currentTarget是事件的处理对象,即当前正在处理事件的元素。在事件处理过程中,事件可能会在不同的元素间传播(如冒泡或捕获),currentTarget表示当前正在处理该事件的元素。 指向:它指向绑定事件监听器的元素,这个元素可能是事件的直接目标,也可能是在事件传播路径上的其他元素。 二、在事件流中的行为 - target: 在事件流的任何阶段,target都保持不变,始终指向触发事件的元素。 - currentTarget: 在事件流的捕获和冒泡阶段,currentTarget可能会变化。在捕获阶段,它从根元素开始,随着事件的传播逐渐接近目标元素;在冒泡阶段,它从目标元素开始,随着事件的传播逐渐向上层元素移动。 只有在事件流的目标阶段,即事件直接在目标元素上触发时,currentTarget才与target相同。 三、使用场景与意义 - target: 当我们需要知道是哪个具体元素触发了事件时,可以使用target。例如,在点击事件中,target会告诉我们用户点击了哪个元素。 - currentTarget: 当我们使用事件委托模式时,currentTarget非常有用。在这种模式下,我们可能在一个父元素上设置事件监听器来处理其子元素的事件。此时,currentTarget会指向设置监听器的父元素,而target会指向实际触发事件的子元素。 通过比较currentTarget和target,我们可以确定事件是否在预期的元素上触发,或者事件是否已经传播到了其他元素。 综上所述,target和currentTarget在事件处理中各自扮演着重要的角色。target帮助我们确定事件的源头,而currentTarget则告诉我们当前正在处理事件的元素是哪个。

13、script标签的 defer 和 async

- async异步执行,浏览器在加载页面的时候如果遇到了async属性,浏览器会立即进行下载和执行,同时继续加载页面。

会存在页面没有加载完script内容就执行了,也有可能页面加载后才执行,因为这种不确定性,如果脚本是需要修改DOM的就有可能出错,所以async比较适合一些第三方脚本

- defer推迟执行,浏览器在加载页面的时候如果遇到了defer属性,浏览器会立即下载,同时继续加载页面。但是不管脚本是否下载完,都会等浏览器解析完HTML后再执行

因此defer比较适合与DOM有关联的脚本。

不管是async还是defer都只适用于外部脚本,还要注意兼容性问题。如果浏览器不能识别这两个属性,那还是把script内容放在页面底部比较好。

14、ts 的泛型有什么作用

  1. 代码复用性:
    • 泛型允许开发者编写具有通用性的代码,而不必提前指定具体的数据类型。
    • 通过使用泛型,可以编写能够处理多种数据类型的函数、类或接口,从而减少了重复代码的量。
    • 例如,在处理集合类型(如数组、列表)时,泛型可以使算法更加灵活,因为它们可以在不同的数据类型上工作。
  2. 类型安全性:
    • 泛型在编译时提供类型检查,确保类型的一致性,从而降低了运行时错误的风险。
    • 使用泛型编写的代码在编译时会根据传入的类型参数来确定操作的数据类型,这有助于在开发阶段捕获并修复类型错误。
  3. 灵活性:
    • 泛型提供了一种将类型参数化的方法,使得代码能够处理不同类型的数据,而无需为每个类型编写单独的函数或类。
    • 通过将类型由原来具体的类型变成一种类型参数,然后在调用时才传入具体的类型作为参数,泛型增加了代码的灵活性。
  4. 提高代码的可读性和可维护性:
    • 泛型的使用可以使代码更加清晰和简洁,因为它们减少了重复的代码结构。
    • 当需要修改数据类型时,只需要更改类型参数,而无需修改整个函数或类的实现。
  5. 扩展性和可维护性:
    • 泛型为代码提供了良好的扩展性,因为当需要添加新的数据类型时,只需要编写一个使用泛型的函数或类,而无需为每个新类型编写新的代码。
    • 同时,泛型也有助于提高代码的可维护性,因为它们减少了代码的冗余和复杂性。

15、浏览器什么情况下重排,什么情况重绘

重排(Reflow) 重排是浏览器重新计算并绘制元素的过程,也称为回流或重构。当渲染树中的一部分(或全部)因为元素的规模尺寸、布局、隐藏等改变而需要重新构建时,就会触发重排。 1. 改变窗口大小:当用户调整浏览器窗口大小时,浏览器需要重新计算和调整页面布局,因此会触发重排。 2. 改变字体大小:修改页面中文本的字体大小可能导致所有相关元素的重排。 3. 改变元素的位置:通过修改元素的位置属性(例如position、top、left等)来移动元素会触发重排。 4. 改变元素的尺寸:修改元素的宽度和高度,包括边距、填充和边框大小,都可能导致重排。 5. 改变元素的内容:如果通过JavaScript动态添加或删除文档的内容,可能导致包含该内容的元素重排。 6. 计算元素的尺寸或位置:当通过JavaScript访问offsetTop、offsetLeft、offsetWidth、offsetHeight等属性时,浏览器可能需要重新计算元素的位置和尺寸触发重排。 7. 更改字体:修改页面上的字体或字体相关属性可能导致浏览器重新计算文本的大小和布局,触发重排。 8. 改变浏览器默认字体大小:在某些情况下,浏览器默认字体大小的改变可能会导致所有文本的重排。 9. 激活CSS伪类:例如:hover、:active等伪类样式的激活可能会导致元素的重排。 10. 修改表格布局:改变表格的列宽或行高会导致表格重新布局,从而触发重排。 重绘(Repaint) 重绘是指页面的外观发生变化,但不影响布局位置的情况。在重绘过程中,浏览器只需重新绘制受影响的元素,而无需重新计算布局。以下是一些常见的触发重绘的操作: 1. 改变元素外观属性:如color、background-color等。 2. 修改轮廓相关属性:更改元素的outline或text-decoration等。 3. 修改透明度或动画:调整元素的透明度或添加动画效果。 4. 显示或隐藏元素:隐藏或显示元素会触发重绘。 总结 - 重排:涉及页面布局的变化,需要重新计算和调整页面元素的位置与大小,是比较耗费性能的操作。 - 重绘:仅涉及元素外观的改变,不需要重新计算布局,相比重排消耗的性能较低。 - 重排必定会引发重绘,但重绘不一定会引发重排。为了避免频繁的重排和重绘操作对性能的影响,开发者可以采取一些优化措施,如合并多个DOM操作、使用CSS动画而非JavaScript动画、使用transform和opacity属性来进行动画等。

16、box-sizing

box-sizing是一个CSS属性,用于指定元素的总宽度和高度的计算方式。它可以设置以下两个值:

  1. content-box: • 这是box-sizing的默认值。 • 它指定元素的宽度和高度仅包括内容区域,不包括填充(padding)、边框(border)或外边距(margin)。 • 换句话说,指定的宽度和高度值仅适用于内容框,即padding、border、margin等都会撑大整个元素的宽高。 • 假设一个元素设置了width: 200px; padding: 10px; border: 5px solid black;,并且box-sizing为content-box,那么该元素的总宽度将是200px(内容宽度)+ 10px * 2(左右padding)+ 5px * 2(左右border)= 230px。
  2. border-box: • 它指定元素的宽度和高度包括内容区域、填充(padding)和边框(border),但不包括外边距(margin)。 • 使用border-box,元素的总宽度和高度保持不变,并且任何指定的填充或边框值都将包含在指定的尺寸内。 • 假设同样的元素设置了width: 200px; padding: 10px; border: 5px solid black;,并且box-sizing为border-box,那么该元素的总宽度将直接为200px,因为padding和border的值已经被包含在这200px之内了。 综上所述,box-sizing属性允许开发者选择是否将元素的padding和border包含在其指定的width和height内,从而更精确地控制元素的尺寸和布局。

17、flex-direction、flex-grow 属性

flex-direction 属性定义了flex容器中子元素的排列方向。它有以下四个可能的值: row(默认值):子元素水平排列,从左到右。 row-reverse:子元素水平排列,从右到左。 column:子元素垂直排列,从上到下。 column-reverse:子元素垂直排列,从下到上。

flex-grow 属性定义了flex容器中的子元素在剩余空间中的放大比例。如果所有子元素的 flex-grow 属性值都相同,那么它们将平均分配剩余空间。如果某个子元素的 flex-grow 值比其他子元素大,那么它将占用更多的剩余空间。 flex-grow 是一个数字值,默认值为0,表示子元素不会放大以占用额外的空间。如果设置为一个正数,那么子元素将按照比例放大。

18、用css画一个半圆、一个扇形

在CSS中,要画一个半圆或者扇形,可以使用border-radius属性配合div元素。但是,由于border-radius只能创建完整的圆形或椭圆形的角,我们不能直接用它来画一个半圆。 可以使用伪元素(:before或:after)和border-radius来绘制半圆或扇形