跳至主要內容

yyshino大约 19 分钟FrontEnd面经

兴盛优选前端一面

  1. 浏览器缓存原理

    1. 浏览器缓存是浏览器在本地磁盘对用户最近请求过的资源进行存储,当访问者再次访问同一资源时,浏览器就可以直接从本地磁盘加载资源,通过缓存的方式就可以减少与服务器的数据传输,减少服务器的负担,加快页面响应速度等。
    2. 浏览器的缓存分为两种 强缓存协商缓存
      1. 强缓存是通过ExpiresCache-Control来控制缓存在本地的有效期。
      2. 协商缓存是利用的是Last-Modified,If-Modified-SinceETag、If-None-Match这两对Header来管理的。(当浏览器对某个资源的请求没有命中强缓存,就会发一个请求到服务器,验证协商缓存是否命中,如果协商缓存命中,请求响应返回的HTTP状态为304 (Not Modified),该请求不携带实体数据,若未命中,则返回200并携带资源实体数据。协商缓存是利用的是Last-Modified,If-Modified-SinceETag、If-None-Match这两对Header来管理的。)
  2. js的基础数据类型和复杂数据类型

    1. 值类型:简单数据类型/基本数据类型,在存储时变量中存储的是值本身,因此叫做值类型 string ,number,boolean,undefined,null
    2. 引用类型:复杂数据类型,在存储时变量中存储的仅仅是地址(引用),因此叫做引用数据类型 通过 new 关键字创建的对象(系统对象、自定义对象),如 Object、Array、Date等
    3. 栈和队列的区别
      1. 队列:是限定只能在表的一端进行插入和在另一端进行删除操作的线性表;
      2. 栈:是限定只能在表的一端进行插入和删除操作的线性表。
      3. 区别:
        1. 操作规则不同。
          1. 队列的修改是依**先进先出(FIFO)**的原则进行的。队列只能在队尾插入元素,在队首删除元素
          2. 栈为后进先出(LIFO),即每次删除(出栈)的总是当前栈中最新的元素,即最后插入(进栈)的元素,而最先插入的被放在栈的底部,要到最后才能删除。
        2. 遍历效率
          1. 队列是基于地址指针进行遍历,而且可以从头部或者尾部进行遍历,但不能同时遍历,无需开辟空间(因为在遍历的过程中不影响数据结构,所以遍历速度要快
          2. 栈是只能从顶部取数据,也就是说最先进入栈底的,需要遍历整个栈才能取出来,而且在遍历数据的同时需要为数据开辟临时空间,保持数据在遍历前的一致性。
  3. 深浅拷贝

    • 常用深拷贝方式

      1. Object.assign:默认是对对象进行深拷贝的,但是我们需要注意的是,它只对最外层的进行深拷贝,也就是当对象内嵌套有对象的时候,被嵌套的对象进行的还是浅拷贝;

      2. JSONJSON.parse(JSON.stringify(obj))JSON会自己去构建新的内存来存放新对象。)

        1. JSON.parse(JSON.stringify(obj))的缺点
          1. 忽略 undefinedsymbol
          2. 不可以对Function进行拷贝,因为JSON格式字符串不支持Function,在序列化的时候会自动删除;
          3. 诸如 Map, Set, RegExp, Date, ArrayBuffer 其他内置类型在进行序列化时会丢失
          4. 不支持循环引用对象的拷贝;(循环引用的可以大概地理解为一个对象里面的某一个属性的值是它自己)
      3. MessageChannel(会忽略 undefined;不能序列化函数;不能解决循环引用的对象)

      4. 递归 (不能解决循环引用的问题)

        function cloneDeepDi(obj){
          const newObj = {};
          let keys = Object.keys(obj);
          let key = null;
          let data = null;
          for(let i = 0; i<keys.length;i++){
            key = keys[i];
            data = obj[key];
            if(data && typeof data === 'object'){
              newObj[key] = cloneDeepDi(data)
            }else{
              newObj[key] = data;
            }
          }
          return newObj
        }
        
    • 如果再当前的宏任务里继续添加微任务会怎么执行(事件循环机制由两个部分组成:宏任务和微任务)

      1. 执行当前宏任务中的同步代码,直到遇到异步操作。
      2. 将异步操作加入到相应的任务队列中。如果是宏任务,加入宏任务队列;如果是微任务,加入微任务队列。
      3. 继续执行下一个宏任务,重复步骤 1 和步骤 2。
      4. 当前宏任务执行完成后,执行微任务队列中的所有任务,直到微任务队列为空。
      5. 执行下一个宏任务,重复步骤 1 和步骤 2。
  4. 事件委托

    1. 定义:在 JavaScript 中,事件委托(delegate)也称为事件托管或事件代理,就是把目标节点的事件绑定到祖先节点上。这种简单而优雅的事件注册方式是基于事件传播过程中,逐层冒泡总能被祖先节点捕获。

    2. 优点:

      1. 节省监听数,也就是节省内存
      2. 可以监听动态元素
      3. 阻止默认动作
    3. 缺点:

      1. focus、blur 之类的事件本身没有事件冒泡机制,所以无法委托
  5. js的作用域

    1. 定义:JavaScript作用域为静态作用域static scope,也可以称为词法作用域lexical scope,其主要特征在于,函数作用域中遇到既不是参数也不是函数内部定义的局部变量时,去函数定义时上下文中查,而与之相对应的是动态作用域dynamic scope则不同,其函数作用域中遇到既不是参数也不是函数内部定义的局部变量时,到函数调用时的上下文中去查
    2. 分类
      1. 全局作用域:直接声明在顶层的变量或方法就运行在全局作用域,借用函数的[[Scopes]]属性来查看作用域,[[Scopes]]是保存函数作用域链的对象,是函数的内部属性无法直接访问但是可以打印来查看。
      2. 函数作用域:当声明一个函数后,在函数内部声明的方法或者成员的运行环境就是此函数的函数作用域
      3. 块级作用域:代码块内如果存在let或者const,代码块会对这些命令声明的变量从块的开始就形成一个封闭作用域
      4. 作用域链:当需要使用函数或者变量时,如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这样一个查找过程形成的链条就叫做作用域链。
  6. 跨域问题

    1. 跨域问题其实就是浏览器的同源策略所导致的。

    2. 「同源策略」是一个重要的安全策略,它用于限制一个[origin]的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介

    3. 同源:「protocol(协议)、domain(域名)、port(端口)三者一致。」 一致的情况下我们叫同源。

    4. 解决跨域

      1. JSONP(原理:<script>可以对跨域资源进行请求,动态创建script标签添加src且携带一个callback函数名,待请求完成后调用callback

      2. CORS请求头(在header中加入origin请求头字段。同样,在响应头中,返回服务器设置的相关CORS头部字段,Access-Control-Allow-Origin字段为允许跨域请求的源)

      3. Nginx代理(通过代理的手段,监听同一端口添加不同路径实现不同服务的跨域访问。)

    5. webpack如何实现跨域在怎么配置

        devServer: {
          port: port,
          open: true,
          proxy: { // 重写的方式,把请求代理到express服务器上
            // detail: https://cli.vuejs.org/config/#devserver-proxy
            [process.env.VUE_APP_BASE_API]: {
              target: `http://120.46.214.246:8080`,
              // target: `http://116.63.165.100:8080`, // 线上测试
              // target: `http://localhost:8080`, // 本地测试
              // secure:false, // 如果是https接口,需要配置这个参数
              changeOrigin: true, // 如果接口跨域
              pathRewrite: {
                ['^' + 'api']: ''
              }
            }
          },
          disableHostCheck: true
        }
      
  7. 如果有(1,2,3)三个接口按顺序触发,怎么按顺序获得他的结果

    1. ajax嵌套调用
    2. Promise链式调用
    3. async await
  8. 什么是虚拟DOM?

    1. 定义:Virtual DOM是一棵以JavaScript对象作为基础的树,每一个节点称为VNode,用对象属性来描述节点,实际上它是一层对真实DOM的抽象,最终可以通过渲染操作使这棵树映射到真实环境上,简单来说Virtual DOM就是一个Js对象,用以描述整个文档。
    2. 优点:修改某个数据或属性时,减少重绘和回流
    3. 缺点:使用Virtual DOM同样也是有部分缺点,代码更多,体积更大,内存占用增大,对于小量的单一的DOM修改使用虚拟DOM成本反而更高,但是整体来说,使用Virtual DOM是优点远大于缺点的。
  9. vue的双向绑定原理

    1. Vue是通过数据劫持的方式来实现数据双向数据绑定的,其中最核心的方法便是通过Object.defineProperty()来实现对属性的劫持,该方法允许精确地添加或修改对象的属性,对数据添加属性描述符中的gettersetter实现劫持。
    2. Vue3 响应式原理基础是 Proxy。
  10. vue2和vue3的区别

    1. 响应式原理:

      1. Vue2 响应式原理基础是 Object.defineProperty;(局限性:无法监听对象或数组新增、删除的元素。)
      2. Vue3 响应式原理基础是 Proxy。(缺点:Proxy会出发多次set/get响应)
    2. 生命周期:对于生命周期来说,整体上变化不大,只是大部分生命周期钩子名称上 + “on”,功能上是类似的。不过有一点需要注意,Vue3 在组合式API(Composition API,下面展开)中使用生命周期钩子时需要先引入,而 Vue2 在选项API(Options API)中可以直接调用生命周期钩子

    3. 多根的原理:Vue3 支持多个根节点,也就是 fragment。(这是一个抽象的节点,如果发现组件是多根的会自动创建一个fragment节点,把多根节点视为自己的children。)

    4. Composition API

      1. **Vue2是选项式API(Option API)**一个逻辑会散乱在文件不同位置(data、props、computed、watch、生命周期钩子等),导致代码的可读性变差。当需要修改某个逻辑时,需要上下来回跳转文件位置。
      2. **Vue3 组合式API(Composition API)**则很好地解决了这个问题,可将同一逻辑的内容写到一起,增强了代码的可读性、内聚性,其还提供了较为完美的逻辑复用性方案。
    5. 异步组件(Suspense):Vue3 提供 Suspense 组件,允许程序在等待异步组件加载完成前渲染兜底的内容,如 loading ,使用户的体验更平滑。使用它,需在模板中声明,并包括两个命名插槽:default 和 fallback

    6. TypeScript支持

  11. vue2的key算法的作用

    1. key的作用主要是为了高效的更新虚拟DOM,(其原理是vue在patch的过程中可以通过key精准判断两个节点是否是同一个,从而避免频繁更新不同元素,使得整个patch过程更加高效,减少dom操作,提高性能。)
    2. vue中在使用相同标签名的元素的进行过渡切换的时候,也会使用到key,其目的是为了让vue可以对他们进行区分,尤其是在v-for的时候,否则vue只会替换其内部的属性而不会触发过渡效果。
  12. 组件的通信方式

    1. Vue2

      1. 父子组件的通信:props $emit (单向数据流、父=>子通过props传递值即可、子=>父通过父组件传递的事件的触发将更改值的行为传递到父组件去执行)
      2. v-model
      3. provide inject(依赖注入)适用于父子组件以及跨级组件的通信(通过provider来提供属性,然后在子组件中通过inject来注入变量)
      4. $attrs $listeners适用于直接的父子组件通信并通过props传递可以实现跨级组件的通信
      5. $children $parent适用于父子组件以及跨级组件的通信
      6. EventBus可以适用于任何情况的组件通信,在项目规模不大的情况下,完全可以使用中央事件总线EventBus 的方式,EventBus可以比较完美地解决包括父子组件、兄弟组件、隔代组件之间通信,实际上就是一个观察者模式
      7. Vuex同样可以适用于任何情况的组件通信,Vuex是一个专为Vue.js应用程序开发的状态管理模式,其采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
    2. Vue3

  13. vue的修饰符

    1. 常用的修饰符一般分为 表单修饰符(v-model修饰符) 和事件修饰符。

    2. 表单修饰符

      1. .lazy:lazy这个修饰符会在光标离开input框才会更新数据:
      2. .trim:输入框过滤首尾的空格
      3. .number:先输入数字就会限制输入只能是数字,先字符串就相当于没有加number,注意,不是输入框不能输入字符串,是这个数据是数字:
    3. 事件修饰符

      1. .stop:阻止事件冒泡,相当于调用了event.stopPropagation()方法:
      2. .prevent:阻止默认行为,相当于调用了event.preventDefault()方法,比如表单的提交、a标签的跳转就是默认事件:
      3. .self:只有元素本身触发时才触发方法,就是只有点击元素本身才会触发。比如一个div里面有个按钮,div和按钮都有事件,我们点击按钮,div绑定的方法也会触发,如果div的click加上self,只有点击到div的时候才会触发,变相的算是阻止冒泡:
      4. .once:事件只能用一次,无论点击几次,执行一次之后都不会再执行
      5. .captruecapture的作用添加事件侦听器时使用事件捕获模式。即是给元素添加一个监听器,当元素发生冒泡时,先触发带有该修饰符的元素。若有多个该修饰符,则由外而内触发。
      6. .sync:对prop进行双向绑定
      7. .keyCode:监听按键的指令,具体可以查看vue的键码对应表
  14. axios的封装

    // 1.创建axios实例:传入一些基础配置如 基础路径(baseURL)、超时时间(timeout)、默认请求头(header)
    const axiosInstance: AxiosInstance = axios.create({
      baseURL: import.meta.env.VITE_APP_BASE_API, // Vite环境变量
      //  baseURL: process.env.VUE_APP_BASE_API, // Webpack环境变量
      timeout: 30 * 1000 // 通用设置 30 秒
    });
    
    // 2.添加请求拦截器-发送请求之前的工作(一般添加token/token鉴权)
    axiosInstance.interceptors.request.use(
      (config: InternalAxiosRequestConfig) => {
        // 在发送请求之前做些什么
        return config;
      }, (error: any) => {
        // 处理请求错误
        return Promise.reject(error);
      },
    );
    
    // 3.添加响应拦截器-请求后的工作(处理HTTP常见错误,进行全局提示ElMessage、传递数据)
    axiosInstance.interceptors.response.use(
      (response: AxiosResponse) => {
        const res = response.data;
        // useUserStore
        // const userStore = useUserStore(); // store
        // const router = useRouter(); // router
            if (res.code !== "00000") {
              // 未登录
              if (res.code === "D0112") {
                console.log("未登录");
                // userStore.logout()
                // console.log('Vue=>',Vue.prototype.$bus);
        
                if (getToken()) {
                  console.log('noLogin');
                  // 未登录
                  $bus.emit('noLogin', 'noLogin')
                  removeToken()
                }
        
                // if (window.location.href.split('#')[1] !== '/login') {
                if (window.location.href.split('#')[1] !== '/intro') {
                  // console.log(1);
                  console.log(router);
                  // router.push({ path: '/login' });
                  router.push({ path: "/intro" })
                  // window.location.href = `/login`
                }
              } else if (res.code === 'A0410') {
                ElMessage({
                  message: res.message || 'Error',
                  type: 'error',
                  duration: 2 * 1000
                })
                console.error('请求必填参数为空!!!');
                return Promise.reject(new Error(res.message || 'Error'))
                // console.log(res);
              } else if (res.code === 'A0301') {
                ElMessage({
                  message: res.message || 'Error',
                  type: 'error',
                  duration: 2 * 1000
                })
                console.log("访问未授权!!");
                return Promise.reject(new Error(res.message || 'Error'))
                // console.log(res);
              } else {
                // 暂时关闭全局 error
                ElMessage({
                  message: res.message || 'Error',
                  type: 'error',
                  duration: 2 * 1000
                })
        
                return Promise.reject(new Error(res.message || 'Error'))
              }
            } else {
        
              // 对响应数据做点什么
              return res;
            }
          }, (error: any) => {
            // 处理响应错误
            return Promise.reject(error);
          },
        );
    
    export default axiosInstance; // 暴露axios对象
    
         1. 创建axios实例:传入一些基础配置如 基础路径(baseURL)、超时时间(timeout)、默认请求头(header)
         2. 请求拦截器:
    

兴盛优选二面面经

11:00到11:31。 1、自我介绍

2、TCP为什么可靠?

  • 传送的数据不重复、不丢失、不出错且按序到达
  • TCP对每一个字节都进行编号,保证了不重复和按序达到
  • 重传机制保证了不丢失
  • 差错检测机制保证了不出错
  • 流量控制:当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。
  • 拥塞控制:当网络拥塞时,减少数据的发送。

3、TCP是单工还是双工的?

  • 双工

4、为什么说TCP是双工的?

  • 可以从三次握手看出,客户端和服务器都可以向对方进行数据传输

  • 可以从TCP选择重传中看出来,在每一时刻是同时存在收发端发送的帧和ACK信号的。

  • 但是从三次握手看起来又像是半双工,但是TCP三次握手不能代表平时的数据传输,三次握手的目的是为了确立连接建立,而在实际TCP传输过程中大多情况是收发端同时发送数据的。

5、为什么要四次挥手?

  • 建立连接时的三次握手中的SYN和ACK一起发送,断开连接时FIN和ACK是分开发送导致多了一次

6、HTTP协议状态码301和302有什么区别?

  • 字面上的区别就是 301 是永久重定向,而 302 是临时重定向。 301 比较常用的场景是使用域名跳转。302 用来做临时跳转 比如未登陆的用户访问用户 中心重定向到登录页面。

7、HTTP1.0和HTTP1.1的区别

  • 缓存处理,在HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准,HTTP1.1则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。
  • 带宽优化及网络连接的使用,HTTP1.0中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1则在请求头引入了range头域,它允许只请求资源的某个部分,即返回码是206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。
  • 错误通知的管理,在HTTP1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。
  • Host头处理,在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随着虚拟主机open in new window技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。HTTP1.1的请求消息和响应消息都应支持Host头域,且请求消息中如果没有Host头域会报告一个错误(400 Bad Request)。
  • 长连接,HTTP 1.1支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟,在HTTP1.1中默认开启Connection: keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点。

8、什么时候用多进程什么时候用多线程?

  • 多线程适用于在单个进程中并行执行任务,并且需要共享大量数据的情况,而多进程适用于在不同进程中并行执行任务的情况。
  • 多线程:在同一个进程中创建多个线程,多个线程共享进程的内存空间,因此它们可以很容易地共享数据,而且通信成本更低。因此,如果您的应用程序需要在单个进程内部并行执行不同任务,并且需要共享大量数据,则多线程是一个很好的选择。
  • 多进程:当您的应用程序需要在不同的进程之间并行执行任务时,您可以使用多进程。不同进程之间的通信通常比同一进程中的通信慢,但它们拥有完全独立的内存空间,因此不存在内存访问冲突的问题。

9、Redis为什么快?

10、讲讲多路IO复用技术

11、Redis内存淘汰策略

12、讲讲LRU和LFU的底层实现原理

13、如何通过LRU算法来设计淘汰策略

14、MySQL的事务隔离级别

15、幻读与脏读的区别

  • 脏读:一个事务读取了另一个事务改写但还未提交的数据。
  • 幻读:一个事务读取了另一个事务插入的新纪录

16、有一张表,有四个字段,需要根据年龄区间来进行查找,如何设计索引。(还有另一个问题,我忘记了,大概是通过年龄区间+另一个字段一起,然后设计索引)

参考

https://juejin.cn/post/6988103754817994788