2023最新web前端面试题汇总

624 阅读11分钟

1.websocket原理

websocket的原理

websocket约定了一个通信的规范,通过一个握手的机制,客户端和服务器之间能建立一个类似tcp的连接,从而方便它们之间的通信

在websocket出现之前,web交互一般是基于http协议的短连接或者长连接

websocket是一种全新的协议,不属于http无状态协议,协议名为"ws"

2. websocket与http的关系

2.1 相同点:

都是基于tcp的,都是可靠性传输协议 都是应用层协议

2.2 不同点:

WebSocket是双向通信协议,模拟Socket协议,可以双向发送或接受信息 HTTP是单向的 WebSocket是需要浏览器和服务器握手进行建立连接的 而http是浏览器发起向服务器的连接,服务器预先并不知道这个连接

2.3 联系

WebSocket在建立握手时,数据是通过HTTP传输的。但是建立之后,在真正传输时候是不需要HTTP协议的

2.4 总结(总体过程)

首先,客户端发起http请求,经过3次握手后,建立起TCP连接;http请求里存放WebSocket支持的版本号等信息,如:Upgrade、Connection、WebSocket-Version等; 然后,服务器收到客户端的握手请求后,同样采用HTTP协议回馈数据; 最后,客户端收到连接成功的消息后,开始借助于TCP传输信道进行全双工通信。

3.组件封装注意事项

比如做后台管理中,很多模块经常会复用,比如侧边导航组件、项目中常用的 echarts图表的封装(比如折线图、柱状图等)

封装组件需要考虑复用性:

预留插槽slot, 多次调用如果 子组件视图结构不一样那么就要 在 子组件template预留好 插槽(单个插槽、具名插槽,作用域插槽)

考虑到数据传递,定义props 组件接收父组件传递的数据,同时需要注意单向数据流,props不能直接修改,$emit自定义事件,父组件修改

业务逻辑不要在子组件中处理,子组件在不同父组件中调用时,业务处理代码不同,切记不要直接在子组件中处理业务,应该子组件 $emit自定义事件,将数据传递给父组件,父组件处理业务。

4.cdn

1.[CDN]定义

  Content Delivery Network,即内容分发网络

  各地部署多套静态存储服务,本质上是空间换时间

  自动选择最近的节点内容,不存在再请求原始服务器

  适合存储更新很少的静态内容,文件更新慢

  举个栗子:

  你,要喝水,每次都要去水房里接水喝,你觉得很麻烦,所以你就选择了水壶去装水,这样就不用每一次都要去水房接水,就可以选择最近的水壶进行接水。

3.工作原理?

  传统访问:用户在浏览器输入域名发送请求-解析域名获取服务器IP地址-根据IP地址找到对应的服务器-服务器响应并返回数据

  使用CDN访问:用户发送请求-智能DNS的解析(根据IP判断地理位置、接入网类型、选择路由最短和负载最轻的服务器)-取得缓存服务器IP-把内容返回给用户(如果缓存中有)-向源站发起请求-将结果返回给用户-将结果存入缓存服务器

5.http相关

image.png

6.webpack 相关

image.png

7.addEventListenter 第三参数

第三个参数可以设置啥?

从官方文档看,addEventListener 方法使用如下:

**

target.addEventListener(type, listener, options); 
target.addEventListener(type, listener, useCapture);

还有一个兼容性不好的使用方法就不提了,也不太常用。

主要关注下第三个参数,可以设置为bool类型(useCapture)或者object类型(options)。

  • options包括三个布尔值选项:

    • capture: 默认值为false(即 使用事件冒泡). 是否使用事件捕获;
    • once: 默认值为false. 是否只调用一次,if true,会在调用后自动销毁listener
    • passive: if true, 意味着listener永远不远调用preventDefault方法,如果又确实调用了的话,浏览器只会console一个warning,而不会真的去执行preventDefault方法。根据规范,默认值为false. 但是chrome, Firefox等浏览器为了保证滚动时的性能,在document-level nodes(Window, Document, Document.body)上针对touchstart和touchmove事件将passive默认值改为了true, 保证了在页面滚动时不会因为自定义事件中调用了preventDefault而阻塞页面渲染。
  • useCapture: 默认值为false(即 使用事件冒泡)

前端实现单点登录

我们登录成功后,后台会返回一个token给我们,我们需要把它存到Cookie里面,方便后面使用。然后请求接口的时候,会在接口带一个token过去给服务器,做为验证当前系统是否已经登录,或者登录过期的依据。

vue请求页面接口的时候,做一个请求拦截,如果在Cookie里面有token,则直接请求数据,正常进入页面;如果没有token,说明项目没有登录,则控制路由跳转去登录页面。

所以只要在不同级别的域名下,获取保存在Cookie里面的token就可以了,只要在设置cookie的时候把domain设置为主域名,就可以在其他二级、三级域名下找到token,这就是主要流程了。

解决移动端1px像素问题

那么为什么会产生这个问题呢?主要是跟一个东西有关,DPR(devicePixelRatio) 设备像素比,它是默认缩放为100%的情况下,设备像素和CSS像素的比值。

window.devicePixelRatio=物理像素 /CSS像素复制代码

目前主流的屏幕DPR=2 (iPhone 8),或者3 (iPhone 8 Plus)。拿2倍屏来说,设备的物理像素要实现1像素,而DPR=2,所以css 像素只能是 0.5。一般设计稿是按照750来设计的,它上面的1px是以750来参照的,而我们写css样式是以设备375为参照的,所以我们应该写的0.5px就好了啊! 试过了就知道,iOS 8+系统支持,安卓系统不支持。 解决方案一:使用伪类缩放 使用伪类缩放需要主要的是:

设置全边框的时候,box-sizing要设置为border-box,否则伪元素上下左右各会多1px 需要将父元素设置为relative 注意 transform 的起点,上边距要用左上角,下边距用左下角

/* 下边框 */
.one-px-border2:after {
    content: "";
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    border-bottom: 1px solid red;
    transform: scaleY(.5);
    transform-origin: left bottom;
}
0.5px上边框

 /* 上边框 */
.one-px-border1:before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    border-top: 1px solid red;
    transform: scaleY(.5);
    transform-origin: left top;
}
0.5px全边框

.one-px-border3:after {
    content: "";
    position: absolute;
    bottom: 0;
    left: 0;
    border: 1px solid red;
    transform-origin: left bottom;
    width: 200%;
    height: 200%;
    transform: scale(.5);
    /* box-sizing要设置为border-box,否则伪元素上下左右各会多1px */
    box-sizing: border-box;
    /* 设置圆角border等 */
    border-radius: 10px;
}

解决方案三:使用图片 切图,使用图片结合border-image进行css样式设置

.border-bottom-1px {
	border-width: 0 0 1px 0;
	border-image: url(linenew.png) 0 0 2 0 stretch;
}

缺点:不够灵活,换颜色需要换图片

解决方案四:使用box-shadow模拟

.box-shadow-1px {
    width: 100px;
    height: 100px;
    box-shadow: inset 0px -1px 1px -1px red;
}

useEffect 模拟生命周期

这里就得用到一个hooks来模拟钩子函数,这个hooks就是useEffect,这个useEffect可以模拟三个钩子函数,分别是componentDidMount,componentWillUnmount和componentDidUpdate。 先贴代码为敬

React.useEffect(() => {
	console.log("这是模拟componentDidMount钩子函数")
	return () => {//return出来的函数本来就是更新前,销毁前执行的函数,现在不监听任何状态,所以只在销毁前执行
		console.log("这是模拟componentWillUnmount钩子函数")
	}
},[])//第二个参数一定是一个空数组,因为如果不写会默认监听所有状态,这样写就不会监听任何状态,只在初始化时执行一次。

通过上面这种写法就可以同时模拟componentDidMount和componentWillUnmount钩子函数。接下来说componentDidUpdate

//在此之前需要使用useRef这个hooks
const flag = React.useRef(null)
React.useEffect(() => {
	if(!flag.current){
		flag.current = true
	} else {
		console.log("更新了")
	}
})

在这里我们没有传第二个参数,也就是说他默认监听所有状态,只要有状态发生改变,他就会执行,但又有一个问题,他初始化的时候也会执行,为了解决这样的问题我们采用了useRef作为标记,初始化的时候flag.current肯定为false,所以我们将它设置成true,所以他就不会初始化执行了。

实现一个类型判断函数


function getType(obj) {
   if (obj === null) return String(obj);
   return typeof obj === 'object' 
   ? Object.prototype.toString.call(obj).replace('[object ', '').replace(']', '').toLowerCase()
   : typeof obj;
}

react 组件复用

  1. 组件复用的方式: render props模式 高阶组件(HOC) 注意:以上两种方式不是新的API,而是演化而成的一种固定模式(写法)

  2. render props模式 思路:将要复用的state和操作state的方法封装在一个组件里面(在组件中提供复用的状态逻辑代码,即状态和操作状态的方法) 这时,我们就要思考两个问题:(1)状态是组件内部私有的,那么如何在复用组件的时候拿到组件内部的state呢?----> 可以在使用组件的时候,添加一个值为函数的props,那么就可以通过函数参数来获取组件内部的state。(2)复用组件时,需要渲染的UI结构会不一样,那么怎么在复用组件时实现渲染任意的UI呢?----->将函数的返回值作为要渲染的UI 注意:复用的组件并没有渲染任何的UI结构,而且通过函数的返回值来渲染的。 以下通过一个简单的例子来演示使用render props模式实现的组件复用(一个效果是随着鼠标移动,获取鼠标的位置,另一个效果是图片随着鼠标移动而移动,这两个效果的实现都要获取x和y坐标,所以可以考虑用组件的复用来实现) Mouse组件(要复用的组件)

import React from "react";
 
class Mouse extends React.Component {
    //复用的state
    state = {
        x: 0,
        y: 0
    }
    //操作state的方法
    handleMouseMove = e => {
        this.setState({
            x: e.clientX,
            y: e.clientY
        })
    }
    //监听鼠标移动事件
    componentDidMount() {
        window.addEventListener("mousemove", this.handleMouseMove)
    }
    render(){
        return this.props.render(this.state)   //通过函数参数暴露组件内部的状态
    }
}
 
export default Mouse;

Mouse_Tree组件(复用Mouse组件的组件)

import React from "react";
import Mouse from "./Mouse.js";
import img from "./images/tree.PNG"
 
class Mouse_Cat extends React.Component {
    render(){
        return(
            <div>
                <Mouse render={ mouse => {
                    return(
                        <p>X坐标为:{mouse.x} Y坐标为:{mouse.y}</p>
                    )
                }}/>  
                <Mouse render={ mouse => {
                    return(
                        <img src={img} alt="树" style={{
                            position: 'absolute',
                            top: mouse.y,
                            left: mouse.x
                        }}/>
                    )
                }}/>    
            </div>
        )
    }
}
 
export default Mouse_Cat;

以上则是组件复用的一个简单应用

另外注意,这个虽然叫做render props模式,但是所添加的props不一定需要命名为render,可以是任意名称的props。在我们的实际应用中,更推荐使用children代替render属性,可以把以上代码做如下修改: //Mouse_Tree组件内部

 {/* <Mouse render={ mouse => {
                    return(
                        <p>X坐标为:{mouse.x} Y坐标为:{mouse.y}</p>
                    )
                }}/>   */}
     <Mouse>
                    {
                        mouse => {
                            return(
                                <p>X坐标为:{mouse.x} Y坐标为:{mouse.y}</p>
                            )
                        }
                    }
     </Mouse>
 
 
 
//Mouse组件内部
 render(){
        // return this.props.render(this.state)   //通过函数参数暴露组件内部的状态
        return this.props.children(this.state);
    }

setState到底是异步还是同步?

先给出答案: 有时表现出异步,有时表现出同步

  1. setState只在合成事件和钩子函数中是“异步”的,在原生事件和setTimeout 中都是同步的。

  2. setState 的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。

  3. setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次setStatesetState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState多个不同的值,在更新时会对其进行合并批量更新。

为什么列表循环渲染的key最好不要用index

用 index 作为 key 时,在对数据进行,逆序添加,逆序删除等破坏顺序的操作时,会产生没必要的真实 DOM更新,从而导致效率低

前端框架原理

前端框架

卡颂大佬在《React 设计原理》中,提出了一个观点:现代前端框架的实现原理都可以用以下公式进行概括:

UI = f(state)

其中:

  • state —— 当前的视图的状态
  • f —— 框架内部的运行机制
  • UI —— 宿主环境的视图

这个公式说明,框架内部运行机制根据当前状态渲染视图,这也能看出现代框架的一个重要特性:数据驱动

数据驱动部分,按 state 变化后,引起框架的 UI 变更的抽象层级,对框架进行了分类,分为应用级、组件级、元素级框架。