b站二面

381 阅读32分钟

唠一唠

接上回,在经历一面的洗礼之后,我在这两天准备的时间里面,还是和以前一样,从早上八点一直待在自习室,晚上九点半被保安赶出去。很快b站二面就来了...

b239d4bbfb49ab4fa46aa20bcd00fb7.jpg

b站二面

老规矩,一上来还是自我介绍。自我介绍完了开始八股拷打,前面有几个问题不记得问的什么了。这里直接从第四个开始聊。大概前前后后问了快40个问题。

1. 聊一聊前端缓存

前端缓存主要包括两种,Http缓存和浏览器缓存。

1. Http缓存

HTTP 缓存是基于HTTP协议的缓存机制,发生在网络请求的过程中。主要包括以下两种:

  • 强缓存

    • 通过Cache-ControlExpires头部字段控制。当浏览器再次请求相同的资源时,根据这些头部信息判断是否可以直接使用本地缓存,无需向服务器发送请求。若缓存有效,则直接从缓存中获取资源并显示,不会发生网络通信。
  • 协商缓存

    • 当强缓存失效时,浏览器会发送一个条件GET请求到服务器,询问缓存的资源是否仍然有效。这通常涉及到ETagLast-Modified头部字段,服务器收到请求后,如果资源没有改变,则返回304 Not Modified状态码,浏览器继续使用本地缓存;否则,服务器返回新的资源和200状态码。

2. 浏览器缓存

浏览器本身也提供了多种缓存形式,主要为以下几种:

  • LocalStorage 和 SessionStorage

    • 这两种Web Storage API主要用于存储持久化的JavaScript对象,它们不是HTTP缓存的一部分,但常用于存储用户界面状态、临时数据等非HTTP请求的内容。
    • Localstorage 存储的数据具有持久化特性,重启浏览器或关闭页面后仍能保留数据。SessionStorage 中存储的数据只存在于当前会话(即浏览器标签页),关闭该标签页后数据就会消失
  • Cookies

    • 虽然主要用于识别用户会话、保持登录状态等,但也可以存储小量数据并在每次HTTP请求时自动携带到服务器。
  • 内存缓存(Memory Cache)

    • 浏览器可能还会在内存中缓存一些资源,例如图片、脚本等,以加快页面渲染速度。
  • Service Worker 缓存

    • 一种高级的客户端缓存机制,它可以拦截网络请求并提供离线支持,允许开发者精细控制哪些资源应该被缓存及如何处理缓存。

由于之前聊到强缓存没有讲的很详细,面试官又追问到

2. 第一次没有缓存的时候,客户端达到服务端,服务会返回什么字段,它们是如何通讯的

当客户端首次请求资源且没有缓存时,服务端在响应时会包含以下与缓存有关的HTTP头部字段:

  1. Cache-Control

    • 可能包含的指令如 max-age=<seconds>,指示资源在多少秒内可作为新鲜缓存使用,无需再次验证。
    • 或者 public 表示响应可以被任何缓存存储和重用,或者 private 表明响应只能被单个用户缓存(如浏览器)。
    • 其他指令还有 no-cache(要求必须验证缓存有效性)、no-store(禁止缓存此响应)等。
  2. Expires

    • 如果服务端使用较老的HTTP/1.0协议,可能会包含 Expires 字段,指定了一个具体的日期/时间戳,过了这个时间点后,缓存认为已过期。
  3. ETag

    • ETag是资源的唯一标识符,虽然不是缓存策略的一部分,但在后续请求时可以用来进行协商缓存验证。
  4. Last-Modified

    • 提供资源最后修改的时间,用于协商缓存验证。

在首次请求过程中,客户端与服务端的通信是一次完整的HTTP请求-响应周期:

  • 客户端发起HTTP请求,请求中通常包含URL、HTTP方法(GET/POST等)、可能的认证信息和其他请求头。
  • 服务端接收到请求后,处理请求并找到要返回的资源。
  • 在构建响应时,服务端会附加上述的缓存控制头部信息。
  • 服务端发送HTTP响应给客户端,响应中包含了资源实体内容以及相关的头部信息,包括缓存控制字段。
  • 客户端接收到响应后,根据这些头部信息决定是否存储资源到缓存,并展示资源内容给用户。

注意:在首次请求时,由于没有缓存,无论服务端返回何种缓存控制字段,客户端都不会因为缓存而影响本次请求的响应。不过,这些字段的作用在于指导客户端后续如何处理对该资源的请求。

3. 介绍一下跨域的背景

跨域是由于同源策略的影响,而之所以要有同源策略,其核心目的是防止恶意网站通过脚本读取和修改另一个源上的数据,从而避免了潜在的安全风险,例如XSS和CSRF,这两个网络安全问题在JWT鉴权时也存在。

显然,如果你只是按照上面这样来答肯定是不行的,跨域是一个很好和面试官拖时间的问题,既然问到了,我们就讲的详细一点,哪怕可能面试官没有问你什么情况会导致跨域,又该如何解决,我们也应该主动说出来,面试讲究的是就是一个主动出击,把面试官的话说了,不让他主动问你,就是胜利!

什么情况会导致跨域?

导致跨域的原因基本上和URL有关,当然还有一个iframe,但iframe其实本质是前端和前端之间的跨域问题,和前面URL也有些许不同。上述总结起来,无非就是以下这五点:

  1. 协议不同:当你试图从一个使用HTTPS协议的页面请求一个HTTP协议的资源时,会触发跨域限制。

  2. 域名不同:如果一个页面的主域名是 www.example1.com,而它试图访问或请求 www.example2.com 上的资源,由于域名不同,这也会产生跨域问题。

  3. 端口不同:即使域名和协议相同,但访问的端口号不同,例如,从 example.com:80 访问 example.com:8080 上的资源,也会被认为是从不同源发出的请求。

  4. 子域名不同:虽然顶级域名相同,但如果子域名不同,例如,从 sub1.example.com 访问 sub2.example.com 的资源,也将受到跨域限制。

  5. iframe嵌套:在一个页面的iframe中加载不同源的网页时,父页面尝试访问子页面内的DOM元素或执行JavaScript操作也会受到跨域限制。

如何解决跨域?

解决跨域的方式其实有很多种,它们适用于不同的场景,有需要前后端一起配合的JSONP,有只需要后端配置的CORS,也有通过后端代理来解决这个问题的,等等...

我将它们总结了一下,其实就是下面这几种

  1. JSONP : JSONP 利用 <script> 标签的src不受同源策略限制的特点,通过动态插入 <script> 标签,将要请求数据的链接放在src里面,然后在链接后面拼接一个参数作为回调函数,请求跨域数据。服务器将要返回的数据放入这个回调函数中,前端再调用这个回调函数拿到数据。但是这种方式有局限性,首先需要前后端一起配合,其次,这种方式仅限于 GET 请求,且需要服务器支持 JSONP 形式的返回。

  2. CORS : CORS 是现代浏览器支持的一种标准跨域机制,这种方式只需要后端来操作即可,后端通过在响应头中增加特定的标记(如 Access-Control-Allow-Origin),告知浏览器允许哪些源(origin)可以访问资源。还可以设置字段限制允许跨域的方式,CORS 支持各种 HTTP 方法(不仅仅是 GET),且相对更为安全和灵活。典型的例子就是我在项目中后端用的koa2-cors,其原理就是这个。

  3. 服务器端代理 : 我们都知道,跨域问题只存在于浏览器和服务端,服务端和服务端之间是不存在跨域的,所以我们就能够想到通过在服务器端设置一个代理服务,所有的请求先经过这个代理,再由代理服务器向目标服务器发起请求,拿到结果后再返回给客户端,这样客户端看起来就像是同源请求。典型的例子就是我在用vite配置的项目中时,可以在vite.config.js文件中配置服务器端代理从而解决开发阶段的跨域问题,但是由于打包后,这些文件就不生效了,所以还需要配置生产阶段的跨域。

  4. Nginx 反向代理: 使用 Nginx 等服务器软件配置反向代理规则,使得原本跨域的请求经过 Nginx 后指向实际的服务端,由于请求始终发给同一个服务器(Nginx),因此可以规避浏览器的同源策略限制。Nginx的配置和CORS很像,也是通过设置字段来允许哪些网站能跨域。这种方式在日常开发中是最常见的,也是用的最多的。

  5. WebSocket: WebSocket 通信协议支持跨域,只需在握手阶段服务器返回适当的 Access-Control-Allow-Origin 头即可。

  6. POSTMessage: 主要应用于跨窗口通信(如 iframe 和父窗口之间),允许来自不同源的窗口之间传递消息,但要求双方通过 JavaScript 进行监听和发送消息。

  7. Document.domain 设置: 适用于同顶级域名下的跨子域问题,通过设置父页面和子页面(如 iframe 中的页面)的 document.domain 属性为相同的顶级域名,实现跨子域通信。

刚刚说了这么多,由于我上述的Document.domain没有讲的我上面写的这么详细,面试官果然很敏锐,又追问到

4. document.domain 这种方式能解决所有跨域问题吗,解决哪里和哪里的跨域问题?

看了上述第七点,显然这个问题回答起来就是游刃有余了

  • document.domain 主要解决的是前端与前端之间,确切地说,是同顶级域名下跨子域的前端页面之间的跨域问题。通过这种方式,可以让两个原本受限于同源策略的不同子域页面进行一定程度的JavaScript交互。但请注意,它并不适用于所有类型的跨域场景,尤其是涉及与其他顶级域名、跨协议或跨端口的跨域问题时,还需要依靠其他跨域解决方案,比如之前我们说到的CORS等。

5. 在js中怎么去做类型判断?

很常见的八股文,这里就不展开聊,之前我也有写过这类文章 -- JS中的类型判断你真的知道吗? - 掘金 (juejin.cn) 感兴趣的掘友可以去看看。

由于我在回答这个的问题的时候,记错了一个单词,将Object.prototype.toString.call() 中的toString 记成了 slice,紧接着下一个问题随即而来,所以说面试官可真敏锐。

6. js 中只有一个toString 吗?

之后面完和我社团的同学交流,他写了篇文章总结了上述问题,我这里偷个懒,直接放上他文章的链接了

^.^ -- 面试官:js只有一个toString吗 - 掘金 (juejin.cn) 写的很不错,掘友们可以去看看

7. js中如何实现继承

给你两个构造函数,一个Parent(父类),一个 Child (子类),你有多少种实现Sub继承Super的方式?

1. 原型链继承

function Parent(){
  this.name = 'Tom'
}

function Child(){
  this.age = 18
}

// 实现
Child.prototype = new Parent() // Child的原型等于Parent 但Parent一定要是一个对象 所以要new一下

缺点1 --- 多个实例之间共用了同一个原型,属性会相互影响

缺点2 --- 子类无法给父类传参 局限性比较大

2. 构造函数继承

function Parent(name){
  this.name = name
}

function Child(name){
  Parent.call(this,name)  // 实现
  this.age = 18
}

缺点1 --- 无法继承到父类上的属性

3. 组合继承

  • 也就是将前两者合起来
function Parent(name){
  this.name = name
}

function Child(name){
  Parent.call(this,name)
  this.age = 18
}

Child.prototype = new Parent() 

缺点 --- 父类被调用两次

4. 寄生组合继承法

function Parent(name){
  this.name = name
}

function Child(name){
  Parent.call(this,name)
  this.age = 18
}

Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child

下面就是拓展的对象的继承方式了

5. 原型式继承

let obj = {
  name:'Tom',
  age:18
}

let obj2 = Object.create(obj)

缺点: 1. 多个对象共有了同一个原型,属性会相互影响

8. 节流和防抖的区别和应用场景

简单题 很常规的八股

函数节流

  • 工作原理:节流保证在一定时间间隔内,只执行一次函数。例如,每n毫秒最多执行一次。无论在这n毫秒内触发了多少次事件,只会在下一个时间间隔开始时执行函数。例如,如果你设置了一个滚动事件的节流函数,即使用户快速滚动屏幕,函数也不会过于频繁地执行,而是按照设定的时间间隔执行,比如每100毫秒执行一次。
  • 应用场景:适用于那些需要在持续触发的事件中维持一定时间间隔的行为,例如滚动事件(实时更新滚动位置但不需要太频繁)、窗口大小调整事件(响应式布局调整但不必每次像素变化都计算)、频繁的键盘输入事件(搜索建议但不必对每个按键都发起请求)等。

函数防抖

  • 工作原理:防抖确保在一系列快速连续触发的事件中,只有在事件停止触发一段时间(即所谓的等待期)后再执行一次函数。如果在等待期内又有事件触发,会重新开始计时。只有当事件触发后的一段时间内没有再次触发事件时,才会执行函数。
  • 应用场景:适用于那些只需要在用户动作结束后执行一次函数的情况,比如搜索框输入完成后提交请求(而非输入过程中频繁请求)、窗口 resize 事件(调整结束后发送通知而非在调整过程中反复发送)或按钮点击事件(防止快速重复点击导致多次提交表单)等。

简而言之,函数节流侧重于限制执行频率,保证最小间隔;而函数防抖侧重于在事件密集触发后,只执行一次最终的、有效的事件回调。

9. 手写防抖节流

很简单的手写提,光速拿下

防抖:

function debounce(fn,delay){
    let timer;
    return function(){
        let args = arguments;
        if(timer) clearTimeout(timer)
        timer = setTimeout(()=>{
            fn.call(this,...args);
        },delay)
    }
}

节流:

function throttle(fn,delay){
    let preTime = Date.now();
    return function(args){
        if(Date.now() - preTime >= delay){
            fn.apply(this,args);
            preTime = Date.now();
        }
    }
}

10. 聊一聊this的指向问题

还是偷个懒,直接引用朋友的文章了 -- js中this究竟指向哪里?现在终于搞定了! - 掘金 (juejin.cn)

11 输出判断题

  • 1和2两个this打印的是什么
const obj = {
  xx: function () {
    console.log(this)
  }
}

const fun = obj.xx
// 1
obj.xx()

// 2
fun()

看了第10点的的文章,这题回答起来就没什么困难了。

答案:

  • 1 ----- 打印的是Obj这个对象

  • 2 ----- 打印的是window这个对象 在node环境下应该是global

12. 手写promise

  • 手写promise,包括then和catch方法
class MyPromise {
  constructor(executor) {
    this.status = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledCallback = [];
    this.onRejectedCallback = [];

    const resolve = (value) => {
      if (this.status === 'pending') {
        this.status = 'fullfilled';
        this.value = value;
        this.onFulfilledCallback.forEach(callbcak => callbcak());
      }
    }

    const reject = (reason) => {
      if (this.status === 'pending') {
        this.status = 'rejected';
        this.value = reason;
        this.onRejectedCallback.forEach(callbcak => callbcak());
      }
    }

    executor(resolve, reject);
  }



  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
    onRejected = typeof onRejected === 'function' ? onRejected : reason => reason

    const newPromise = new MyPromise((resolve, reject) => {
      if (this.status === 'fullfilled') {
        setTimeout(() => {
          try{
            const result = onFulfilled(this.value)
            resolve(result)
          }catch(err){
            reject(err)
          }
        }, 0);
      }else if(this.status === 'rejected'){
        setTimeout(()=>{   // 这里用setTimeout 模拟异步微任务 实际这里应该是微任务
          try{
            const result = onRejected(this.reason);
            resolve(result)
          }catch(err){
            reject(err)
          }
        },0)
      }else{
        this.onFulfilledCallback.push((value)=>{
          setTimeout(() => { // 这里用setTimeout 模拟异步微任务 实际这里应该是微任务
            try{
              const result = onFulfilled(this.value)
              resolve(result)
            }catch(err){
              reject(err)
            }
          }, 0);
        })
        
        this.onRejectedCallback.push((reason)=>{
          setTimeout(()=>{ // 这里用setTimeout 模拟异步微任务 实际这里应该是微任务
            try{
              const result = onRejected(this.reason);
              resolve(result)
            }catch(err){
              reject(err)
            }
          },0)
        })
      }
    })

    return newPromise;
  }

  catch(onRejected){
    onRejected = typeof onRejected === 'function' ? onRejected : reason=>reason;
    return new MyPromise((resolve,reject)=>{
      if(this.status === 'rejected'){
        setTimeout(()=>{  // 这里用setTimeout 模拟异步微任务 实际这里应该是微任务
          try{
            const result = onRejected(this.reason);
            resolve(result);
          }catch(err){
            reject(err)
          }
        },0)
      }else{
        this.onRejectedCallback.push(
          (reason)=>{
            setTimeout(()=>{  // 这里用setTimeout 模拟异步微任务 实际这里应该是微任务
              try{
                const result = onRejected(this.reason);
                resolve(result);
              }catch(err){
                reject(err)
              }
            },0)
          }
        )
      }
    })
  }
}

13. 前端预加载

  • 前端预加载在Web开发中是指在用户访问页面时,提前加载和缓存后续页面或当前页面中即将使用的资源。具体来说,它涉及以下几个方面:
  1. 资源预加载

    • 通过HTML的<link>标签,可以设置rel="preload"属性,告诉浏览器提前加载指定的资源(如CSS样式表、JavaScript文件、字体文件、图片等),以便在真正需要时资源已经存在于本地缓存中,减少后续渲染时的延迟。
    <link rel="preload" as="style" href="styles.css">
    <link rel="preload" as="script" href="scripts.js">
    
  2. 图片预加载

    • 使用JavaScript创建Image对象,设置其src属性为需要预加载的图片URL,当图片加载完成后,浏览器会将其缓存,这样在后续实际需要显示图片时就能立即渲染。
    var img = new Image();
    img.src = 'image.jpg';
    img.onload = function() {
      // 图片已成功预加载
    };
    
  3. Ajax预加载

    • 通过Ajax请求提前获取和缓存数据,不仅可以预加载图片,还可以预加载JSON、XML等文本数据,甚至是CSS和JavaScript资源。
  4. 预解析DNS

    • 通过设置<link rel="dns-prefetch">,浏览器可以预先解析未来的DNS请求,减少DNS查找延迟。
    <link rel="dns-prefetch" href="//example.com">
    
  5. 预连接

    • 使用<link rel="preconnect">可以预先建立到服务器的TCP连接,进一步减少资源加载时间。
    <link rel="preconnect" href="https://cdn.example.com">
    

前端预加载有助于提高页面性能,特别是在资源丰富的单页应用或多页应用中,可以极大地提升用户体验,减少页面空白等待时间和资源加载的延迟感。

14. 聊一聊TypeScript的泛型,用来解决什么场景的问题

TypeScript 的泛型是其类型系统中的一个重要特性,它允许编写可重用的代码组件,这些组件可以在多种数据类型上工作,同时保持类型安全性。泛型的主要目的是解决以下场景的问题:

  1. 代码复用

    • 泛型允许创建适用于多种类型的数据结构或算法,例如创建一个可以容纳任何类型的集合类(如数组或映射),或者定义一个可以处理任何类型参数的方法。
  2. 类型安全性

    • 泛型在编译阶段提供类型检查,确保在处理不同类型的数据时,不会因为类型不匹配而出现运行时错误。这有助于在编译时发现潜在的类型错误,提高代码质量。
  3. 抽象和接口一致性

    • 泛型可以用于定义泛化的接口或类,这样可以强制实现这些接口或继承这些类的代码必须遵守某种类型约定,增强代码的抽象层次和一致性。
  4. 函数重载和泛型约束

    • 泛型可以用于函数重载,确保一组相关函数共享相同类型的处理逻辑。此外,通过类型约束(如extends关键字),可以限定泛型参数必须满足特定类型要求,如实现特定接口或继承特定类。
  5. 容器和数据结构

    • 在实现容器类如列表、映射等数据结构时,泛型可以指定容器中元素的类型,避免混杂不同类型的元素。

15 background-size

问 : cover 和 contain 两个属性有什么区别

  • background-size: cover:

    • 当设置为 cover 时,背景图像将会被拉伸至足够大以覆盖整个容器,同时保持图像的原始宽高比。这意味着,背景图像的宽度和高度至少会有一个维度填满整个容器(按比例缩放以适应容器的尺寸),但可能会导致图像的部分内容超出容器边界而被裁切。
  • background-size: contain:

    • 当设置为 contain 时,背景图像将会保持其原始宽高比,并尽可能大的缩放,以适应容器的尺寸,但不会超过容器的边界。因此,图像的各个边缘都会被放置在容器内,可能会在容器内留下空白区域,确保图像的完整内容可见。

16. flex弹性布局的兼容性,pc的兼容性很好嘛

  • 第一次被问到这个,直接懵了,之前没看过类似的知识,下意识是感觉pc兼容性应该是没那么的,但当时说不出个所以然。

问AI的,好家伙,原来就是问这个,我还以为现代浏览器兼容性也不好...

答案附上:

Flexbox(Flexible Box Layout Module)弹性布局模块最初在CSS3中提出,随着时间推移,各大主流浏览器对其的支持度逐步提高。在早期,flex布局的兼容性在PC端并不是很好,尤其是在IE浏览器中,仅在Internet Explorer 10及以上版本开始部分支持flex布局,而且IE10的实现存在一些bug和不兼容W3C标准的地方。

然而,随着技术发展和浏览器版本的更新迭代,如今大部分现代浏览器(包括桌面端的Chrome、Firefox、Safari、Edge等)对flex布局的支持已经相当成熟。在最新的浏览器版本中,flex布局的兼容性问题已大大减少,可以说在主流PC浏览器上,flex布局的兼容性是比较好的。

但是,对于一些陈旧的浏览器版本,特别是IE9及更早版本,flex布局仍然是不支持的。因此,在面向广泛的PC用户群时,如果需要兼容这部分老旧浏览器,可能就需要考虑回退策略,比如使用传统的布局方式或者辅助工具(如Autoprefixer)来帮助生成兼容性样式。同时,也可以通过Feature Queries(@supports)来检测浏览器是否支持flex布局,从而有条件地应用相应的CSS代码。

17. flex 父元素和子元素相关属性

之前我有总结过一篇关于flex弹性布局的文章 -- Flex布局?来看看这一篇 - 掘金 (juejin.cn)

18. position:sticky 应用场景

  • 粘性定位

应用场景示例:

  1. 导航栏(Header / Footer)

    • 当页面滚动时,顶部导航栏在到达页面顶部时变成固定不动,保持在浏览器视窗顶部,方便用户随时访问导航链接,即使向下滚动也能看到导航菜单。
  2. 购物车或用户信息悬浮框

    • 在电商网站中,页面滚动时购物车图标或用户信息面板可以保持在屏幕的某个位置,直到滚动到特定点才消失或转换为另一种展现方式。
  3. 表格标题行或列

    • 在长表格滚动时,可以将表格的第一行或第一列设置为 sticky,确保在滚动过程中标题始终可见,便于用户理解各列或行数据的意义。比如手机电话簿旁边的字母
  4. 文章详情页的目录导航

    • 长篇文章的右侧目录,当页面滚动时,目录中的当前章节标题始终保持在可视区域内,方便读者快速定位阅读进度。

19. Vue3的setup是干嘛用的,两种书写方式

Vue3 中的 setup 是一个全新的组件选项,它是Vue Composition API的核心入口点,用于替代Vue2中的data、methods、computed等选项,以更灵活和直观的方式管理组件的状态和逻辑。setup函数在组件实例被创建之前执行,也就是说它会在onMounted钩子函数之前执行(Vue3没有了created和 beforeCreate,setup从某种意义上说来说有点类似在两个生命周期之间执行的函数,仅个人理解哦),并且它能够访问到组件的props、上下文(context)以及注入的外部状态(如Vue Router的路由参数或Vuex store的状态)。

20. ref和reactive 区别

一面问了,二面又问到了,不过一面好像问的是响应式的原理,我也扯到了这两个上去。不太记得了,不过问题不大,他们之间还是有关联的。这两者的区别可以看一面的第三问。这里就不再列出来了

21. nextTick干嘛用的

Vue.js 中的 nextTick 函数用于在下次 DOM 更新循环之后执行延迟回调。由于 Vue.js 使用异步更新队列来优化渲染效率,当数据状态改变后,Vue 不会立即更新 DOM,而是把更新操作放在一个队列中,然后在当前事务完成后(所有数据更改完成后)批量更新 DOM。这样做可以避免频繁的 DOM 操作,提高性能。

nextTick 方法的作用就是在 DOM 更新之后执行一段代码,确保这段代码中的 DOM 操作是在 Vue.js 数据绑定更新 DOM 结构后执行,这对于依赖于最新 DOM 结构的代码片段至关重要。例如,当需要在数据变更后立即获取更新后的 DOM 属性值,或进行某些依赖于新 DOM 结构的操作时,可以使用 nextTick

22. Keepalive干嘛用的 使用场景 基于Vue-router的keep-alive可以写吗

Vue 的 keep-alive 组件是用来对路由组件进行缓存的,其主要作用是避免在路由切换过程中不必要的组件销毁和重建,从而提高应用性能,特别是在有大量数据渲染或复杂计算的页面之间切换时,能够保留用户界面的状态和数据,提供更好的用户体验。

使用场景包括但不限于以下几种:

  1. 页面缓存:在多层级的路由结构中,用户可能会频繁地在多个视图之间切换,如果这些视图中有大量的数据加载或复杂的渲染逻辑,那么使用 keep-alive 就可以避免每次切换时重新加载和渲染,从而提升页面切换速度和流畅度。
  2. 表单数据保持:在表单填写场景中,用户在未提交表单时切换到其他页面,返回时希望表单内容依旧保留。
  3. 列表翻页或筛选状态保持:在分页列表或带有筛选条件的列表页面,当用户翻页或筛选后切换到其他页面再回来时,期望之前的分页状态和筛选条件不变。

基于Vue-router的keep-alive的使用方式通常是将它包裹在<router-view>标签之外,以便对路由切换的组件进行缓存:

<keep-alive>
  <router-view></router-view>
</keep-alive>

这样,当用户在被包裹在keep-alive中的路由组件之间切换时,这些组件实例会被缓存,而不是被销毁和重新创建。同时,Vue Router还提供了meta属性来精细化控制哪些路由组件需要被缓存,例如:

{
  path: '/some-route',
  component: SomeComponent,
  meta: {
    keepAlive: true // 表示该路由对应的组件需要被缓存
  }
}

然后在keep-alive的使用中可以结合includeexclude等属性来指定缓存哪些组件。

比如这样:

<keep-alive include="Home, About"> 
    <router-view></router-view> 
</keep-alive>

include表示缓存哪些组件 而exclude恰恰相反。

23. Computed 和 watch的区别

这个问题...,被问过无数次了。。。

之前第一次面试的时候已经讲过了,这里就不再列出来了。

24. 什么叫Vue.js的单向数据流

Vue.js 的单向数据流是指数据在组件树中自上而下流动的一个设计理念,即数据总是从父组件流向子组件,而子组件不能直接修改父组件的状态,只能通过触发自定义事件(emit事件)的方式将变更传递给父组件,由父组件决定是否以及如何更新状态。

具体体现在以下几个方面:

  1. Props向下传递: 父组件可以通过Props向子组件传递数据,子组件只能通过Props接收父组件的数据,但不能直接修改这些Props。
  2. 事件驱动更新: 当子组件需要更新数据时,它不能直接修改从父组件接收的Props。相反,它需要触发一个事件,将更新请求通过事件冒泡的方式传递给父组件,父组件再根据需要更新自身的状态,进而可能导致子组件状态间接更新。
  3. 响应式系统: Vue.js 的响应式系统确保了当父组件的状态变化时,所有依赖这些状态的子组件都会自动更新视图,形成一种单向的数据流动和视图更新机制。

单向数据流的优点包括:

  • 易于理解和追踪数据变化,降低代码的复杂性。
  • 提高了应用的可维护性和可预测性,因为数据流的方向单一且清晰。
  • 避免了双向数据绑定可能导致的数据冲突和副作用。

25. 父子组件通讯方式

这个是因为我没想起单向数据流来,面试官问我那你父子组件是如何通讯的,这个上文之前也已经说过了,再第一次面试的那里。

26. router 和 route 分别是什么

  • router 是路由管理器,用于管理应用的整体路由配置和导航操作;而 route 是一个具体的路由实例,表示当前正在浏览的页面路由信息。
  1. router

    • router 是 Vue Router 的实例,代表整个路由系统。它负责管理应用中的路由配置、导航守卫(guards)、路由懒加载等功能。
    • 你可以通过 new VueRouter({ routes: [...] }) 来创建一个 router 实例,并将其注入到 Vue 应用中。
    • 通过 router,我们可以执行诸如跳转(router.push('/path'))、重定向(router.replace('/path'))、获取当前激活的路由实例(router.currentRoute)等操作。
  2. route

    • route 是一个具体的路由对象,代表当前激活的路由实例,包含了当前路由的所有信息,如路径(path)、查询参数(query)、参数(params)等。
    • 当用户导航到新的 URL 时,Vue Router 会解析出对应的 route 对象,并将它注入到组件实例的 $route 属性中。
    • 在组件内部,我们可以通过 this.$route 来访问当前活跃的 route 对象,并根据其属性来动态渲染组件的内容。

27. 父组件的render和子组件的render执行顺序

  • 在渲染和更新过程中,子组件的 render 函数通常会先于父组件执行,但父组件的生命周期钩子函数会先于子组件执行。这是因为Vue.js的渲染机制是由上至下(父到子)进行组件树的遍历和渲染的。
Vue2中:
  1. 初始化渲染阶段

    • 父组件先执行 beforeCreate 和 created 生命周期钩子。
    • 父组件执行 beforeMount 钩子。
    • 父组件的 render 函数被调用,开始渲染父组件模板,此时如果父组件模板中含有子组件,则会递归调用子组件的 render 函数。
    • 子组件依次执行各自的 beforeCreate -> created -> beforeMount 钩子。
    • 子组件的 render 函数被调用,渲染子组件的模板。
    • 最终,子组件先完成渲染(生成虚拟DOM),然后父组件完成渲染。
    • 所有组件的 mounted 钩子按从子到父的顺序执行。
  2. 更新阶段

    • 当数据发生变化时,组件进入更新阶段。
    • 父组件先执行 beforeUpdate 钩子。
    • 父组件的 render 函数重新执行,开始渲染更新后的模板,同样会递归到子组件进行渲染。
    • 子组件依次执行各自的 beforeUpdate 钩子。
    • 子组件的 render 函数重新执行,更新子组件的虚拟DOM。
    • 在这个过程中,子组件的 render 函数会先于父组件的 render 完成更新。
    • 最后,所有组件按从子到父的顺序执行 updated 钩子。
Vue3中
  1. 初始化渲染阶段

    • 父组件先执行 setup() 函数中的相关操作。
    • 父组件挂载前,如果有 onBeforeMount 回调函数,它会被执行。
    • 父组件的 render 函数被调用,开始渲染父组件模板,遇到子组件时,会执行子组件的 setup() 和挂载前的相关生命周期钩子。
    • 子组件按照它们在模板中的顺序执行各自的 setup() 函数以及 onBeforeMount 回调。
    • 子组件的 render 函数被调用,渲染子组件的模板。
    • 渲染完成后,所有组件按从子到父的顺序执行 onMounted 回调。
  2. 更新阶段

    • 父组件在数据变化触发更新时,如果有 onBeforeUpdate 回调函数,它会首先被执行。
    • 父组件的 render 函数再次执行,开始更新其模板及其中包含的子组件。
    • 子组件按照它们的顺序执行各自的 onBeforeUpdate 回调。
    • 子组件的 render 函数重新执行,更新子组件的虚拟DOM。
    • 更新完成后,所有组件按从子到父的顺序执行 onUpdated 回调。

27. 介绍一下平时开发用的ui库和工具库

主要讲了一下ui库为 Element-plus 和 vant,然后工具库为VueUse和一些构建工具的库比如webpack,vite,rollup,babel等等...

28. 项目正常上线的流程

由于我还没有实习过,但是本人两个项目都部署到服务器上去了,从打包到下载Xshell连接宝塔,使用宝塔进行前后端的配置,开发端口,配置数据库,等等...

实际项目开发不太清楚,这里直接引用网上找来的了。

项目的正常上线流程通常涉及多个阶段和角色协作,以下是一个典型的Web项目上线流程概述:

  1. 开发阶段

    • 完成项目需求分析与设计。
    • 开发人员依据设计稿和需求文档编写代码,并定期将代码提交到版本控制系统(如Git)。
    • 在开发环境进行单元测试、集成测试,确保功能正常。
  2. 代码审查(Code Review)

    • 团队成员交叉审查代码,确保代码质量、风格统一和无明显问题。
  3. 构建与部署到测试环境

    • 开发人员通过CI/CD工具(如Jenkins、GitHub Actions、Travis CI等)进行自动化构建。
    • 构建产物部署到测试服务器,进行详尽的功能测试、性能测试、兼容性测试、压力测试等。
  4. 修复Bug与优化

    • 根据测试反馈修复存在的问题,优化性能或用户体验。
    • 循环进行上述测试直至达到上线标准。
  5. UI验收与产品验收

    • UI设计师和产品经理对产品进行视觉和功能验收,确认与设计稿和需求相符。
  6. 数据迁移与备份

    • 如有需要,进行生产环境的数据迁移和备份,确保上线后数据完整性。
  7. 部署到预生产环境(如有)

    • 部署到一个与生产环境相似的预生产环境,进行最后一次模拟线上环境的验证。
  8. 发布计划与公告

    • 制定详细的上线计划和回滚方案。
    • 若有必要,提前通知用户更新事项,比如系统维护公告。
  9. 部署到生产环境

    • 在低流量时段,通过CI/CD流程将最终版本部署到生产服务器。
    • 期间密切关注服务器负载、日志监控等指标。
  10. 在线监控与验证

    • 上线后,运维人员密切关注系统的各项监控指标,确保系统稳定性。
    • 产品团队验证线上功能是否正常,用户是否能正常使用。
  11. 灰度发布(可选)

    • 对于大型项目,可能采取灰度发布的策略,逐步将新版本推送给部分用户,观察效果,发现问题及时修复。
  12. 后期维护与优化

    • 上线后持续收集用户反馈,跟踪线上问题,进行必要的热修复或版本迭代。

以上流程仅供参考,具体步骤可能会根据项目规模、团队分工、公司政策等因素有所增删和调整。

然后这些问题就大概已经面了50多分钟了,也没考算法题,这是我没有想到的,写这篇文章的时候已经是周二了,前面加起来写了三四天。从上周四下午面完二面开始写的,目前还在等二面的消息,面试官最后问了一下如果我实习什么时候能来,会不会影响到我的秋招准备面其他大厂,然后就没有消息了...

我想大概率是二面寄了 哎...

准备准备,冲击其他大厂了 ^.^

当然,如果你也在准备春招,可以加我微信 biangbiang6161 , 我有一群平时一起学习的小伙伴,可以互相分享面试经历,交流面试经验。春招冲击大厂,加油 ^.^