一小时复习前端面试|2024年年初30道面试题冲刺金三银四
这套题目列举的都是前端面试最最高频的问题,如果回答的太差,会减分!回答好了基本中高级没问题。
*****************************************
1. 从输入url到渲染页面
- 浏览器的地址栏输入URL并按下回车。
- 浏览器查找当前URL是否存在缓存,并比较缓存是否过期。
- DNS解析URL对应的IP。
- 根据IP建立TCP连接(三次握手)。
- HTTP发起请求。
- 服务器处理请求,浏览器接收HTTP响应。
- 渲染页面,构建DOM树。
- 关闭TCP连接(四次挥手)
2. Js中的单线程和事件循环
- Js是单线程,但是浏览器是多线程。
- Js中采用了事件循环(Event Loop)来执行异步任务。
- 所以,事件循环是一种异步编程模型,事件循环会不断地从任务队列(Task Queue)中取出待处理的任务并执行,直到任务队列为空为止。任务可以分为两类:宏任务(Macro Task)和微任务(Micro Task)。
- 微任务会优先于宏任务执行
3. Js实现继承有哪几种方式?
- 原型链继承:将父类的实例作为子类的原型,通过 prototype 进行继承
- 构造继承:将父类的实例属性复制给子类,通过 call 进行继承
- 实例继承:为父类实例添加新特性,作为子类实例返回
- 拷贝继承:将父类实例通过循环拷贝给子类
- 组合继承:就是 原型链继承 和 构造继承,一起使用
- 寄生组合继承:通过寄生方式,砍掉父类的实例属性,避免了 组合继承中,在调用两次父类的构造时,初始化两次实例方法/属性 的缺点
4. Js 中bind(),call()和apply()的区别
JavaScript 中的 bind()、call() 和 apply() 方法都可以用来改变函数内部的 this 指向。
它们有一些重要的区别:
- 三种方法最大的区别在于参数传入方式不同:bind() 方法接受一系列参数列表,而 call() 和 apply() 方法则分别接受一组参数和一个参数列表。具体而言,bind() 将参数作为一个个单独的值传入,而 call() 和 apply() 都允许传递一个数组作为参数。
- 执行时间不同:bind() 绑定后返回一个新函数,并不会立即执行,需要调用该函数才会执行;而 call() 和 apply() 则会立即执行函数。
- 返回值不同:bind() 方法返回一个绑定后的新函数,而 call() 和 apply() 则直接执行原始函数并返回执行结果。
它们的作用分别如下:
- bind() 方法:bind() 可以指定函数内部的 this 指向,并将其绑定到一个新函数上进行返回。该函数并不会立即执行,而是等待调用。bind() 也可以用来实现柯里化(currying)
- call() 方法:call() 可以在指定的 this 值和若干个参数(参数的列表)的前提下调用某个函数或方法。注意,call() 方法需要将参数逐个传递进去,而不能像 apply() 方法一样将所有参数打包成一个数组。
- apply() 方法:apply() 和 call() 的作用非常类似,都是改变函数内部的 this 指向。区别在于,apply() 方法需要将参数打包成一个数组传递进去,而 call() 则是将参数逐个传递。
5. Js 中的闭包及其使用场景
- 官方说法:闭包就是指有权访问另一个函数作用域中的变量的函数。
- MDN说法:闭包是一种特殊的对象。它由两部分构成:函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成。
- 深度回答:浏览器在加载页面会把代码放在栈内存( ECStack )中执行,函数进栈执行会产生一个私有上下文( EC ),此上下文能保护里面的使用变量( AO )不受外界干扰,并且如果当前执行上下文中的某些内容,被上下文以外的内容占用,当前上下文不会出栈释放,这样可以保存里面的变量和变量值,所以我认为闭包是一种保存和保护内部私有变量的机制。
*****************************************
6. Vue2与Vue3中的双向数据绑定
双向数据绑定就是:数据劫持 + 发布订阅模式(观察者模式)。
Vue2中在实例初始化时遍历 data 中的所有属性,并使用 Object.defineProperty把这些属性全部转为 getter/setter。并 劫持各个属性 getter 和 setter,在数据变化时发布消息给订阅者,触发相应的监听回调,而这之间存在几个问题
- 初始化时需要遍历对象所有 key,如果对象层次较深,性能不好
- 通知更新过程需要维护大量 dep 实例和 watcher 实例,额外占用内存较多
- Object.defineProperty 无法监听到数组元素的变化,只能通过劫持重写数方法
- 动态新增,删除对象属性无法拦截,只能用特定 set/delete API 代替
- 不支持 Map、Set 等数据结构
Vue3中使用 Proxy 来监控数据的变化。Proxy 是 ES6 中提供的功能,其作用为:用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。
7. Vue中template模版的编译原理
- 解析(parse):将模板字符串解析成 AST(抽象语法树)。
- 静态分析(static analysis):对 AST 进行静态分析,标记出其中的静态节点(Static Node)。
- 优化(optimize):遍历 AST,对静态节点进行优化,去掉不必要的操作。
- 代码生成(code generation):将 AST 转换成渲染函数(render function)的可执行代码。
- 最终的渲染:将生成的渲染函数运用到数据上,最终生成视图。
8. Vue2中的diff流程
Vue2中的虚拟DOM diff算法,其核心是采用双端比较(Two-Ended Algorithm)。
具体来说,Vue2中diff算法的步骤如下:
- 首先比较新旧虚拟DOM树的根节点,如果它们是不同类型的节点,则直接替换整个节点树;
- 如果根节点相同,则比较它们的子节点,这个过程称之为“Diff Children”;
- 在“Diff Children”过程中,使用双端比较算法,即同时从新虚拟DOM树和旧虚拟DOM树的两端开始向中间遍历,找到相应的节点进行比较,找到更新的节点后就停止比较;
- 如果新旧虚拟DOM树的子节点数组长度不同,则根据差异进行添加或删除节点;
- 对于相同位置的节点,判断它们是否相同,如果不同则进行更新;
- 最后返回新的虚拟DOM树。
9. Vue3中的diff流程
在Vue3中,Diff流程主要分为两个阶段:标记阶段和应用阶段。其中标记阶段是用来比较新老VNode树的差异,并记录下来需要进行的具体操作;应用阶段则是将这些具体操作应用在真实DOM上,完成页面的更新。
10. Vue中的组件间通信有哪些?
- props:父组件通过 props 属性向子组件传递数据。子组件接收该数据后,即可在其模板中直接使用。
- $emit() 和事件:子组件通过 $emit() 方法触发一个自定义事件,并把需要传递的数据作为参数传入。父组件监听该自定义事件,并在回调中处理数据。通过事件可以实现任意级别的组件间通信。
- $parent 和 $children:父组件可以通过 $children 找到所有的子组件,子组件可以通过 $parent 找到其父组件。这种方式属于直接引用和修改组件对象,需要注意和谨慎使用。
- provide 和 inject:祖先组件通过 provide 属性向子孙组件传递数据,子孙组件通过 inject 属性来获取这些数据。provide 和 inject 绑定并非响应式的,但是可以将一个观察者实例注入到 provide 中,使得组件在 inject 期间发生变化时获得通知。
- Vuex 状态管理(Vue3中的pinia):Vuex 是一个专门为 Vue.js 应用程序开发的状态管理库,提供了一种集中式存储管理应用程序中的所有组件的状态。组件通过调用 mutation 方法来改变状态,其他组件通过监听 state 属性来获取最新的状态。
- 兄弟组件间的传值:Vue2中使用eventBus中央事件总线 , Vue3中使用的mitt库
*****************************************
11. Vue-router中如何实现懒加载?
在路由配置文件中使用动态导入import()语句,并用箭头函数返回实现路由懒加载。
12. Vue3相对于Vue2进行了哪些优化?
- 更灵活的响应式系统:Vue 2.x 中响应式系统的核心是 Object.defineProperty,劫持整个对象,然后进行深度遍历所有属性,给每个属性添加getter和setter,实现响应式。Vue 3.x 中使用 Proxy对象重写响应式系统。
- 更快的渲染速度:Vue3 的编译器生成的渲染函数比 Vue2 生成的更高效。
- 编译阶段:Vue 2.x 通过标记静态节点,优化 diff 的过程。Vue 3.x中标记和提升所有的静态节点,diff的时候只需要对比动态节点内容。
- 更小的体积:Vue3 将源码拆分为多个独立的模块,这样就可以按需导入所需的模块,从而减小了整个库的体积。
- 更好的 TypeScript 支持:Vue3 对 TypeScript 的支持更加友好,内部使用了更先进的 TypeScript 特性,并为其提供了更好的声明文件。
- 更好的组件系统:比如,Vue3中引入了一个新的 Fragment 组件,它可以替代原来的 template 标签作为根节点
- 新增了setup组合式API
13. 介绍一下React Fiber
React Fiber是React框架的一种重新实现,旨在改善渲染性能和用户体验。它通过引入优先级调度、增量渲染和可中断的工作单元等机制,将渲染任务分解成小的可中断的单元,从而使React能够更好地处理大型应用程序和高优先级任务,提供流畅且响应迅速的用户界面。
14. React中常用的高阶组件有哪些?
- withRouter:将路由信息注入到组件中,使它们能够访问到路由对象(如location、history和match等)。
- connect:将React组件与Redux Store连接起来,并将State和Dispatch作为Props传递给组件。这使得组件能够直接从Store中读取和操作数据。
- memo:对于纯函数组件,使用memo可以缓存组件输出,以提高性能。
- withStyles:用于添加CSS样式到组件中。
- redux-thunk:使Action Creator返回一个函数而不是一个Action对象,从而可以执行异步操作并dispatch新的Action。
- recompose:提供了一组高阶功能,用于增强函数式React组件。例如,compose函数可以将多个HOC组合在一起。
- react-redux:提供了一组基于Redux Store的React组件,并简化了React与Redux之间的集成。
15. React Hook为什么不能放到条件语句中?
React Hook 不能放到条件语句中的原因是:React 需要使用 Hook 的规则是必须确保每次渲染时,Hook 调用的顺序都是一致的。也就是说,在一个组件内部,每一次渲染时,Hook 的调用顺序必须是相同的。
如果将 Hook 放到条件语句中,当条件发生变化时,Hook 的调用顺序就可能被打乱,从而导致组件状态不一致、出现错误等问题。因此,React 在运行时会对 Hook 的调用顺序进行验证,来确保 Hook 的使用符合规范。
*****************************************
16. React有哪些常用的hooks?
- useState:该 Hook 用于在函数组件中添加一个状态管理器。通过 useState,可以创建一个状态变量及其更新函数,并在组件内使用该变量来保存和更新组件的状态。
- useEffect:该 Hook 用于在组件渲染完成后执行一些副作用操作(例如订阅数据、更新 DOM 等)。通过 useEffect,可以在组件加载、更新和卸载时设置和清理副作用操作,并且可以在副作用操作之间共享状态。
- useContext:该 Hook 用于在组件之间共享一些全局的状态或函数,以避免通过多层嵌套的 Props 传递进行数据传输。通过 useContext,可以让组件在全局状态或函数的上下文中运行,并让它们能够方便地读取或更新全局状态或函数。
- useReducer:该 Hook 用于在组件中使用一种“状态容器”模式,以避免通过多层 Props 传递或 Context 共享进行状态管理。通过 useReducer,可以创建一个状态容器及其更新函数,并在组件内使用该容器来保存和更新组件的状态。
- useMemo:该 Hook 用于在组件渲染完成后缓存一些计算结果,以避免因为重复计算导致的性能问题。通过 useMemo,可以创建一个缓存变量,并在组件内使用该变量来保存计算结果并缓存。
- useCallback:该 Hook 用于在组件渲染完成后,将一些函数进行缓存,以避免因函数重复创建导致的性能问题。通过 useCallback,可以创建一个缓存函数,并在组件内使用该函数来代替重复创建的函数。
- useRef:该 Hook 用于在组件渲染完成后创建一个引用,以便在组件多次渲染时能够保留上一次渲染中的值。通过 useRef,可以创建一个引用变量,并在组件内使用该变量来保存一些持久化的数据。
- useImperativeHandle:该 Hook 用于在组件中实现一些自定义的 Ref 对象,并且要求将一些组件内部的方法或状态暴露给父组件使用。通过 useImperativeHandle,可以创建一个自定义的 Ref 对象,并在组件内指定一些公开的方法或属性。
- useLayoutEffect:该 Hook 与 useEffect 类似,但它会在浏览器渲染更新之前同步执行副作用操作,以确保 React 组件与浏览器同步更新。通常情况下,应该使用 useEffect,但在需要直接操作 DOM 元素或进行测量布局界面时,应当使用 useLayoutEffect。
- useDebugValue:该 Hook 可以帮助开发者在调试工具中显示额外的信息,以便更好地理解 Hook 的使用和行为。通常情况下,这个 Hook 只用于调试过程中,而不是实际的应用程序代码中。
17. 介绍下React中的useEffect
- 在 React 中,useEffect 是一个用于处理副作用的 Hook。
- 副作用是指在组件生命周期中的某些特定时刻需要执行的操作,例如数据获取、订阅事件、手动操作 DOM 等。
- useEffect 的作用就是在组件渲染完成后执行这些副作用操作。
18. 介绍一下Promise的状态及其方法
在JavaScript中,Promise对象包含三种状态:Pending(进行中)、Fulfilled(已成功)和Rejected(已失败)。
Promise的方法如下:
- Promise对象可以通过 then() 方法添加成功(Fulfilled)和失败(Rejected)时的回调函数。then() 方法可以链式调用,每次返回一个新的 Promise 对象,因此可以很容易地实现异步任务的连续执行。
- Promise对象还提供了 catch() 方法用于捕获错误和 finally() 方法用于在 Promise 被解析后运行代码块。
- Promise.all() 方法接收一个 Promise 数组作为参数,返回一个新的 Promise,只有当所有 Promise 都解析成功时才会被解析,否则该 Promise 会被拒绝。
- Promise.race() 方法接收一个 Promise 数组作为参数,返回一个新的 Promise,只要有一个 Promise 被解析或拒绝就会被解析或拒绝。
- Promise.resolve() 和 Promise.reject() 方法分别返回一个已解析和一个已拒绝的 Promise 对象,可以用于快速创建 Promise。
19. 介绍一下async/await 的实现原理
在 JavaScript 引擎中,async/await 函数的实现原理是基于 Promise 对象和生成器函数(Generator Function)的协作。
20. 介绍一下let、const、var的区别
*********************************
21. 箭头函数和普通函数有什么区别
- 写法不同:箭头函数使用箭头(=>)来定义,而普通函数使用 function 关键字定义。
- this 的处理方式不同:在箭头函数中,this 的值与外层作用域的 this 绑定。而在普通函数中,this 的值由调用该函数的方式决定。
- 箭头函数没有 arguments 对象:箭头函数中没有自己的 arguments 对象,它的参数只能通过参数列表来传递。
- 箭头函数不能用作构造函数:由于箭头函数中没有自己的 this 值,因此不能用作构造函数来创建对象实例。
22. Css中常用的垂直居中解决方案有哪些?
属性 | 用于控制元素内部的行内元素(如文本或图片)的垂直对齐方式。通常与
结合使用,实现单元格内部的元素垂直居中。 |
属性 | 用于设置行内元素的行高,可以使单行文本元素垂直居中。但是如果元素高度超过一行,则无法实现垂直居中。 |
布局 | 使用
和相关属性可以实现容器内元素的水平和垂直居中。 |
布局 | 使用
和相关属性可以实现网格布局中元素的水平和垂直居中。 |
绝对定位 + 负边距 | 将需要垂直居中的元素绝对定位到容器中心,然后通过负边距调整元素位置。 |
23. 什么是BFC?
BFC(Block Formatting Context)是 CSS 中一个很重要的概念。它是指一个块级容器,其中的元素按照特定规则布局和渲染,同时也影响着其内部和外部元素的布局。
24. Css中移动端适配有哪些方案?
- 首先,通过meta标签设置viewport
- rem单位搭配@media媒体查询:可以通过使用rem单位,它以HTML元素的font-size为比例,也可以搭配 postcss-pxtorem 搭建项目
- vw/vh 布局:也可以通过使用vw/vh 布局,vw/vh 方案与 rem 方案类似,都是将页面分成一份一份的,只不过 vw/vh 是将页面分为 100 份,也可以搭配 postcss-px-to-viewport 搭建项目
- 百分比布局:也可以使用百分比来实现布局,但是需要特定宽度时,这个百分比的计算对开发者来说并不友好,且元素百分比参考的对象为父元素,元素嵌套较深时会有问题。
25. 什么是Css中的回流(重排)与重绘?
回流(重排)(reflow)和重绘(repaint)是浏览器渲染页面时的两个核心概念。
- 回流(重排)指的是当页面中的元素发生布局或几何属性发生变化时,浏览器需要重新计算这些元素的位置和大小,然后重新构建页面的渲染树,这个过程称为回流。由于需要重新计算布局,回流的代价很大,会对页面的性能产生负面影响。
- 重绘指的是当页面中的元素样式发生改变时,浏览器会重新绘制这些元素的外观,但不会改变它们在页面中的位置和大小。重绘的代价相对较小,但仍然会对页面性能产生一定的影响。
*****************************************
26. 常用的跨域解决方案有哪些?
- CORS:跨域资源共享(Cross-Origin Resource Sharing),是一种允许浏览器向跨域服务器发送 Ajax 请求的机制,支持现代浏览器,服务器端需要设置 Access-Control-Allow-Origin 头信息,指定允许的源或通配符,从而实现跨域请求。
- 代理:在同源页面内部发送 AJAX 请求到同域服务器,由服务器代理转发请求到跨域服务器,最后再将结果返回给同源页面。
- WebSocket:WebSocket 是一种 HTML5 协议,它使得浏览器和服务器之间可以建立持久化的连接,可以直接使用 Socket 进行通信,避免了浏览器的跨域限制。
27. Webpack中有哪些核心概念?
- Entry(入口):Webpack在打包时需要从哪个文件开始构建依赖关系图,就是入口。可以设置多个入口文件,以生成多个输出文件。
- Output(输出):打包后的文件放在哪里,以及如何命名这些文件。可以指定输出目录、文件名、公共路径等。
- Loader(模块加载器):Webpack只能处理JavaScript文件,而其他类型的文件如CSS、图片等需要通过Loader转换才能被Webpack处理。Loader用于对模块内容进行转换处理。
- Plugin(插件):Plugin可以用于执行各种任务,例如打包优化、错误处理和环境变量注入等。Webpack本身只提供了一些基本的Plugin,但社区中有很多第三方Plugin可供使用。
- Mode(模式):Webpack提供了三种模式:development、production和none。不同的模式会启用不同的Webpack内置Plugin和Loader,以便于开发和生产环境的优化。
- Chunk(代码块):Webpack在打包时会把所有相关联的模块组成一个Chunk。可以通过Code Splitting技术将代码拆分成多个Chunk,以实现按需加载。
- Module(模块):Webpack把每个文件都看作一个模块,它可以是JavaScript、CSS、图片等。这些模块通过依赖关系进行组合,构成整个应用程序。
28. Vite 和 Webpack 的区别
- 优点:快速的冷启动: 采用No Bundle和esbuild预构建,速度远快于Webpack高效的热更新:基于ESM实现,同时利用HTTP头来加速整个页面的重新加载,增加缓存策略真正的按需加载: 基于浏览器ESM的支持,实现真正的按需加载
- 缺点生态:目前Vite的生态不如Webapck,不过我觉得生态也只是时间上的问题。生产环境由于esbuild对css和代码分割不友好使用Rollup进行打包
29. Webpack常见的优化方案有哪些?
- 升级 webpack 版本,3升4,实测是提升了几十秒的打包速度
- 使用Tree Shaking和Scope Hoisting来减少代码体积和模块构建时间,其中Tree Shaking可以去除未使用的代码,而Scope Hoisting可以将模块内的代码尽量合并到一个函数(单一作用域)中,以减少函数声明和闭包的数量。
- 使用splitChunksPlugin插件来将公共代码抽离成单独的chunk,以减少代码重复和提高缓存命中率。
- 合理配置resolve.alias和resolve.extensions选项来减少Webpack查找文件的时间。
- 针对生产环境,可以开启代码压缩以及多进程并行处理等优化方式,以减少构建时间和服务器负载。
- 使用DLLPlugin和DllReferencePlugin来预先编译一些稳定不变的代码,以减少每次构建的时间。
- 使用HappyPack来启用多线程并发处理,以加速代码构建和增强开发体验。
- 对于图片、字体等资源文件,可以通过url-loader和file-loader等loader设置较小的limit值,将文件转换成base64编码的字符串内嵌在js文件中,以减少http请求次数。
30. 浏览器中强缓存与协商缓存的缓存机制
强缓存和协商缓存是两种不同的缓存策略。
强缓存通过设置响应头中的Cache-Control或Expires字段,告诉浏览器在一定时间内直接使用本地缓存,不需要发送请求到服务器。只有当缓存过期或被清除时,浏览器才会发送请求到服务器获取新的资源。
协商缓存通过设置响应头中的ETag和Last-Modified字段,浏览器在每次请求时携带If-None-Match和If-Modified-Since字段,与服务器进行比较。如果资源未发生变化,则服务器返回304 Not Modified状态码,并告知浏览器继续使用缓存;如果资源发生变化,则服务器返回新的资源。
强缓存是基于时间的缓存控制,而协商缓存则是通过与服务器进行交互来判断是否使用缓存,更加灵活。
*****************************************