• 前端技术面试核心问题(持续更新)


    参考资料 前端面试宝典
    在这里插入图片描述

    首屏渲染优化

    代码层面

    • 组件库的按需引入
    • 路由的异步加载
    • 使用服务端渲染SSR,直接由服务端返回渲染好的HTML
    • 事件防抖节流,减少事件处理次数
    • 减少DOM层级
    • 减少DOM渲染次数(批量渲染documengFragment,差量渲染Vue/React)

    通信层面

    • 缓存网路数据在localStorage里或vuex/redux里,减少请求次数
    • 小图片直接使用base64以减少网络请求次数
    • 使用HTTP协议的强缓,服务端给更新频率低的数据一个较长的缓存时间(响应头Cache-Control:maxAge(3600247))
    • 从CDN或静态资源服务器去获取图片、js、css、图标字体等静态资源

    打包层面

    • 对图片进行物理压缩,如在线压缩工具TinyPNG
    • 打包时对HTML,CSS,JS进行压缩处理
    • 开启Gzip压缩;
    • 合理分包,将第三方包、轮子包、业务代码包分开,这样在前端版本升级时,往往还需要更新业务代码包,而第三方包和轮子包由于内容未变导致hash未变,因此直接被服务端判断为304,从而直接使用本地缓存;
    • 打包时开启Tree-Shaking,摇掉无用代码,减少包体积;
    • 从HTML中抽离CSS文件,以便单独下载与缓存;

    小程序的微信授权流程(用微信账号登录自家服务器)

    • 在小程序端wx.login()得到登录码code
    • 小程序请求自家的登录接口,携带登录码code
    • 自家服务器请求微信服务器,使用appid+appSecret+code换回session_key与openid
    • 自家服务器将session_key与openid重新换算为自家的登录信息(如token)
    • 小程序在后续请求自家服务器的过程中都携带该token,自家服务器就知道小程序端已经登录过了
      参考链接
      在这里插入图片描述

    Set与Map

    • ES6的新增数据结构
    • Set用于基础数据的去重,一些核心API如下:
    // 对数组去重
    cosnt mySet = new Set([3,1,4,1,5,9,2,6,5,3,5,8])
    
    // 转化回数组
    const norepeatArr = [...mySet]
    
    mySet.add(value) //添加元素
    mySet.has(value) //判断某元素是否存在
    mySet.size //元素数量
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • Map用于键值对的便捷操作与查询,底层就是Object

      const contacts = new Map()

      //设置或覆盖key-value
      contacts.set(‘Jessie’, {phone: “213-555-1234”, address: “123 N 1st Ave”})
      contacts.has(‘Jessie’) //查询key是否存在
      contacts.get(‘Hilary’) //查询key对应的value
      contacts.delete(‘Raymond’) //删除key-value
      contracts.size //查询键值对的数量

        Promise与asycn,await的区别

        • await是promise.then的语法糖
        //ajaxApi(url,parmas)的返回值是promise对象
        //阻塞等待promise对象resolve数据出来
        const data = await ajaxApi(url,parmas)
        
        • 1
        • 2
        • 3
        • await关键字所在的函数必须声明为异步函数
        async function fn(){//内含await}
        async ()=>{//内含await}
        const fn = async ()=>{//内含await}
        
        • 1
        • 2
        • 3
        • promise.catch由await所在的代码块进行try-catch
        async fn(){
            try{
                // ajaxApi(url,parmas)的返回值是promise对象
                // 如果promise毁约(reject),则错误信息由try-catch的catch获得
                const data = await ajaxApi(url,parmas)
            }catch(err){
                //此处的err即promise所reject出的错误信息
                hanleErr(err)
            }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10

        HOC与RenderProps

        答题要点

        • 两者都是React复用逻辑的主要方式,另外个主要方式是大名鼎鼎的天王【自定义Hook】
        • HOC的逻辑是子组件进去,父组件出来,父组件在直接渲染子组件之余,为子组件注入了新的props、新的生命周期、新的DOM事件监听、新的副作用…这一大堆东西即你想复用的数据与逻辑
        • RenderProps的逻辑是组件进去,怀了孕(Baby)的组件出来,Baby组件自带响应式数据与变化逻辑,即你想复用的数据与逻辑
        • 案例:以【实时显式鼠标移动位置】这一复用逻辑为例:

        HOC案例

        /* 子组件App进去 增强型的父组件XApp出来 */
        const withMouse = (App) => {
          class XApp extends Component {
            constructor(props) {
              super(props);
        
              // 为子组件注入响应式数据x,y
              this.state = {
                x: 0,
                y: 0,
              };
            }
        
            /* 鼠标移动动态修改状态x,y的值 */
            onMouseMove = (e) => {
              this.setState(() => ({
                x: e.pageX,
                y: e.pageY,
              }));
            };
        
            render() {
              // 在此以外做的一切事情都是增强
              // return 
        
              return (
                // 给子组件的顶层DOM添加鼠标移动监听器
                <div onMouseMove={this.onMouseMove}>
        
                  {/* 为子组件注入响应式数据x,y */}
                  {/*  */}
                  <App {...this.props} {...this.state} />
                </div>
              );
            }
          }
        
          // 普通的App进来 增强型App出去
          // 增强1:在顶层DOM身上绑定了鼠标移动事件监听器
          // 增强2:注入了响应式数据x,y即鼠标实时移动到的位置
          return XApp;
        };
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        • 26
        • 27
        • 28
        • 29
        • 30
        • 31
        • 32
        • 33
        • 34
        • 35
        • 36
        • 37
        • 38
        • 39
        • 40
        • 41
        • 42

        RenderProps案例

        import React, { Component } from 'react'
        
        /* 对鼠标移动的state与事件的封装提取 */
        class Mouse extends Component {
            constructor(props) {
                super(props);
        
                // 老鼠baby自带响应式数据
                this.state = {
                    x: 0,
                    y: 0
                }
            }
        
            /* 在DOM事件中改变响应式数据 */
            onMouseMove = (e) => {
                this.setState({
                    x: e.pageX,
                    y: e.pageY
                })
            }
        
            render() {
                return (
                    // 老鼠baby自带DOM事件监听——用以动态改变响应式数据
                    <div onMouseMove={this.onMouseMove}>
                        {/* 拿出我妈给我的renderFn(x,y)执行得到DOM */}
                        {this.props.renderFn(this.state.x, this.state.y)}
                    </div>
                )
            }
        }
        
        class App extends Component {
            render() {
                return (
                    <div>
                        {this.props.name}
                        <br />
        
                        {/* 将一只老鼠子组件放在此处 即相当于把【实时显式鼠标移动位置】放在这 */}
                        {/* 
        { height: "1000px", backgroundColor: "#eee" }}>鼠标实时位置:{x},{y}
        */
        } <Mouse // 告诉我的baby拿到响应式数据x,y后如何渲染JSX renderFn={ (x, y) => <div style={{ height: "1000px", backgroundColor: "#eee" }}>鼠标实时位置:{x},{y}</div> } /> </div> ) } } export default App
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        • 26
        • 27
        • 28
        • 29
        • 30
        • 31
        • 32
        • 33
        • 34
        • 35
        • 36
        • 37
        • 38
        • 39
        • 40
        • 41
        • 42
        • 43
        • 44
        • 45
        • 46
        • 47
        • 48
        • 49
        • 50
        • 51
        • 52
        • 53
        • 54
        • 55
        • 56
        • 57

        前端权限控制

        • 登录成功后,服务端返回该用户的角色/权限;
        • 可以将该角色/权限等级数据存储在全局(例如全局状态管理);
        • 路由层面A计划-动态生成路由表:根据该用户的等级动态添加它有权访问的路由(一旦用户访问自己无权访问的路由时命中404);
        • 路由层面B计划-路由守卫:使用路由守卫,当用户访问越权的路由时一脚踹到登录页;
        • 界面层面A计划-条件渲染:根据用户的权限条件渲染它能访问的Link,组件、具体元素等;
        • 界面层面B计划-封装:
        • Vue中可以自定义指令如v-auth="3"当用户的等级不足3时将该按钮disable掉或隐藏掉;
        • React中可以使用HOC实现例如WithCustomAuth(MyButton,3)在返回JSX的时候使用条件渲染
        function WithCustomAuth(Com,level){
            function Parent(props){
                reurn ({
                    authFromRedux >= level 
                    ? <Com {...props} /> 
                    : <a href="/login">登录</a>
                })
            }
            return Parent
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10

        Websocket与HTTP有啥区别

        • Websocket基于TCP协议,工作在传输层,长连接双工通信;
        • HTTP是应用层协议,单向短链接,请求-响应-断开;
        • Websocket已为大多数浏览器所支持,场景如:客服、秒杀、实盘、直播…
          案例
          服务端
        var app = require('express')();
        var server = require('http').Server(app);
        var WebSocket = require('ws');
        
        // 服务端监socket监听在8080 等待客户端连接
        var wss = new WebSocket.Server({ port: 8080 });
        
        // 准备客户端存储对象
        const clients = {}
        
        /* 有客户端请求接入 入参ws即为接入的客户端socket对象 */
        wss.on('connection', function connection(ws) {
            console.log('server: receive connection from client');
        
            // 将该客户端存下来 时间戳:socket
            clients[Date.now()] = ws
            console.log("clients",Object.keys(clients));
            
            // 接收到该客户端发来的消息
            ws.on('message', function incoming(message) {
                console.log('message',message);
                ws.send('got it:'+message);
        
                // 转发给它想要发送的其它客户端
                const [to,msg] = message.toString().split(":")
                clients[to*1].send(message.toString())
            });
        
            // 该客户端主动断开连接
            ws.on("close",function(code,reason){
                console.log("客户端已断开连接");
            })
        
            // 与该客户端的连接发送错误
            ws.on("error",function(err){
                //处理错误
            })
        
            // 立刻给刚刚接入的客户端发送消息
            ws.send('welcome!');
        });
        
        /* 在http的3000端口返回页面给客户端 */
        app.get('/', function (req, res) {
          res.sendfile(__dirname + '/index.html');
        });
        app.listen(3000);
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        • 26
        • 27
        • 28
        • 29
        • 30
        • 31
        • 32
        • 33
        • 34
        • 35
        • 36
        • 37
        • 38
        • 39
        • 40
        • 41
        • 42
        • 43
        • 44
        • 45
        • 46
        • 47

        客户端

        <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <meta http-equiv="X-UA-Compatible" content="IE=edge">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>Document</title>
        </head>
        <body>
            <input type="text" placeholder="please enter..." id="ipMsg">
            <button id="btn">发送消息</button>
            <button id="btnClose">断开连接</button>
        
            <script>
                // 发起远程连接请求websocket长连接
                var ws = new WebSocket('ws://localhost:8080');
        
                // 连接成功时打印一下
                ws.onopen = function () {
                  console.log('ws onopen');
                //   ws.send('from client: hello');
                };
        
                // 收到服务端的消息时打印一下
                ws.onmessage = function (e) {
                  console.log('ws onmessage');
                  console.log('from server: ' + e.data);
                };
        
                // 点击按钮给服务端发送消息
                btn.onclick = function(e){
                    ws.send(ipMsg.value);
                }
        
                // 点击按钮断开与服务端的连接
                btnClose.onclick = function(e){
                    ws.close()
                    console.log("连接已断开");
                }
                
              </script>
        </body>
        </html>
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        • 26
        • 27
        • 28
        • 29
        • 30
        • 31
        • 32
        • 33
        • 34
        • 35
        • 36
        • 37
        • 38
        • 39
        • 40
        • 41
        • 42
        • 43

        react中怎么在父组件里使用子组件的函数

        • 父组件给子组件一个callback,子组件在使用该callback时入参一个自己的函数,让父组件去调用
          PS:纯脑洞问题 没有普适性
        function Parent(props){
            const callMe = (sonFn)=>sonFn()
            return (
                <>
                    <Son callback={callMe}>
                </>
            )
        }
        
        function Son({callback}){
            const sonFn = (sonFn)=>alert('老爸调用到我了')
            return (
                <>
                    <button onClick={callback(sonFn)}>让父组件调用我的函数</button>
                </>
            )
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17

        Map与Object的区别

        • 本质上都是key-value管理数据
        • Map是ES6新增的数据结构,主要就是在Object的基础上封装除了几个便捷API,以做到和其它语言接轨(很多其它语言中都有Map这种数据结构)
        • 这些便捷API包括map.set(key,value),let value = map.get(key),map.delete(key),map.size
        • 一个简单的MyMap封装
        
        class MyMap {
            constructor(){
                this.dataObj = {}
                this.size = 0
            }
        
            set(key,value){
                if(!this.dataObj.hasOwnProperty(key)){
                    this.size ++
                }
                this.dataObj[key] = value
            }
        
            get(key){
                return this.dataObj[key]
            }
        
            delete(key){
                delete this.dataObj[key]
                this.size --
            }
        }
        
        const mm = new MyMap()
        mm.set("name","OOP小菜鸡")
        console.log(mm.get("name"));
        console.log(mm);
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        • 26
        • 27
        • 28

        按需加载 VS 异步加载 VS TreeShakig VS Webpack分包

        • 按需加载主要指代码层面不引入不需要的东西,典型场景是组件库中只引入自己需要的组件
        import {Button} from "antd"
        
        • 1
        • 异步组件,什么时候访问,就什么时候加载,否则不加载
        //同步引入 编译时引入 无论用户访问不访问都引入
        //import Home from "@/views/Home.vue"
        
        //异步加载 运行时加载 用户访问该页面时才临时去加载
        // 注释部分代表Webpack打包时将该组件打包为about.js这样一个异步chunk
        const About = ()=>import(/* webpackChunkName: "about" */,"@/views/About.vue")
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • TreeShaking:Webpack打包时自动将引而未用的内容排除在外,启动方式mode:production
        • Wepack分包的核心目的是为了最大化地使用包的缓存,例如将常用的第三方包打在vendor.hash.js中,自己的轮子代码打在common.hash.js中,将自身的业务逻辑打在main.hash.js中,当项目需要升级时,如果只改动了业务代码而没有触及轮子与第三方包,则vendor.hash.js与common.hash.js这两个chunk的hash是不变的,HTTP的缓存机制自动生效,用户端只需要更新main.hash.js这一个包即可;

        递归组件

        • 自己作为自己的子组件,例如:无穷子菜单,博文的无穷回复;
        • 核心逻辑,以无穷回复为例:
        • 每篇博文有它的回复数据,假设叫replies
        • 组件CommentItem正常渲染出每个回复item的用户名、回复时间
        • 每个回复的item依然有它的子回复replies
        • 将replies中的每一项,继续映射为一个新的CommentItem
        function CommentItem({item}){
            return <>
                <h3>item.username</h3>
                <span>item.date</span>
                
                {/*渲染item的子回复replies*/}
                {
                    item.replies.map(
                        it=><CommentItem item={it}/>
                    )
                }
            </>
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13

        [参考链接] (https://juejin.cn/post/7087904975400992798)

        JWT-token的原理

        # jwt鉴权机制

        md5消息摘要算法保护用户的信息

        md5消息摘要算法的基本原理

        • md5是消息摘要(digest)算法;
        • 无论【信息】多长或多短,一律生成一个不可逆的32位字符串;
        • 只要【信息】变化一点点,生成的消息摘要都会大相径庭;
        • 消息摘要算法用于校验数据的完整性(即原始数据是否被人篡改过);
        • 其它知名的消息摘要算法还有SHA256,原理相同,生成的消息摘要更长;

        以保护用户的密码为例

        • 用户注册时填入的密码以消息摘要的形式存储在数据库中;
        • 用户登录时填写的登录密码,在服务端生成消息摘要,再与数据库中的密码消息摘要做比对;
        • 服务端在校验密码时生成消息摘要的过程,只要程序员不偷偷记录和转移数据,则理论上用户信息是“安全”的;

        “这不需要测试,肯定是好的,不用担心”

        在这里插入图片描述

      • 相关阅读:
        【LeetCode热题100】--34.在排序数组中查找元素的第一个和最后一个位置
        网络安全专业术语中英对照指南
        docker篇---docker-compose和dockerfile构建镜像
        Go --- go-elasticsearch介绍及简单使用
        Java基础——递归
        智能汽车安全:保护车辆远程控制和数据隐私
        NumPy 数组创建方法与索引访问详解
        docker 安装 Centos7
        Neo4j数据库删除数据
        ZYNQ 程序编译
      • 原文地址:https://blog.csdn.net/u010986776/article/details/126516458