Web前端面试题(更新中...)

1、React生命周期中有哪些坑?如何避免?

  • getDerivedStateFromProps 容易编写反模式代码,使受控组件和非受控组件区分模糊

  • componentWillMount 在 React 中已被标记弃用,不推荐使用,主要的原因是因为新的异步架构会导致它被多次调用,所以网络请求以及事件绑定应该放到 componentDidMount 中

  • componentWillReceiveProps 同样也被标记弃用,被getDerivedStateFromProps 所取代,主要原因是性能问题。

  • shouldComponentUpdate 通过返回 true 或者 false 来确定是否需要触发新的渲染。主要用于性能优化。

  • componentWillUpdate 同样是由于新的异步渲染机制,而被标记废弃,不推荐使用,原先的逻辑可结合 getSnapshotBeforeUpdate 与 componentDidUpdate 改造使用。如果在 componentWillUnmount 函数中忘记解除事件绑定,取消定时器等清理操作,容易引发 bug。如果没有添加错误边界处理,当渲染发生异常时,用户将会看到一个无法操作的白屏,所以一定要添加。

2、调和阶段setState干了什么?

  • 1)当调用 setState 时,React会做的第一件事情是将传递给 setState 的对象合并到组件的当前状态
  • 2)这将启动一个称为和解 ( reconciliation)的过程,和解的最终目标是以最有效的方式,根据这个新的状态来更新UI,为此,React 将构建一个新的 React 元素树 (您可以其视为 UI的对象表示)
  • 3)一旦有了这个树,为了弄清 UI 如何响应新的状态而改变,React会将这个新树与上一个元素树相比较diff
  • 4)通过这样做, React 将会知道发生的确切变化,并且通过了解发生什么变化,只需在绝对必要的情况下进行更新即可最小化 UI的占用空间

3、Connect组件的原理是什么?

  • connect是一个高阶函数,它真正连接 Redux 和 React,包在我们的容器组件的外一层,接收上面 Provider 提供的 store 里面的 state 和 dispatch,传给一个构造函数,返回一个对象,以属性形式传给我们的容器组件

  • 原理

    • 首先传入mapStateToProps、mapDispatchToProps,然后返回一个生产Component的函数,然后再将真正的Component作为参数传入wrapWithConnect,这样就生产出一个经过包裹的Connect组件,该组件具有:
      • (1)通过props.store获取祖先Component的store
      • (2)props包括stateProps、dispatchProps、parentProps,合并在一起得到nextState,作为props传给真正的Component
      • (3)componentDidMount时,添加事件this.store.subscribe(this.handleChange),实现页面交互
      • (4)shouldComponentUpdate时判断是否有避免进行渲染,提升页面性能,并得到nextState
      • (5)componentWillUnmount时移除注册的事件this.handleChange首先connect之所以会成功,是因为Provider组件:
  • 在原应用组件上包裹一层,使原来整个应用成为Provider的子组件,接收Redux的store作为props

  • 通过context对象传递给子孙组件上的connect,它真正连接 Redux 和 React,它包在我们的容器组件的外一层,它接收上面 Provider 提供的 store 里面的 state 和 dispatch,传给一个构造函数,返回一个对象,以属性形式传给我们的容器组件。

4、为什么要有跨域?Web开发中会有哪些跨域问题?前端跨域的解决方案?

  • 为什么要有跨域:
    • 跨域是指在浏览器的同源策略下,当前页面所在的域名、协议、端口与请求的资源不一致时,浏览器为了安全起见,会阻止该请求的发送和数据的接收
  • 跨域问题:
    • 域名不同:比如从A域名向B域名发起请求。
    • 协议不同:比如从HTTPS协议向HTTP协议发起请求。
    • 端口不同:比如从A域名的8080端口向A域名的80端口发起请求。
  • 解决方案:
    • 同源策略
      • 它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指**"协议+域名+端口**"三者相同
      • 同源策略限制以下几种行为:Cookie、LocalStorage 和 IndexDB 无法读取DOM和JS对象无法获得ajax 请求不能发送
    • 9种跨域解决方案
      • 1、JSONP跨域:使用script标签来向其他域名发送请求,需要在服务器端将数据包装为一个函数调用,再返回给客户端
      • 2、跨域资源共享(CORS):使用服务器端的CORS配置,允许其他域名的请求访问当前域名的资源,需要在服务器端进行设置,比较灵活,但是需要浏览器的支持
      • 3、nginx代理跨域 :和nodejs中间件跨域原理都相似,都是搭建一个服务器,直接在服务器端请求HTTP接口,这适合前后端分离的前端项目调后端接口
      • 4、nodejs中间件代理跨域
      • 5、location.hash + iframe跨域
      • 6、window.name + iframe :使用window.name属性来实现跨域通讯。该属性可以在不同页面之间共享数据,但是只能共享字符串类型的数据,数据量也比较小
      • 7、iframe:在页面中嵌入一个隐藏的iframe,将请求发送到该iframe中,然后通过JavaScript来控制iframe中的内容,实现跨域通讯。

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

  • 浏览器缓存:

    • 是浏览器对之前请求过的文件进行缓存,以便下一次访问时重复使用,提高访问速度,告诉浏览器在约定的这个时间前,可以直接从缓存中获取资源而无需跑到服务器去获取
    • http缓存机制主要在http响应头中设定,响应头中相关字段为Expires、Cache-Control、Last-Modified、Etag
  • 强缓存:

    • 浏览器不会像服务器发送任何请求,直接从本地缓存中读取文件并返回Status Code: 200 OK
  • 协商缓存

    • 向服务器发送请求,服务器会根据这个请求的request header的一些参数来判断是否命中协商缓存,如果命中,则返回304状态码并带上新的response header通知浏览器从缓存中读取资源

6、大文件如何做断点续传?

  • 分片传

  • 将需要上传的文件按照一定的分割规则,分割成相同大小的数据块;

  • 初始化一个分片上传任务,返回本次分片上传唯一标识;

  • 按照一定的策略(串行或并行)发送各个分片数据块;

  • 发送完成后,服务端根据判断数据上传是否完整,如果完整,则进行数据块合成得到原始文件断电续传断点续传指的是在下载或上传时,将下载或上传任务人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分,而没有必要从头开始上传下载。用户可以节省时间,提高速度

7、react新出来两个钩子函数是什么?和删掉的will系列有什么区别

  • getDerivedStateFromProps和getSnapshotBeforeUpdate。

  • 区别

    • 1、componentWillMount中可能需要做的事(一些数据初始化的操作就应该放在这个钩子中处理),constructor与componentDidMount也能做,甚至做的更好,此方法被废弃。

    • 2、componentWillReceiveProps实际行为与命名并不相符,由于不稳定性已由getDerivedStateFromProps代替;

    • 3、而componentWillUpdate同等理由被getSnapshotBeforeUpdate代替

8、withRouter的作用?

把不是通过路由切换过来的组件中,将react-router 的 history、location、match 三个对象传入props对象上

不是所有组件都直接与路由相连的,当这些组件需要路由参数时,使用withRouter就可以给此组件传入路由参数,将react-router的history、location、match三个对象传入props对象上,此时就可以使用this.props.history跳转页面了或者接受参数了

9、React的props.children使用map函数来遍历会收到异常显示,为什么?应该 如何遍历?

在reactJS中props.children不一定是数组 有三种可能 :

  • 1当前组件没有子节点数据类型就是undefined,

  • 2有一个子节点数据类型就是object 。

  • 3 有多个子节点的时候才会是array ,只有在多个节点的时候才可以直接调用map方法,react资深提供了一个react.children.map()方法,可以安全遍历子节点对象。

10、redux中同步action与异步action最大的区别是什么?

  • 同步: Redux的教程中反复使用todo列表的例子,那就是个典型的同步action,每当disptach action时,state就会被立即更新,当然setState是异步的
  • 异步: 一般异步处理都会使用中间件,比如redux-thunk或者redux-saga,他们做的事情是包装dispatch,request action由view触发,receive action由这些中间件触发

11、CDN的特点及意义

  • CDN的功能特点:

    • (1)节省骨干网带宽,减少带宽需求量
    • (2)提供服务器端加速,解决由于用户访问量大造成的服务器过载问题
    • (3)服务商能使用WebCache技术在本地缓存用户访问过的Web页面和对象,实现相同对象的访问无须占用主干的出口带宽,并提高用户访问因特网页面的相应时间的需求
    • (4)能克服网站分布不均的问题,并且能降低网站自身建设和维护成本
    • (5)降低“通信风暴”的影响,提高网络访问的稳定性
  • 意义

    • 使用CDN可以获取一些好处,无论它们是公有CDN还是提供静态内容的私有CDN,你的里程可能会有所不同,具体取决于通过CDN传递的流量以及你产生的流量。

12 、什么是useReducer?函数组件中如何使用useReducer

  • 1)useReducer是React中的一个Hook函数,用来管理一些有着复杂逻辑状态的组件,它可以替代useState
  • 2)useReducer要接受一个Reducer函数和一个初始状态的函数并返回当前状态和一个dispatch函数,Reducer函数接收当前状态和action对象,并根据action类型来更新状态,dispatch函数用于触发状态更新
  • 创建初始值的状态initialState创建所有对状态的操作reducer(state,action)传给useReducer,得到读和写的接口调用写({‘type’:‘操作类型’})useReducer 接受的第一个参数是一个函数
  • 我们可以认为它就是一个 reducer , reducer 的参数就是常规 reducer 里面的 state 和 action ,返回改变后的 state , useReducer 第二个参数为 state 的初始值 返回一个数组,数组的第一项就是更新之后 state 的值 ,第二个参数是派发更新的 dispatch 函数。

13、applyMiddleware的作用是什么?

  • 1.中间件都需要通过applyMiddlewares进行注册,

  • 2.将所有的中间件组成一个数组,依次执行然后作为第二个参数传入到createStore中

  • 3.applyMiddlewares调用实例:返回store对象我们用 applyMiddleware 是为了改造 dispatch 的,所以 applyMiddleware 执行完后,dispatch 是变化了的,而 middlewareAPI 是 applyMiddleware 执行中分发到各个 middleware,所以必须用匿名函数包裹 dispatch, 这样只要 dispatch 更新了, middlewareAPI 中的 dispatch 应用也会发生变化

14、如何解决react页面不刷新?

  • 深拷贝或者改变引用类型数据的地址\

  • Key最好不用index

  • 在this.setState中使用一个回调来获取一个新的值

  • 不要直接修改store中的值

  • 在render中判断state.value值不等于空,再加载子组件

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

react-redux 是的官方 React UI 绑定层,允许您的 React 组件从 Redux 存储中读取数据,并将操作分派到存储以更新状态。@reduxjs/toolkit 是对 Redux 的二次封装,开箱即用可的一个高效的 Redux 开发工具集,使得创建store、更新store更加方便

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

  • Image-loader 加载并且压缩图片文件
  • Babel-loader 将es6转换为es5
  • Css-loader 加载css 支持模块化 压缩 文件导入
  • Style-loader 把css代码注入到js中 通过dom操作去加载
  • less-loader:开发中,我们也常常会使用less、sass、stylus预处理器编写css样式,使开发效率提高
  • raw-loader:在webpack中通过import方式导入文件内容,该loader并不是内置的,所以首先要安装,然后在 webpack.config.js 中进行配置
  • file-loader:把识别出的资源模块,移动到指定的输出⽬目录,并且返回这个资源在输出目录的地址(字符串)
  • url-loader:可以处理file-loader中的所有事情,但是遇到图片格式的模块,可以选择性的把图片转成base64格式的字符串,并打包到js中,对小体积的图片比较合适,大图片不合适

17、TypeScript中泛型的理解及其应用场景?

  • 泛型的本质是参数化类型,通俗的将就是所操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法的创建中,分别成为泛型类,泛型接口、泛型方法

  • TypeScript 中不建议使用 any 类型,不能保证类型安全,调试时缺乏完整的信息

  • TypeScript可以使用泛型来创建可重用的组件。支持当前数据类型,同时也能支持未来的数据类型。扩展灵活。可以在编译时发现你的类型错误,从而保证了类型安全。使用泛型可以创建泛型函数、泛型接口,泛型类

18、移动端1像素的解决方案?

  • 伪类+transform
  • viewport + rem
  • border-image
  • postcss-write-svg
  • 媒体查询利用设备像素比缩放,设置小数像素
  • box-shadow
  • background-image 渐变实现
  • transform: scale(0.5) 方案 - 推荐: 很灵活
  • 总结
    • 0.5px,相信浏览器肯定是会慢慢支持的,目前而言,如果能用的话,可以hack一下。对于老项目,建议采用transform+伪类。新项目可以设置viewport的scale值,这个方法兼容性好。postcss-write-svg简单易用,仅适合直线,圆角建议用transform+伪类实现

19、说说你对弹性盒子的理解?使用场景有哪些?常用属性?

  • 弹性盒子(Flexbox)是CSS3中的一种新的布局模式,它可以简化复杂的布局结构,使得页面的布局更加灵活和自适应。Flexbox布局是基于一个容器和容器内部的一些项目来实现的。
  • 使用场景:
    • 实现等高列布局
    • 实现自适应布局
    • 实现水平和垂直居中
    • 实现响应式设计
  • 常用属性:
    • flex-direction:设置容器内项目的排列方向,可以是水平方向(row)或垂直方向(column)。
    • justify-content:设置项目在水平方向上的对齐方式,可以是居中对齐(center)、两端对齐
    • align-items:设置项目在垂直方向上的对齐方式,可以是居中对齐(center)、顶部对齐(flex-start)
    • flex-wrap:设置项目是否允许换行,可以是不换行(nowrap)、按需换行(wrap)等。
    • align-content:设置多行项目在垂直方向上的对齐方式,可以是居中对齐(center)、两端对齐
    • flex-grow:设置项目的放大比例,可以是一个数字(如2)或一个比例值(如1/3)。
    • flex-shrink:设置项目的缩小比例,可以是一个数字(如2)或一个比例值(如1/3)。
    • flex-basis:设置项目的基础宽度或高度,可以是一个像素值(如200px)或一个百分比值(如50%)。

20、css预编译语言的理解?使用场景有哪些?

  • CSS预处理器是指在原有CSS语法基础上,增加了一些编程语言的特性,比如变量、函数、循环、嵌套等等。常见的CSS预处理器有Sass、Less和Stylus等。使用CSS预处理器可以让我们更加方便地编写CSS样式,提高开发效率。
  • 使用场景:
    • 变量和函数:我们可以使用变量和函数来简化CSS的编写,减少代码量,提高维护性。
    • 嵌套规则:我们可以使用嵌套规则来简化CSS的层级结构,让代码更加易读易懂。
    • Mixin:Mixin是一种可以重复使用的代码块,可以在多个地方引用,提高了代码的重用性。
    • 继承:继承可以让我们实现CSS样式的继承和重载,提高了代码的可维护性和灵活性。

21、原生Ajax的实现原理是什么,实现步骤?

  • 原生Ajax的实现原理是通过XMLHttpRequest对象向服务器发送异步请求,从而实现在不刷新整个页面的情况下更新部分内容
  • 步骤
    • 创建XMLHttpRequest对象:使用new关键字创建一个XMLHttpRequest对象,如var xhr = new XMLHttpRequest();
    • 设置请求参数:调用XMLHttpRequest对象的open方法,设置请求的方式(GET或POST)、请求的url地址、以及是否异步请求等参数。
    • 发送请求:调用XMLHttpRequest对象的send方法,发送请求到服务器。
    • 监听请求状态:调用XMLHttpRequest对象的onreadystatechange方法,在服务器响应的不同阶段触发不同的事件,如readyState和status等。
    • 处理响应数据:根据服务器的响应数据,更新页面中的相应部分内容,如innerHTML或innerText等。
  • 整个Ajax过程是异步的,即发送请求和接收响应都是在后台进行的,不会阻塞页面的主线程。因此,可以在不影响页面性能和用户体验的情况下,实现动态的数据加载和页面更新。
  • 需要注意的是,Ajax请求也需要遵守同源策略,即请求的url必须与当前页面的域名、协议、端口号完全一致。否则就会触发跨域问题,需要使用CORS、JSONP等技术进行解决

22、git冲突产生的情况?如何解决?

  • 冲突的情况可能包括以下几种
    • 同时修改同一行:当两人修改同一行代码时,Git无法自动决定使用哪个版本,因此会产生冲突。
    • 如果一个人删除了文件,而另一个人修改了同一个文件,Git也会产生冲突。
  • 解决冲突的步骤如下:
    • 使用git status命令查看哪些文件产生了冲突。
    • 打开冲突文件
    • 根据你的需求,手动编辑文件,选择保留需要的部分或者进行修改,解决冲突。
    • 解决完所有冲突后,使用git add命令将文件标记为已解决冲突。
    • 最后使用git commit命令提交解决冲突的文件。

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

  • Interface不能声明原始类型,type可以
  • Type类型使用范围广,接口类型只能用来声明对象,interface不可以定义非对象类型
  • 声明对象时,interface可以多次声明,type就不可以
  • Interface支持继承,type不可以

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

history.push({pathname:’/user’,state:{id:100}})

​ history.push({pathname:’/user’,query:{id:100}})

​ History.push(‘/路径名’)

​ History.replace(‘/路径名’)

​ History.goBack()

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

  • umijs/plugin-access可以实现路由权限和页面权限管理
  • 首先保证umi下载完成
  • 之后npm安装这个插件
  • 安装好之后开始在umi配置文件中启用
  • 在路由配置中使用 access属性指定当前路由权限

26、TypeScript中的方法重写是什么?

  • typeScript方法重写是在子类中重新定义一个与父类中同名,参数类型和参数个数相同的方法
  • 子类的方法会覆盖到父类的方法,实现对方法的重写

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

  • offsetWidth: 元素本身宽度
  • offsetHeight: 元素本身高度
  • 这两个包括padding,滚动条和border
  • clientWidth: 可视区域的宽度
  • clientHeight: 可视区域的高度
  • 这两个是内容加上padding以后的大小,不包括border值和滚动条大小
  • scrollWidth: 内容完整的宽度
  • scrollHeight: 内容完整的高度
  • 这两个表示内容的完整宽高,包括padding以及没有完全显示出来的内容,不包括滚动条

28、redux里面的数据流向是怎么回事

  • Action(动作):动作是一个简单的JavaScript对象,用于描述发生了什么操作。它必须包含一个type字段,用于标识动作类型,以及可选的payload字段,用于携带额外的数据。例如,{ type: 'INCREMENT', payload: 1 }表示执行增加操作并传递了增加的数量。
  • Dispatcher(派发器):派发器是Redux中的一个核心概念,用于将动作发送给Redux Store。你可以使用Redux提供的dispatch函数来派发动作。当你调用dispatch(action)时,派发器会将动作发送给Redux Store。
  • Redux Store(存储):Redux Store是一个包含应用程序状态的对象。它是唯一的,并且在整个应用程序中共享。当派发器将动作发送给Redux Store时,Store会通过Reducers来处理该动作。
  • Reducer(归纳器):归纳器是一个纯函数,接收当前状态和动作作为参数,并根据动作的类型来计算新的状态。它被用来处理动作并更新Redux Store中的状态。在Redux中,可能会有多个归纳器,每个归纳器负责处理不同部分的状态。最终,所有归纳器的结果将合并成一个新的状态对象,并替换旧的状态。
  • 视图组件:视图组件是React组件,它从Redux Store中获取所需的状态,并使用它来渲染UI。当状态发生变化时,视图组件会自动更新以反映新的状态。
  • 用户操作:用户操作(例如点击按钮、输入文本等)触发派发器派发相应的动作。这些动作被传递给归纳器,归纳器通过计算新的状态来响应操作

29、为什么 useState 返回的是数组而不是对象

  • 如果 useState 返回数组,那么可以顺便对数组中的变量命名,直接根据顺序解构,代码看起来也比较干净
  • 如果是对象的话返回的值必须和 useState 内部实现返回的对象同名,想使用多次就需要定义别名了

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

  • public:使用public修饰的属性或方法,在任何地方都可以访问
  • protected:使用protected修饰的属性或方法,只允许当前类,当前类的子类访问
  • private:使用private修饰的属性或方法,只允许当前类访问
  • readonly:只可以阅读,不可以修改

31、React的props.children使用map函数来遍历会收到异常显示,为什么?如何遍历

  • 原因可能是因为props.children不是一个可遍历的数组(例如当只有一个子元素时,props.children是一个单个的React元素而不是数组)
  • 如何遍历:
    • 使用React.Children.toArray()方法将props.children转换为一个可遍历的数组
    • 使用Array.map()遍历props.children并对每个子元素进行处理。

32、说说你对自定义hook的理解

自定义Hook是React中的一个特性,可以在函数组件中复用逻辑状态,它以use开头,可以在其它React函数组件中被调用

33、怎么预防用户快速连续点击,造成数据多次提交,给出三种方案

  • 按钮禁用:在用户进行一次点击后,立即将按钮禁用一段时间,防止用户再次点击。可以设置按钮的disabled属性来实现,禁用时间可以根据需要进行调整,通常可以设置为1-2秒,以防止误操作。
  • 后端处理:在服务器端进行处理,当接收到一个请求时,可以先检查最后一次请求的时间,如果时间间隔过短,则拒绝处理该请求。可以将最后一次请求的时间戳存储在session或者数据库中,进行比较判断。这种方式相对安全,但需要服务器端的支持。
  • 前端限制:在客户端进行处理,使用JavaScript进行限制。可以在前端记录最后一次点击的时间,并在下一次点击时进行比较,如果时间间隔过短,则不触发相应的事件

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

  • 使用空元素清除浮动
    • 优点:简单易懂,兼容性好
    • 缺点:添加多余内容,结构上不够语义化
  • clearflx:
    • 优点:简洁,结构上语义化
    • 缺点:需要在CSS中定义clearflx类
  • overflow清除浮动
    • 优点:简洁,不需要额外的元素
    • 缺点:有时候会触发滚动条
  • BFC(块级格式化上下文)清除浮动:
    • 优点:可靠,不需要额外的元素
    • 缺点:需要理解BFC的概念,在特定场景下使用

35、如何监听div的尺寸变化

  • ResizeObserver是浏览器原生提供的一种观察DOM元素尺寸变化的方法
  • 通过querySelector方法获取要监听的div元素,并创建了一个ResizeObserver实例
  • 然后通过调用实例的observe()方法,将div元素传入进行监听。当div的尺寸发生变化时,回调函数会被触发,我们可以在回调函数中进行相应的处理逻辑,比如更新UI或执行其他操作

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

  • history:
    • 优点:
      • 1)url更美观,没有冗余字符
      • 2)支持前进后退按钮 ,用户可以方便的导航页面
      • 3)对搜索引擎友好,可以更好的被搜索引擎索引
    • 缺点:
      • 1)需要后台服务器的支持,保证前端路由下刷新页面时能正确返回响应html内容
  • hash:
    • 优点:
      • 1)不需要后台服务器特殊配置
      • 2)兼容性好
    • 缺点:
      • 1)url不美观,后缀有#
      • 2)不支持前进后退按钮
      • 3)对搜索引擎的爬取和索引能力较弱

37、什么是面向对象编程及面向过程编程,它们的异同和优缺点?

  • 面向过程编程
    • 分析出解决问题所需要的步骤,然后用函数把这些步骤一步步实现,使用的时候依次调用即可
    • 优点:
      • 1)性能比面向对象高,因为类调用时需要实例化
      • 2)开销比较大,比较消耗资源,比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发
    • 缺点:没有面向对象易维护、易复用、易扩展
  • 面向对象编程
    • 把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描述某个事物在整个解决问题的步骤中的行为
    • 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护
    • 缺点:性能比面向过程低

38、window.onload和$(document).ready区别?

  • window.onload和$(document).ready都是在页面加载完成执行的函数
  • window.onload:页面所有元素都加载完毕,包括图片等所有元素,只能执行一次,如果需要获取DOM绑定元素的属性值时,最好使用window.onload,因为他是在所有元素加载完毕才执行
  • $(document).ready:DOM结构加载完毕之后执行,不必等到加载完毕,并且可以写多个.ready

39、现在要你完成一个Dialog组件,说说你设计的思路?它应该有什么功能?

  • 1)dialog组件需要提供hook指定渲染位置,默认渲染body下面
  • 2)然后改组件,指定外层样式,宽高等
  • 3)组件外层还需要一层mask来遮住底层内容,点击mask可以执行传进来的onCancel函数关闭dialog
  • 4)外层传入visible表示是否可见
  • 5)然后Dialog可能需要自定义头head和底部footer,默认有头部和底部,底部有一个确认按钮和取消按钮,确认按钮会执行外部传进来的onOk事件,然后取消按钮会执行外部传进来的onCancel事件。
  • 6)当组件的visible为true时候,设置body的overflow为hidden,隐藏body的滚动条,反之显示滚动条
  • 7)组件高度可能大于页面高度,组件内部需要滚动条
  • 8)只有组件的visible有变化且为ture时候,才重渲染组件内的所有内容

40、JS数组之间进行合并,写出三种合并方式

  • 1)concat()方法

    • Let arr = [1,2,3]

      Let arr1 = [4,5,6]

      arr.concat(arr1)

  • 2)扩展运算符方法

    • Let arr = [1,2,3]

      Let arr1 = [4,5,6]

      Let data = […arr,…arr1]

      Console.log(data)

  • 3)push()

    • Let arr = [1,2,3]

    • Let arr1 = [4,5,6]

      Let data = arr.push(…arr1)

      Console.log(data)

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

类型断言

  • 使用尖括号语法或者 as 关键字进行类型断言
let data: unknown = "Hello TypeScript";

let strLength: number = (data as string).length;

类型保护

  • 通过使用类型保护,你可以在代码中进行条件判断,并指定更具体的类型
function isString(value: unknown): value is string {
  return typeof value === "string";
}

let data: unknown = "Hello TypeScript";

if (isString(data)) {
  let strLength: number = data.length;
}

42、TypeScript中的枚举

  • 枚举是一种用于定义一组具名常量的方式。它们通过为一组相关的值分配有意义的名称,使得代码更加可读、可维护,并且可以提供更好的类型安全性
  • TypeScript 中枚举的特点和用法:
    • 1)枚举成员的值是常量:枚举成员的值是固定的,不能修改
    • 2)枚举成员可以有自定义的值
    • 3)通过枚举成员的名称获取对应的值,也可以通过值获取对应的枚举成员的名称
    • 4)枚举类型可以用于声明变量、函数参数、函数返回值等类型,用于限制变量的取值范围
  • 枚举的应用场景:
    • 枚举常用于表示一组相关的常量,如方向、状态、选项等。它们提供了一种更直观、可读性更高的方式来处理这些常量,并在编译时进行类型检查

43、谈谈var变量提升? 函数的?

var变量提升:

  • 当使用 var 关键字声明一个变量时,该变量的声明会被提升到其作用域的顶部,也就是在代码执行之前。
  • 这意味着可以在变量声明之前就访问该变量,但它的值会是 undefined
  • 变量的赋值操作仍然在原来的位置执行
console.log(myVar); // undefined
var myVar = 10;
console.log(myVar); // 10

函数变量提升:

  • 函数变量提升可以在函数声明之前调用该函数。
  • 函数表达式不会被提升,因此在函数表达式之前调用该函数会导致错误。
foo(); // 可以在函数声明之前调用
function foo() {
  console.log("Hello, World!");
}

bar(); // TypeError: bar is not a function
var bar = function() {
  console.log("Hello, World!");
};

44、Umi中如何实现路由权限,实现按钮权限?

  • 路由权限
    • 在 Umi 中,可以使用 render 方法或者 Authorized 组件来实现路由权限控制。
    • render 方法可以根据用户的权限动态渲染路由组件,根据用户登录状态或权限等条件进行判断。
    • Authorized 组件是 Umi 内置的权限验证组件,它可以根据用户权限配置来控制路由的访问权限。
  • 按钮权限
    • 对于按钮权限,常见的做法是在渲染按钮时根据用户权限进行判断,并决定是否显示或禁用按钮。
    • 在组件中,可以通过获取用户权限信息,然后根据权限进行条件渲染。

45、Vuex的五大核心属性,Vuex的优缺点?四个映射函数?使用场景?

  • Vuex是一个专门为 vue.is开发的状态管理模式采用集中式管理状态的方式对状态进行管理。遵循单项数据流
  • 再结合Vue的数据视图双向绑定实现页面的更新。统一页面状态管理,可以让复杂的组件交互变的简单清晰,同时在调试时也可以通过DEVtools去查看状态
  • 核心属性
  • mutations:用来修改state中的状态的,而且也只有mutations可以修改state中的状态
  • actions:对state中的数据进行一个操作。常用的是异步操作
  • state:用来存储状态的
  • getters:对state中的数据的进行处理,类似于计算属性
  • modules:对state中的数据进行分模块存储
  • 四个映射函数
    • mapState
    • mapGetters
    • mapMutations
    • mapActions
  • Vuex的优点:让组件之间的数据共享变得更加简单
  • Vuex的缺点
    • 1.数据不能持久化保存
    • 2.使用不当,极易造成全局数据混乱,污染全局空
  • Vuex的使用场景
    • Vuex比较试用于中大型项目,组件之间的层级结足够复杂数据量足够庞大。
    • 比较小型项目,组件之间的层级结构比较简单数据量不大的项目不建议使用Vuex来管理项目
    • Vuex保存token、保存用户信息等

46、Vue中生命周期的理解

  • beforeCreate (创建前) :
    • 执行时组件实例还未创建,不能访问到data、computed、watch、methods上的方法和数据。
  • created (创建后) :
    • 实例创建完成,此时渲染得节点还未挂载到 DOM,所以不能访问到 $el 属性。
    • 组件初始化完毕,各种数据可以使用,常用于异步数据获取
  • beforeMount (挂载前) :
    • 在挂载开始之前被调用,相关的render函数首次被调用,未执行渲染、更新,dom未创建
  • mounted (挂载后):
    • 初始化结束,dom已创建,可用于获取访问数据和dom元素
  • beforeUpdate (更新前):
    • 更新前,可用于获取更新前各种状态
    • 响应式数据更新了,但是对应的真实 DOM 还没有被渲染
  • updated (更新后) :
    • 更新后,所有状态已是最新
    • 避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
  • beforeDestroy (销毁前):
    • 销毁前,可用于一些定时器或订阅的取消
    • 实例销毁之前调用。这一步,实例仍然完全可用this 仍能获取到实例
  • destroyed (销毁后) :
    • 组件已销毁
    • 实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用.
  • keep-alive 独有的生命周期,分别为 activated 和 deactivated 。用 keep-alive 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数,缓存渲染后会执行 activated 钩子函数。

47、Vue2组件通信

  • 父子通信
    • 父传子:自定义属性的方式。在子组件中通过props来接收
    • 子传父: $emit()派发自定义事件的方式
    • ref:在子组件上绑定一个ref属性,通过this.Srefs来获取 p a r e n t : 在子组件中通过 t h i s . parent:在子组件中通过 this. parent:在子组件中通过this.parent 来获取父组件$root
    • v-model:在于组件总绑定一个 v-model 属性,在子组件中通过 value 属性来接收,通过$emit(“input”) 深发input事件
    • .sync 修饰符:绑定自定义属性的时候,在自定义属性的后面加上.sync修饰符,在子组件中$ emit("update:自定义属性)来派发事件
  • 兄弟通信
    • eventBus(事件搭桥)
    • 创建一个空的 vue 实例对象并挂载在vue 的 原型上
      • 在A组件中通过$emit()来派发事件,传递数据
      • 在B组件中通过$on()来接收事件和传递的数据
    • vuex
    • pinia
  • 跨组件通信
    • provide/inject
      • 在根组件中通过 provide 来注入数据
    • 在子组件中通过 inject 来接收provide注入的数据

48、Vue3的实现原理

  • 父子通信
    • defineProps父传子
      • 子组件设置props属性,定义接受父组件传递过来的参数
      • 父组件再使用子组件标签中通过字面量来传递值
    • defineEmits子传父
      • 子组件通过defineEmits触发自定义事件,emit第二个参数为传递的数值
      • 父组件绑定监听器获取到子组件传递过来的参数
  • 使用 ref
    • ref/defineExpose
      • 父组件在使用自组建的时候设置ref
      • 父组件通过设置子组件的ref来获取数据
      • 在子组件中,通过defineExpose将需要背负组件使用的变量或方法暴露出去
  • 事件总线 EventBus(场景:兄弟组件传值)
    • 创建一个Bus.js文件,用来控制数据和注册事件的
    • Bus.js里面有一个Bus类
    • eventList 是必须项,用来存放事件列表
    • constructor 里除了 eventList 外,其他都是自定义数据,公共数据就是存在这里的。
    • $on 方法用来注册事件。
    • $emit 方法可以调用 $on 里的事件。
    • $off 方法可以注销 eventList 里的事件。
  • attrs
    • attrs:包含父作用域除class和style除外的非props属性集合
  • 依赖注入:Provide 与 Inject
    • 适用场景:父(祖)组件给后代组件传值,如果传的是函数,后代组件还能通过参数的形式将数据传给父组件
    • provide / inject 类似于消息的订阅和发布,遵循 vue 当中的单项数据流,数据在哪,修改只能在哪,不能在数据传递处修改数据,容易造成状态不可预测
    • Provide接收两个参数:
      • name:定义提供 property 的 name
      • value :property 的值。
    • Inject接收两个参数:
      • name:接收 provide 提供的属性名。
        default:设置默认值,可以不写,是可选参数。
        使用时
  • 双向绑定:v-model
    • v-model 是 Vue 的一个语法糖
  • 全局状态管理:Vuex
    • 适用场景: 复杂关系的组件数据传递,Vuex作用相当于一个用来存储共享变量的容器,详细使用可前往

49、Vue中slot插槽的理解?

  • 插槽就是子组件中的提供给父组件使用的一个占位符,用 表示,它的显示是由父组件来控制的,而插槽在哪里显示就由子组件来进行控制
  • 插槽分类:
    • 匿名插槽:当slt没有指定name属性值的时候-个默认显示插槽,一个组件内只有有一个匿名插槽匿名插槽其实也有个默认的名字,default,可以省略不写
    • 具名插槽:在子组件中定义插槽时,给对应的插槽分别起个名字,方便后边插入父组件将内容根据name来填充对应的内容
      • 使用方式:
        • 1.在子组件中要先定义好插槽,并起好名字
        • 2.在父组件中的视图层中,某个标签上,给这个标签添加slot属性
    • 作用域插槽:作用域插槽可以让父组件使用插槽时的内容能够访问子组件中的数据
      • 使用方式:作用域插槽就是父组件在调用子组件的时候给子组件传了一个插槽4

50、vue中混入的理解

  • 作用:将组件中具有相同逻辑的代码封装在一起,便于维护和复用,通过混入可以将一组组件选项(例如数据、方法、生命周期钩子等)合并到一个单独的对象中,然后将该对象应用到多个组件中。这样可以避免重复编写相同的代码,提高代码的复用性和维护性
  • 全局混入
    • 全局混入是一种在所有组件中都生效的混入方式,它会影响到每个组件实例。
    • 通过全局混入,可以在所有组件中注入一些公共的逻辑、方法或者数据。这些逻辑、方法或者数据会被合并到每个组件实例中,这样可以在各个组件中使用。
  • 局部混入
    • 局部混入是一种在单个组件中使用混入的方式,它只会影响到该组件实例。
    • 通过局部混入,可以将一些特定的逻辑、方法或者数据混入到一个组件中,而不会影响其他组件
    • 这样就可以在不同的组件中使用不同的混入,以满足各个组件的需求。

51、Vuex的实现原理

  • 实现一个受保护的state:通过new 去初始化一个Vue实例,利用Vue中的data是响应式的。在data中定义一个$$state属性,值就是传递state对象,然后通过访问器get的方式去访问state。
new Vue({
    data: {
      // 在一个属性前面加上两个$, 那么这个属性是不被代理的
      $$state: options.state
    }
  })

  // 通过访问器的方式来访问state
  get state() {
    return this._vm._data.$$state
  }
  • 实现单项数据流:定义commit方法和dispatch方法,这两个方法都分别接收两个参数type(传递的类型)和payload(载荷信息)。获取到mutaions和actions这两个选项。然后分别在commit方法和dispatch方法中通过传递type属性获取到函数并执行。commit中的函数执行的时候需要传递state和payload作为参数;dispatch中的函数在执行的时候需要传递store和payload作为参数
commit(type, payload) {
  const entry = options.mutations[type]
  entry && entry(state, payload)
}
dispatch(type, payload) {
  const entry = options.actions[type]
  entry && entry(store, payload)
}
  • getters的实现:getters的实现借助了vue中的computed属性。遍历getters中的所有key,并在computed中通过key声明一个无参函数,并返回一个带有参数state的函数(这个函数就是定义在getters中的函数)。然后通过Object.defineProperty一个getters对象。只提供get方法。在get方法中返回计算属性computed的结果
const computed = {};
const getters = {}
Object.keys(options.getters).forEach(key => {
  const fn = options.getters(key);
  computed[key] = function() {
    return fn(state)
  }
})
Object.defineProperty(getters, key, {
  // 定义get方式即可
  get() {
  
  }
})
new Vue({
  computed: 
})

52、vuex 和 pinia的区别

  • Pinia(适合那些想要一个简单、轻量级的状态管理库的开发者)
    • pinia是一个轻量级的状态管理库,它专注于提供一个简单的API来管理应用程序的状态,提供了 Composition-API 风格 的 API,最重要的是,在与 TypeScript 一起使用时具有可靠的类型推断支持
    • Pinia基于Vue 3的Composition API构建的,更加灵活和可组合
    • Pinia采用了类似于React Hooks的方式来管理状态,更加直观和易于使用
    • 优点
      • 更加轻量级,因为它不需要使用 Vuex 的一些复杂的概念
      • 更加简单易用:Pinia 的 API 设计更加简单易用,因为它使用了 Vue.js 3 的新特性,如 Composition API
      • Pinia 提供了更加灵活的状态管理方式,因为它支持多个 store 实例,而 Vuex 只支持一个 store 实例
    • 缺点
      • Pinia 是一个相对较新的状态管理库,因此它可能存在一些未知的问题和限制。
      • 由于 Pinia 是一个相对较新的库,它的生态系统可能不够完善,因此可能需要花费更多的时间来解决问题。
  • Vuex(适合那些需要更多功能和灵活性的开发者)
    • vuex是一个更完整的状态管理库,它提供了更多的功能,比如模块化、插件和严格模式等等
    • Vuex则是基于Vue 2的Options API构建的,因此在某些方面可能会受到限制
    • Vuex则采用了一种基于mutations和actions的方式来管理状态,这可能需要更多的代码来实现相同的功能
    • 优点
      • Vuex 是一个比较成熟的状态管理库,它已经被广泛使用和测试。
      • Vuex 的稳定性也比 Pinia 更高,因为它已经经过了多个版本的迭代和改进。
      • Vuex 提供了一些高级功能,如中间件和插件,使得它可以处理更加复杂的状态管理需求。
    • 缺点
      • Vuex 的概念比较复杂,因此学习曲线比较陡峭。
      • Vuex 在处理一些简单的状态管理需求时可能会有些繁琐,因为它需要使用一些复杂的概念和 API

53、怎么理解回流跟重绘?什么场景下会触发?

  • 在回流是页面中修改了一个盒子的宽、高、或因内容撑大等影响盒子几何属性所引起的。

  • 回流必然会引起重绘,但重绘不一定引起回流

  • 对一些DOM元素的新增或删除、DOM元素因内容的增加减少而变化、获取一个DOM元素的offsetLeft、scrollTop等属性这些都会引起回流

  • 重绘是页面中字体样式、主题颜色、字体颜色的更改所引起的

  • 触发回流的场景:

    • (1)元素位置发生变化

      (2)元素的宽高发生变化

      (3)元素内容发生变化

      (4)浏览器的大小

      (5)当DOM节点发生添加或者删除操作。

  • 触发重绘的操作:

    • (1)字体颜色的变化

      (2)阴影部分的变化

54、ref 和 reactive的区别

  • ref
    • 只能操作浅层次数据,把基本数据类型当作自己的属性值
    • 可以创建任何数据类型的响应式对象
    • 如果传递的是基本数据类型的数据,返回的是响应式数据
    • 通过ref创建出来的数据,操作的时候要加一个.value
  • reactive
    • 深层次依赖于reactive
    • 仅仅能创建对象类型(对象、和 MapSet 这样的集合类型)的响应式对象
    • 如果传递的是基本数据类型的数据,返回的是不是响应式对象
    • reactive创建出来的数据不需要加.value

55、toRef 和 toRefs的区别

  • toRef
    • 可以将不是ref对象类型的属性转成ref对象类型。但是一次只能转一个
    • toRef的本质是引用,与原始数据有关联
  • toRefs:
    • toRefs可以将一个非响应式对象中的所有属性都转成ref对象类型
    • 用于将响应式对象转换为结果对象,它会遍历对象身上的所有属性,然后挨个调用toRef执行,其中结果对象的每个属性都是指向原始对象相应属性的ref

56、vue2中的watch 和 vue3中的watch的区别

  • 调用方式:
    • 在vue2中,使用$watch()方法来监听数据的变数;
    • 在vue3中,我们使用watch()函数来实现相同的效果
  • 监听数组和对象:
    • vue2中通过设置选项deep:true来深度监听对象或数组的变化。但是这种方式可能会导致性能问题。
    • vue3中,可以使用新的ref和reactive响应式API以更好地支持对象和数组的监听
  • 取消观察:
    • vue2中,返回一个unwatch()方法来取消观察
    • vue3中,返回一个可被调用的stop()函数来完成相同的操作
  • 首次触发:
    • vue2 中需要immediate:true的选项来触发一个监听器的回调函数
    • 在vue3中,默认情况下watch()将立即运行回调函数,如果你希望异步执行回调函数(例如在第一次渲染后),可以将选项 lazy 设置为 true,同时使用 trigger 来强制执行回调函数

57、请简单叙述Vue2和Vue3的区别和变化

  • (1)vue2是使用了选项式API 而vue3中使用的是更加方便管理代码的组合式API

  • 在vue2中选项式API有data,methods,mounted等等通过这些属性的来分别定义数据编写方法,但是由于选项式API在编写代码上比较分散不容易管理所以针对这个痛点vue3衍生出o了组合式API,组合式API通过setup()函数,没有所谓的选项,在代码编写方面更加集中,维护方面更加便利。

  • (2)vue3是比较碎片化的,减少内存空间,更加方便。

  • (3)Vue2是只允许一个根节点的存在,在vue3中允许多个根节点存在。

  • (4)在生命周期上也有很大的不同:

    • ​ boforeCreate —use setup

      ​ Created --use setup

      ​ beforeMount --onBeforeMount

      ​ Mounted ---- onMounted

      ​ beforeUpdate – onbeforeUpdate

      ​ Updated --onUpdated

      ​ beforeDestory --onbeforeDestory

      ​ Destoryed —onDestoryed

  • (5)在vue2中存在this指向,但是同时也存在this指向有问题的时候,而vue3中不存在this指向.

  • (6)Vue2的双向数据绑定原理是通过object.defineProperty()来使用getter(),setter()来进行数据劫持,结合发布-订阅者模式来实现的,并且MVVM是实现双向数据绑定的一个基本底层原理,MVVM是指Model,View,View-Model三层。

57、对Promiserace()和Promise.all的理解

Promise.race():方法

  • 它接受一个Promise对象的数组(或可迭代对象),只要数组中的某个Promise对象执行完毕(不管是fulfilled或rejected),Promise.race就会返回这个结果。
  • 也就是说,它会返回第一个完成的Promise结果。其他的Promise即使还没完成,也会被忽略。
  • 这可以用于实现竞速情况,比如有多个异步操作,只需要第一个完成的结果即可。

Promise.all():方法

  • 它也接受一个Promise对象的数组(或可迭代对象),但它会等待所有Promise都完成(不管是fulfilled或rejected)才返回结果。
  • 只有所有Promise都成功完成才会返回成功结果,只要有一个Promise失败或者被rejected,它就直接返回第一个失败的Promise结果。
  • 这适用于需要等待多个异步操作都完成的情况,比如需要同时获取多个资源的数据。
    区别:
  • Promise.race()是等到第一个Promise完成即返回,Promise.all()是等所有Promise都完成才返回。
  • Promise.race()只要有一个Promise完成即可,Promise.all()需要所有Promise都完成。
  • Promise.race()可能会返回第一个完成的Promise,Promise.all()会返回一个数组,包含所有Promise的结果。

所以Promise.race()适用于竞速场景,Promise.all()适用于需要等待所有异步操作的场景。

58、说说transition和transform的区别

  • 1、作用对象:
  • transition作用于元素的状态转换,如元素显示/隐藏,添加/删除类名等操作,可以设置状态转换的过渡效果。
  • transform作用于元素本身,可以对元素进行旋转、缩放、移动等几何转换操作。
  • 2、动画类型:
  • transition可以设置四种动画类型:transition-property、transition-duration、transition-timing-function、transition-delay。
  • transform只支持transform属性,但可以设置2D和3D转换。
  • 3、是否支持硬件加速:
  • transition支持硬件加速,性能较高。
  • transform在早期浏览器支持不好,需要软件渲染,性能较低。
  • 4、 兼容性:
  • transition兼容性好,支持IE10+。
  • transform在IE9以下不支持,需要添加前缀。
  • 5、应用场景:
  • transition更适用于状态转换动画,如显示/隐藏元素。
  • transform更适用于元素位置/形状变化动画,如旋转、缩放等。

59、requestAnimationFrame

  • requestAnimationFrame是HTML5新增的API,它用于在浏览器中调用动画重绘回调函数。
  • 主要特点和用法:
    1. 与setTimeout/setInterval不同,requestAnimationFrame的回调函数会在浏览器下一次重绘之前执行,可以确保动画状态的连贯性。
    2. 它根据设备的显示能力来决定调用频率,通常为每秒60次,以保证流畅性。这比固定频率如setInterval(16ms)更优。
    3. 回调函数会接收一个时间参数,表示从页面加载开始到调用该回调之间的毫秒数。可以用来计算动画当前状态。
    4. 可以连续调用requestAnimationFrame实现动画循环:
    5. 取消动画循环可以使用返回值的cancelAnimationFrame方法。
    6. 兼容性好,支持所有现代浏览器。对于不支持的浏览器可以使用setTimeout模拟。
    7. 适用于需要精确控制动画帧率的场景,如页面动画、游戏动画、 Canvas动画等。

60、如何使用webpack打包项目的

  • 安装webpack
npm install webpack webpack-cli --save-dev
  • 创建webpack配置文件:通常命名为webpack.config.js
  • 配置入口文件
module.exports = {
  entry: './src/index.js'
};
  • 配置输出文件
output: {
  filename: 'bundle.js',
  path: path.resolve(__dirname, 'dist')
}
  • 配置loader:处理不同类型的文件,如js、css等
  • 配置插件:HtmlWebpackPlugin自动生成html文件
  • 脚本命令
"scripts": {
  "build": "webpack"
}
  • 运行打包
npm run build
  • 开发服务器
devServer: {
  contentBase: './dist'
}
  • 多环境配置:区分开发与生产环境
  • 优化配置:代码压缩、分离、长缓存等

你可能感兴趣的:(前端,react.js)