23尾声面试记录

107 阅读17分钟

CSS

1.flex和grid有何区别?

共性:
目前布局方案中的主流,因其各自的特点,应用的场景也不尽相同

区别:
Flex布局是一维布局,Grid布局是二维布局
都有容器和项目的概念,除此之外还有 行、列、单元格、网格线的概念
从布局的操作性和复杂度上看,Grid布局要强于Flex布局,学习时间相对较多的

flex主要属性:
display: flex;
flex-direction: 设置主轴方向
flex-wrap: 控制项目在主轴上是否换行
justify-content: 控制项目在主轴上的对齐方式
align-items: 控制项目在交叉轴上的对齐方式

应用在项目上的属性:
order: 数值越小,排序越靠前
flex-grow: 控制项目分配剩余空间的比例 默认0
flex-shrink: 控制项目项目压缩的分配比例 默认1
flex-basis: 控制项目占据主轴的空间 默认auto
flex: 1 ===> 0 1 auto

grid 行、列
容器里面的水平区域称为"行"(row),垂直区域称为"列"(column)
容器中行和列的交汇点叫做单元格(cell)
水平网格线划分出行,垂直网格线划分出列
x 行有 x + 1 条水平网格线;y 列有 y + 1 条垂直网格线

grid主要属性
display: grid
grid-template-columns定义每一列的列宽
grid-template-rows定义每一列的行高
grid-row-gap grid-col-gap 设置行间距、列间距
justify-items和align-items分别控制单元格内容的水平和垂直对齐
justify-content和align-content 控制整个内容区域在容器中的水平和垂直位置
grid-auto-flow 指定项目排列顺序
grid-template-area划定区域

2.div水平垂直都居中

1.绝对定位:采用 transform: translate(-50%,-50%); 当前div的父级添加相对定位(position: relative;)父相子绝
2.绝对定位:绝对定位下top left right bottom 都设置0 父相子绝
3.flex

3.何为高度塌陷

当垂直方向给元素设置margin属性的时候
水平方向不会出现是因为水平的会叠加,垂直方向是哪一个大,就以哪个为准

1.同辈元素(给上面div设置margin-bottom 30px,下面的div设置margin-top 20px,
会以数值大的30px为主,解决:只取一边即可)
2.父子辈元素 父级想距离子级有距离 (解决:给父级设置内边距 或者 父级设置边框)

4.定位

相对定位:相对于自己的位置进行移动,不脱离文档流
绝对定位:脱离文档流,以自身为起点,一层一层往外找,找到第一个position的值为非static的父级元素做参照,否则以整个
body作为参照
固定定位:脱离文档流,一般情况下,相对于浏览器显示窗口为定位,但是当其最近的父元素有transform属性时,就会以这个
父级元素当参照

JS

开发当中数组常用的API

forEach: 遍历数组当中每个数据,无返回值
filter: 数组当中的每个元素,传入的执行函数,条件成立返回true,则返回false
map: 映射出自己想要的数据结构,比如后端的key不满足前端组件的数据格式,常用map
reduce: 接受的参数(迭代函数、初始值),数组当中每个元素,执行传入的迭代函数,迭代函数第一个参数为上一次的计算
结果,第二个参数为当前遍历的元素,最终会返回一个计算结果
splice: 对数组进行增删改查
slice: 分割数组
sort: 对数组进行排序,a-b升序,b-a降序
pop: 删除尾部元素,或者弹出最后一个元素
push: 往数组当中最佳一个或者多个元素
shift: 删除头部元素,或者弹出第一个元素
unshift: 往头部追加一个或多个元素
join:  数组转字符

开发当中字符常用API

str.slice
str.split
str.indexOf
str.concat
str.charAt
str.trim()
str.charCodeAt
String.fromCharCode
str.toUpperCase
str.toLowerCase
string.replace

forEach能不能跳出循环?如果能,怎么跳出?

详解:juejin.cn/post/726311…

正常来说,forEach是跳不出循环的,无论怎么return
原因:forEach 内侧嵌套着一个函数,即使你retrun 也无法阻断外面的for循环执行

那不正常的话,1.减少数组长度;2.throw一个error

script 标签中的 async 和 defer 属性

当浏览器遇到带有 async 属性的 script 时,请求该脚本的网络请求是异步的,不会阻塞浏览器解析 HTML,一旦网
络请求回来之后,如果此时 HTML 还没有解析完,浏览器会暂停解析,先让 JS 引擎执行代码,执行完毕后再进行解析;
多个async脚本文件执行时执行顺序没有保障

当浏览器遇到带有 defer 属性的 script 时,获取该脚本的网络请求也是异步的,不会阻塞浏览器解析 HTML,一旦网络
请求回来之后,如果此时 HTML 还没有解析完,浏览器不会暂停解析并执行 JS 代码,而是等待 HTML 解析完毕再执行 JS 代码;
多个defer脚本文件执行时的执行顺序有保障

image.png

前端页面白屏如何排查?

todo...

使用浏览器的调试工具 Performance 做一下性能分析

发版以后,还是没更新最新内容?

- 鸡肋方法:手动强刷F5

- 在服务端控制资源的缓存时间,设置http响应头中的Cache-Control字段,可以设置为no-cache或者max-age=0,
表示不进行缓存

- 在资源链接后添加版本参数的方式,如?v=1.0.0,每次上线新版本时更改版本号,由于url不同,浏览器会自动去
下载新的资源

浏览器缓存机制

在浏览器加载资源的时候,首先会根据请求头的expires和cache-control判断是否命中强缓存策略,判断是否向远程服务器请求
资源还是去本地获取缓存资源。

这当中又分为
Expires
在浏览器第一次请求资源时,服务器端的响应头会附上Expires这个响应字段,当浏览器在下一次请求这个资源时会根据上次的
expires字段是否使用缓存资源(当请求时间小于服务端返回的到期时间,直接使用缓存数据)

Cache-control
cache-control字段优先级高于上面提到的Expires,值是相对时间。
在cache-control中有常见的几个响应属性值,它们分别是(下面的属性背 max-age 和 no-cache就可以了)
max-age 3600 例如值为3600,表示(当前时间+3600秒)内不与服务器请求新的数据资源
no-cache 储存在本地缓存区,只是在于原始服务器进行新鲜度在验证之前,缓存不能将其提供给客户端使用

强缓存:简单来说就是给缓存资源设置一个过期时间,浏览器每次想要请求资源时都会判断一下这个资源是否过期,只在过期的时
候才去向服务器请求资源。

协商缓存
那么,当浏览器在某次请求资源的时候发现本地缓存的资源过期了,这时候就会向服务器发送请求,并设置协商缓存
因此,协商缓存是需要浏览器向服务器发送请求的,服务器会根据 request header 里面的一些参数
(If-None-MatchIf-Modified-Since)来判断是否命中协商缓存:如果命中,则返回 304 状态码,并且在 
response header中携带新的参数,通知浏览器从缓存中读取资源;如果没有命中,则返回 200 状态码,并且服务器
会返回新的缓存资源给浏览器

浏览器攻击

XSS攻击:(Cross Site Scripting)跨域脚本攻击。
原理:(跨域问题解决)
不需要你做任何的登录认证,它会通过合法的操作(比如在 url 中输入、在评论框中输入),向你的页面注入脚本(可能是 js
、html 代码块等)。
防范:

编码;对于用户输入进行编码
过滤;移除用户输入和事件相关的属性。(过滤 script、style、iframe等节点)
校正;使用 DOM Parse 转换,校正不配对的 DOM 标签

CSRF攻击:SRFCross-site request forgery)跨站请求伪造。
原理:

登录受信任网站 A,并在本地生成 Cookie(如果用户没有登录网站 A,那么网站 B 在诱导的时候,请求网站 A 的 api 接口时
,会提示你登录)
在不登出 A 的情况下,访问危险网站 B(其实是利用了网站 A 的漏洞)
防范:
token 验证;
隐藏令牌;把 token 隐藏在 http 请求的 head 中。
referer 验证;验证页面来源。

二者的区别:

CSRF:需要用户先登录网站 A,获取 cookie。
XSS:不需要登录。
CSRF:是利用网站 A 本身的漏洞,去请求网站 A 的 api。
XSS:是向网站 A 注入 JS 代码,然后执行 JS 里的代码,篡改网站 A 的内容。

防抖与节流

节流:一段时间内只能触发一次,如果这段时间内触发多次事件,只有第一次生效会触发回调函数,一段时间过后才能再次触发
(一定时间内只执行第一次)

防抖:在事件被触发时,延迟n秒后再触发回调函数,如果n秒内又触发了事件,则会重新开始计算时间
(一定时间内最后一次生效)
节流代码:
function throttle(fn,delay){
  let flag = true;

  return function(){
    if(flag){
      flag = false;
      setTimeout(() =>{
        flag = true
      },delay)
      return fn()
    }
  }  
}
防抖代码:
function debounce(fn,delay){
  let timer = null;
  return function(){
    if(timer){
      clearTimeout(timer)
    }
    timer = setTimeout(fn,delay)
  }
}

重流和重绘

重排:
当DOM的变化影响了元素的几何信息(元素的的位置和尺寸大小),浏览器需要重新计算元素的几何属性,将其安放在界面
中的正确位置,这个过程叫做重排。
重排也叫回流,简单的说就是重新生成布局,重新排列元素。
下面情况会发生重排(挑几个就好):
● 页面初始渲染,这是开销最大的一次重排
● 添加/删除可见的DOM元素
● 改变元素位置
● 改变元素尺寸,比如边距、填充、边框、宽度和高度等
● 改变元素内容,比如文字数量,图片大小等
● 改变元素字体大小
● 改变浏览器窗口尺寸,比如resize事件发生时
● 激活CSS伪类(例如::hover)
● 设置 style 属性的值,因为通过设置style属性改变结点样式的话,每一次设置都会触发一次reflow
● 查询某些属性或调用某些计算方法:offsetWidth、offsetHeight等,除此之外,当我们调用 getComputedStyle方法,或者
IE里的 currentStyle 时,也会触发重排,原理是一样的,都为求一个“即时性”和“准确性”。

重绘:
一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,叫做重绘。

http1,2,3区别

HTTP1.1
高延迟 — 队头阻塞(Head-Of-Line Blocking)
队头阻塞是指当顺序发送的请求序列中的一个请求因为某种原因被阻塞时,在后面排队的所有请求也一并被阻塞,会导致客户端
迟迟收不到数据。
将同一页面的资源分散到不同域名下,提升连接上限;减少请求数量;内联一些资源

无状态特性 — 阻碍交互
指协议对于连接状态没有记忆能力,下一次请求服务器并不知道它与上一条请求有何关联,换句话说就是掉登录态

明文传输 — 不安全性
传输内容没有加密,中途可能被篡改和劫持
不支持服务端推送
HTTP2
新增:
二进制分帧 - HTTP2 性能增强的核心
多路复用 - 解决串行的文件传输和连接数过多
分帧好处:
服务器单位时间接收到的请求数变多,可以提高并发数。最重要的是,为多路复用提供了底层支持。

缺陷
TCP 以及 TCP+TLS 建立连接的延时
TCP 的队头阻塞并没有彻底解决
多路复用导致服务器压力上升
多路复用容易 Timeout
HTTP3.0 基于 UDP 协议的 QUIC 协议
特点:
改进的拥塞控制、可靠传输
快速握手
集成了 TLS 1.3 加密
多路复用

Vue

vue2和vue3区别

生命周期
整体上变化不大 大部分生命周期钩子加上 on,功能上类似

API
vue2是选项式,逻辑会散乱的在不同的位置(data,computed,watch,生命周期钩子等),导致代码可读性变差,当要修改
某个逻辑,需要上下跳转文件位置
vue3选项式,可以将同一逻辑内容写一起,增强代码的可读性,还有利于代码复用

响应式原理:
Vue2 响应式原理基础是 Object.definePropertyVue3 响应式原理基础是 Proxy
Proxy可以直接监听对象而非属性
Proxy可以直接监听数组的变化
Proxy有多达13种拦截方法,不限于apply、ownKeys、deleteProperty、has等等是Object.defineProperty不具备的
Proxy返回的是一个新对象,我们可以只操作新的对象达到目的,而Object.defineProperty只能遍历对象属性直接修改
Proxy作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利
Object.defineProperty的优势如下:
兼容性好,支持IE9

虚拟DOM:虚拟DOM上增加 patchFlag 字段,当值为1时候,表示动态文本节点,diff时候,只需比对class、style;如果
值为-1,为静态节点,保存为一个变量进行静态提升,可在重新渲染的时候直接引用,无需重新创建

Diff算法优化:patchFlag 帮助 diff 时区分静态节点,以及不同类型的动态节点。一定程度地减少节点本身及其属性的比对。

多根节点:vue2不支持多个根节点,会报错;vue3支持也就是 fragment

Vue3新增异步组件(Suspense)允许程序在等异步组件加载完成前的兜底内容渲染

Vue3新增传送门(Teleport)提供 Teleport 组件可将部分 DOM 移动到 Vue app 之外的位置

vuex和pinia区别

区别:
废除mutation;
无需创建自定义的类型标注,一切可标注类型;(会自动进行类型推断)
写法风格不限制,可采用组合式写法或者是选项式。
非常轻量,只有1kb的大小
vuex当中唯一能修改状态 只能通过mutation 而pinia可以直接修改

相同:
访问时 ,都支持setup函数写法,只需要获取store实例
vuex 和 pinia订阅法则
vuex:
1.直接watch 由state 转成的 计算属性
2.使用store.watch 
watch(fn: Function, callback: Function, options?: Object): Function
 (响应式地侦听 fn 的返回值(返回我们想要监听的state),当值改变时调用回调函数。fn 接收 store 的 state 作为第一个
 参数,其 getter 作为第二个参数。最后接收一个可选的对象参数表示 Vue 的 vm.$watch 方法的参数)
3.使用store.subscribe 订阅store的mutation 
subscribe(handler: Function, options?: Object): Function
handler 会在每个 mutation 完成后调用,接收 mutation 和经过 mutation 后的状态作为参数:
停止侦听,调用此方法返回的函数即可停止侦听

pinia:
1.watch 整个state/或者单个state
2.store.$subscribe
$subscribe() 方法侦听 state 及其变化
store.$subscribe((mutation, state) => {},{ detached: true // 设置为true,表示在组件卸载,依旧保留 })

** 单个的时候,使用watch,整个state用$subscribe,在patch之后只触发一次 ** (pinia)

子组件有一个输入框,当输入框输入完毕,父级需要获取输入内容?

1.prop + emit 父级组件,给子级组件传一个状态+emit,里面触发的时候,执行这个emit方法;

2.自定义组件v-model 父级组件给子组件 传递一个 v-model="value",此时内部组件应该有 prop+emit v-model只是省略
这两步骤;

12区别在于 1是在父级组件内部 定义emit;2是把emit默认化,传入v-mode的时候,就相当于定义好了修改方法,直接emit
发送即可;

3.子组件v-model/定义修改变化方法自己维护,通过defineExpose暴露出去,父级拿到子组件实例,自己获取

4.父子级组件树下,通过provide indeject维护

React

mobx和redux区别

React Hook

@ useState
函数组件当中可以使用useState来创建一个状态和修改状态的方法,传入的值就是状态的初始值

@ useEffect
作用一:模拟了生命周期
useEffect 在挂载的时候都会执行一次 模拟了 类组件的 ComponentDidMountVue mounted
useEffect 如果没有设置第二个参数的时候 模拟了 类组件的 ComponentDidUpdateVue updated
useEffect的函数当中返回一个函数 这个函数叫 “清除函数” 清除函数会在组件卸载的时候执行 模拟了 类组件的
ComponentWillUnmountVue beforeDestroy
作用二:实现了对于状态的变化的监听
useEffect( 执行函数,[ 状态1, 状态2,...]) 可以在第二个参数当中设置要监听的状态, 当指定的状态发生变化了就会触发 执行函数。
react useEffect(()=>{ },[状态]) <=> vue watch(状态,执行函数) <=> vue wacthEffect()

@ useContext
在函数组件当中通过传入一个上下文对象 来获取上下文对象供应的数据

@ useMemo 性能优化的一点
使用useMemo 可以缓存一个函数返回的结果,并且可以执行依赖项,只有依赖项指定的状态发生变化了才会去重新更新缓存的值。
( vue 当中的 计算属性)

@ useCallback 性能优化的一点
useCallback缓存一个函数,跟useEffect和useMemo一样可以设置依赖项。某种意义上 useCallback是useMemo缓存一个函数
的语法糖。 所以这也是React项目当中性能优化的手段之一。

@ useRef
useRef 是用在函数组件当中创建ref对象的方法。 创建的ref用在html节点或者是函数组件上可以快速的方便通过ref对象
的current属性来得到对应的html节点对象或者是组件实例。

React组件如何跳过更新

通过使用shouldComponentUpdate()方法来跳过更新。shouldComponentUpdate()方法接收两个参数,即
nextProps和nextState,它们是将要更新的新属性和状态。如果shouldComponentUpdate()返回false,
则React将跳过更新,否则将继续更新

类组件和函数组件区别

区别:
类组件是面向对象编程,它有内部状态管理,并具有继承能力
函数组件属于函数式编程,不存在内部状态管理,没有继承能力
类组件是可以获取实例化的this对象,可以通过bind改变this指向,而函数组件没有this对象
类组件内部通过render方法return返回渲染jsx模版,而函数组件直接return返回

函数式组件捕获了渲染时所使用的值,类组件永远捕获最新的值

函数优缺点:
优点:
相对于类式组件,一般情况而言,代码量更少,代码更简洁,可读性更强;
更易于拆分组件和测试;
缺点:
在业务逻辑巨复杂,状态依赖关系错乱的情况下,使用useEffect、useMemo等hooks,对其依赖项数组的思考为
开发者带来了更大负担;
不具备处理错误边界等业务情况的hooks;
类组件:
优点:
功能完备,具有componentDidsCatch、getDerivedStateFromError等钩子函数处理边界错误;
缺点:
在复用性上,hoc组件等会出现诸如嵌套地狱、重名props被覆盖、难以拆分和测试等问题;

高阶组件:接受 React 组件作为输入,输出一个新的 React 组件
目前我个人是更偏向于函数式编程,因为简单、可以更
快地编写,也更加容易阅读和理解。但对于比较复杂的边界类情况,我更倾向于类组件,因为函数组件要写很多的useEffect

image.png

React Hook有哪些限制

1.React当中有个缓存的联表,每个hook都会在链表当中缓存形式存在,如果使用if判断如果条件不同就会导致对应不到对应的
链表当中的hook,会导致异常
2.React 的函数组件中调用 Hook