前端面试题详解整理70| vue2的生命周期,用CORS怎样解决跨域, JSONP的工作原理,不用filter对数据如何进行筛选,谁都可以向服务器发送请求,

35 阅读14分钟

小米前端日常实习

第一次面大厂居然是米子, 我的米 实习是在实习僧投的, 反馈很快, 下午打电话约的面试时间, 邮件确认

面试开始拷打项目, 问Vue框架相关的内容, 后面做手写题 ( 心累, 看了一下别人的面经, 猜测小米喜欢问JS基础多一点, 以为项目和框架问的较少, 结果JS基础没有怎么问...... 收到面试邀请的时候比较意外, 准备没有很充分 )

  1. 项目相关的

1. 不用filter对数据如何进行筛选

如果不使用 filter 方法来筛选数据,可以使用其他方式,比如使用 for 循环遍历数据并根据条件进行筛选,然后将符合条件的数据放入一个新的数组中。以下是一个示例代码:

// 假设有一个数组 arr 存储了待筛选的数据
const arr = [1, 2, 3, 4, 5];

// 定义一个空数组用于存储筛选后的数据
const filteredArr = [];

// 使用 for 循环遍历数据进行筛选
for (let i = 0; i < arr.length; i++) {
  if (arr[i] > 2) {
    // 如果数组元素大于 2,则将其放入 filteredArr 中
    filteredArr.push(arr[i]);
  }
}

// 打印筛选后的结果
console.log(filteredArr); // 输出:[3, 4, 5]

在这个示例中,我们使用 for 循环遍历数组 arr,对每个元素进行判断,如果元素大于 2,则将其添加到 filteredArr 数组中。这样就实现了对数据的筛选操作。

  1. 向服务端发送请求用的什么工具

4. 谁都可以向服务器发送请求吗?

向服务端发送请求通常使用的工具包括:

  1. 浏览器:最常见的方式是通过浏览器地址栏输入 URL 地址或者通过页面中的 JavaScript 脚本发起 HTTP 请求。

  2. 命令行工具:例如 curlwget 等命令行工具可以直接发送 HTTP 请求。

  3. 开发工具:像 Postman、Insomnia 等专业的 API 调试工具,可以方便地构建和发送 HTTP 请求,查看请求响应结果。

  4. 编程语言的 HTTP 客户端库:几乎所有的编程语言都有自己的 HTTP 客户端库,比如在 JavaScript 中可以使用 fetchXMLHttpRequestaxios 等库来发送 HTTP 请求;在 Python 中可以使用 requests 库;在 Java 中可以使用 HttpClient 等库。

至于谁可以向服务器发送请求的问题,一般情况下是有一定限制的:

  • 对于公共开放的接口,一般是允许任何人发送请求的,比如一些公开的 API 接口。
  • 对于需要用户身份认证或者权限控制的接口,通常需要提供有效的身份认证信息(比如用户名和密码、Token 等)才能发送请求。
  • 对于一些需要特殊权限或者受到限制的接口,可能只允许特定的用户或者特定的 IP 地址发送请求,其他用户无法直接发送请求。

因此,并不是所有人都可以随意向服务器发送请求,具体是否允许发送请求需要根据接口的设置和权限进行判断。

6. 什么是跨域?

跨域(Cross-Origin)是指在 Web 开发中,当浏览器试图在当前网页的 Origin(协议 + 域名 + 端口)下访问另一个 Origin 的资源时发生的一种安全限制。如果请求的目标资源的 Origin 与当前网页的 Origin 不同,就会发生跨域。

7. JSONP的工作原理是什么样的?

JSONP(JSON with Padding)是一种跨域请求的解决方案,其工作原理是利用 <script> 标签的跨域特性,动态创建 <script> 标签,将请求的数据作为回调函数的参数传递,并将请求的 URL 地址中指定一个回调函数的参数名,服务器返回的数据会作为这个回调函数的参数来执行,从而实现跨域请求。

8. 用CORS怎样解决跨域?

CORS(Cross-Origin Resource Sharing,跨域资源共享)是一种跨域解决方案,它通过在 HTTP 头部添加一些字段来告诉浏览器,当前页面所在的域(Origin)是否被允许访问目标资源。具体而言,服务端需要在响应中添加 Access-Control-Allow-Origin 头部字段来指定允许访问的源,浏览器会根据这个字段来决定是否允许跨域访问。

下面简要介绍一下这三种解决跨域的方案的实现原理:

  1. 跨域:浏览器的同源策略限制了从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。只有当两个 URL 的协议、端口(如果有指定)、主机名完全相同时,才允许相互交互。如果不同源,就会发生跨域。

  2. JSONP:JSONP 利用 <script> 标签的跨域特性来实现跨域请求,通过在 URL 中指定一个回调函数的参数名,服务器返回的数据会作为这个回调函数的参数来执行。因为 <script> 标签的 src 属性不存在跨域限制,所以可以通过动态创建 <script> 标签来加载远程资源,从而实现跨域请求。

  3. CORS:CORS 是一种标准的跨域解决方案,它通过在服务端响应中添加一些特定的 HTTP 头部字段,告诉浏览器当前页面所在的域是否被允许访问目标资源。如果目标资源的服务端允许当前页面所在的域访问资源,浏览器就会将实际响应返回给页面,否则浏览器会拦截响应,阻止页面访问目标资源。

10. 简单说一下Vue2的生命周期有哪些?

11. keep-alive相关

  1. Vue 爷孙节点如何进行通信

Vue 2.x 的生命周期钩子包括以下几个:

  1. beforeCreate:实例刚被创建,此时数据观测和事件配置都尚未完成。
  2. created:实例已经创建完成,数据观测和事件配置已完成,但尚未挂载到 DOM 上。
  3. beforeMount:在挂载开始之前被调用,相关的 render 函数首次被调用。
  4. mounted:实例已经挂载到 DOM 上,此时组件已经渲染完成。
  5. beforeUpdate:数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。
  6. updated:数据更新后调用,发生在虚拟 DOM 重新渲染和打补丁之后。
  7. beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
  8. destroyed:实例销毁后调用,所有的事件监听器和子实例被移除。

关于 keep-alive,它是 Vue 内置的一个组件,用于缓存不活动的组件实例,防止它们被销毁。它有两个主要的属性:

  • include:字符串或正则表达式,只有匹配的组件会被缓存。
  • exclude:字符串或正则表达式,匹配的组件不会被缓存。

keep-alive 主要通过在组件的生命周期钩子中加入逻辑,来实现缓存和销毁组件实例的功能。

关于 Vue 爷孙节点之间的通信,通常可以通过以下方式实现:

  1. 通过 props 和事件:父组件可以通过 props 将数据传递给子组件,子组件则可以通过 $emit 触发事件,将数据传递给父组件。这是 Vue 中最常用的父子组件通信方式。
  2. 通过 provide 和 inject:父组件可以通过 provide 提供数据,子组件则可以通过 inject 注入数据。这种方式允许祖先组件提供数据,然后允许子孙组件在不通过 props 传递的情况下访问这些数据。
  3. 通过 attrsattrs 和 listeners:如果子组件没有声明任何 prop,且需要将所有的 attribute 和事件传递到原生 HTML 元素上,可以使用 $attrs$listeners 来实现。

这些是 Vue 2.x 中常用的父子组件通信方式,可以根据具体场景选择合适的方式。

  1. Vue学习时间有多久

15. Vue2和Vue3之间发生的改变?

Vue 3 相对于 Vue 2 做了许多重大的改变和优化,主要包括以下几个方面:

  1. 性能优化

    • Vue 3 中引入了基于 Proxy 的响应式系统,相比 Vue 2 中的 Object.defineProperty,Proxy 的性能更高。
    • 新的编译器能够生成更小、更快的运行时代码。
    • 优化了静态节点提升和树摇动态节点,减小了包大小。
    • 重新设计了虚拟 DOM 的实现,提高了渲染性能。
  2. Composition API

    • 引入了 Composition API,使组件内部的逻辑更易于复用和组合。
    • Composition API 允许开发者根据功能相关性组织代码,而不是强制按照生命周期方法组织代码。
  3. TypeScript 支持

    • Vue 3 对 TypeScript 的支持更加友好,内置了 TypeScript 的类型定义,提供了更好的类型推断和错误提示。
  4. Fragment 和 Teleport

    • 引入了 Fragment 和 Teleport,提供了更灵活的组件结构和渲染位置控制。
  5. 更好的 TypeScript 集成

    • Vue 3 的代码库和编译器都是使用 TypeScript 编写的,这意味着对 TypeScript 的支持更加紧密和完善。
  6. 全面重构

    • Vue 3 的内部代码进行了全面重构,使得代码更加清晰、模块化和易于维护。

总的来说,Vue 3 在性能、开发体验和功能特性等方面都有明显的提升,是 Vue 生态系统的一次重大升级。

17. Vue组件有个state多次在template中被用到, 如果改变了state10次, 页面渲染几次?

如果一个 Vue 组件的状态(state)在模板中多次使用,并且每次状态变化都会触发重新渲染,那么页面会重新渲染多次,具体次数取决于状态变化的次数和模板中状态使用的次数。

假设状态变化了 10 次,并且模板中状态被使用了 10 次,那么页面会重新渲染 10 次。

然而,Vue 在进行重新渲染时,会尽可能地进行优化,避免不必要的重复渲染,这通常通过虚拟 DOM 的比对和批量更新来实现。因此,即使状态变化了 10 次,但实际页面重新渲染的次数可能会少于 10 次,具体取决于 Vue 内部的优化策略和渲染机制。

18. 如何做到数据改变一次就渲染一次?

在 Vue 中,可以通过以下几种方式实现数据改变一次就渲染一次:

  1. 手动触发渲染:可以使用 this.$forceUpdate() 方法手动触发组件的重新渲染。这个方法会强制组件重新渲染,无论数据是否发生了变化。

  2. 使用计算属性:Vue 中的计算属性是基于响应式依赖进行缓存的,只有在相关的响应式数据发生改变时,计算属性才会重新求值。因此,将需要频繁更新的数据逻辑放在计算属性中,可以确保数据改变一次就渲染一次。

  3. 使用 watch 监听数据变化:Vue 的 watch 选项可以用来监听数据的变化,并在数据变化时执行特定的逻辑。通过 watch 监听数据的变化,可以在数据改变时执行渲染逻辑,从而实现数据改变一次就渲染一次。

  4. 使用函数式组件:Vue 3 中引入了函数式组件,函数式组件是无状态的,只有在传入的 props 发生变化时才会重新渲染。因此,使用函数式组件可以确保数据改变一次就渲染一次。

  5. 使用 v-once 指令:在模板中使用 v-once 指令可以将其所在的元素和组件标记为静态内容,并且只渲染一次。这样就可以确保数据只渲染一次。

以上是一些常见的方法,你可以根据具体情况选择合适的方式来实现数据改变一次就渲染一次的效果。

20. 介绍一下TailwindCSS

Tailwind CSS 是一个基于原子类的 CSS 框架,它提供了一组预定义的原子类,用于快速构建界面。与传统的 CSS 框架不同,Tailwind CSS 不提供预定义的组件或布局,而是将样式属性拆分成单个类,每个类代表一个具体的样式属性。

以下是 Tailwind CSS 的一些特点和优势:

  1. 原子类:Tailwind CSS 的核心思想是原子类,即将样式属性拆分成单个类,每个类代表一个具体的样式属性。通过组合不同的原子类,可以快速构建出丰富多样的界面样式。

  2. 可定制性:Tailwind CSS 提供了丰富的配置选项,可以根据项目需求进行定制。用户可以通过配置文件来自定义颜色、间距、字体等样式属性,以满足项目的需求。

  3. 响应式设计:Tailwind CSS 内置了一套响应式设计系统,可以根据不同的屏幕尺寸应用不同的样式。通过添加相应的响应式前缀类,可以轻松实现响应式布局和设计。

  4. 功能丰富:Tailwind CSS 提供了丰富的功能类,涵盖了排版、颜色、边框、背景、阴影等各个方面的样式属性。开发者可以利用这些功能类快速实现各种常见的样式效果。

  5. 易于学习:由于 Tailwind CSS 使用原子类来构建样式,因此学习曲线相对较低。开发者无需记忆大量的 CSS 类名,只需根据需要选择合适的原子类进行组合即可。

  6. 优化性能:Tailwind CSS 可以通过 PurgeCSS 等工具将未使用的样式自动删除,从而减小 CSS 文件的体积,提高页面加载性能。

总的来说,Tailwind CSS 是一个功能丰富、灵活定制、易于学习的 CSS 框架,适用于快速构建现代化的 Web 界面。

22. 浏览器打开多个窗口, 对同一个WebSocket地址进行连接能够同时得到数据吗?

WebSocket 是一种基于 TCP 协议的全双工通信协议,它通过在客户端和服务器之间建立持久的连接,实现了双向的实时通信。因此,如果多个浏览器窗口都连接到同一个 WebSocket 地址,它们都可以同时接收到从服务器端发送过来的数据。

每个浏览器窗口都会建立自己的 WebSocket 连接,这些连接是相互独立的,因此它们之间不会相互影响。当服务器端发送数据时,每个连接都会单独接收到数据,并且可以在客户端进行相应的处理和展示。

需要注意的是,WebSocket 连接是建立在 TCP 协议之上的,因此服务器端需要支持 WebSocket 协议,并且能够处理多个连接。另外,由于浏览器窗口之间的连接是相互独立的,因此服务器端发送的数据会被复制到每个连接中,可能会增加服务器的负载。因此在设计 WebSocket 应用时,需要考虑服务器的性能和资源消耗。

  1. 问的差不多了, 开始做题: (没想到飞书的视频这么高级, 在桌面端能够直接弹窗让写代码, 窗口双方都可以编辑, 几乎没有代码提示)
  2. 使用CSS手写一个三角形, 这个小米考的比较多, 所以提前背了一下, 本来想多写几种的, 被打断施法说写一种就可以!

26. 面试官说: "既然你项目当中有树相关的内容, 那么就来写一个树的题目吧! ", (我内心狂喜, 刚刚才复习了BFS和DFS, 之前看同公司实习面经也有问到这个题, 这不直接拿下? ), 题目是使用JS将一个{"a.b.c": 123,"a.c": 456}形式的对象转换成一个嵌套的对象, 当时有点紧张, 面试官说树怎么怎么, 但是看示例输出没有node相关的结构, 然后当时就比较纠结是不是要判断已经存在的节点(脑子有点抽了), 而且连split方法也给忘记了, 寄. (题目简单还是下去实现了一下) (自己基础还是比较差, 关键时候想不起来)

// 定义一个函数,用于将形如 {"a.b.c": 123, "a.c": 456} 的对象转换为嵌套的对象形式
function convertToObject(obj) {
  // 创建一个空对象,用于存储转换后的结果
  const result = {};

  // 遍历输入对象的每个键值对
  for (const key in obj) {
    // 将当前键名按照 . 进行拆分,得到一个键名数组
    const keys = key.split('.');
    // 定义一个临时变量用于遍历嵌套对象,初始值为结果对象
    let temp = result;

    // 遍历键名数组,逐级创建嵌套属性
    for (let i = 0; i < keys.length; i++) {
      // 获取当前键名
      const k = keys[i];

      // 如果是最后一个键名
      if (i === keys.length - 1) {
        // 将值赋给最后一个属性
        temp[k] = obj[key];
      } else {
        // 如果不是最后一个键名,则判断属性是否已存在,如果不存在则创建一个空对象
        temp[k] = temp[k] || {};
        // 更新临时变量为当前属性的值,用于进一步遍历嵌套对象
        temp = temp[k];
      }
    }
  }

  // 返回转换后的嵌套对象
  return result;
}

// 示例输入对象
const input = {"a.b.c": 123, "a.c": 456};
// 调用转换函数,将输入对象转换为嵌套对象
const output = convertToObject(input);
// 打印转换后的结果
console.log(output);

这段代码通过详细的注释解释了函数的每一步操作,包括遍历输入对象、拆分键名、逐级创建嵌套属性等过程。注释使得代码更易读,更容易理解其功能和实现原理。 你可以使用 JavaScript 来实现这个转换过程。以下是一个简单的实现方法:

function convertToObject(obj) {
  const result = {};

  for (const key in obj) {
    const keys = key.split('.');
    let temp = result;

    for (let i = 0; i < keys.length; i++) {
      const k = keys[i];

      if (i === keys.length - 1) {
        temp[k] = obj[key];
      } else {
        temp[k] = temp[k] || {};
        temp = temp[k];
      }
    }
  }

  return result;
}

const input = {"a.b.c": 123, "a.c": 456};
const output = convertToObject(input);
console.log(output);

这段代码首先定义了一个 convertToObject 函数,该函数接受一个形如 {"a.b.c": 123,"a.c": 456} 的对象作为参数,并返回一个嵌套的对象。

在函数内部,使用 for...in 循环遍历输入对象的每个键值对。对于每个键值对,使用 split() 方法将键名按照 . 进行拆分,得到一个键名数组。然后遍历该数组,在嵌套的结果对象中逐级创建属性,并将值赋给最后一个属性。

最后返回转换后的嵌套对象。通过调用这个函数,你可以将输入的对象转换成嵌套的对象形式。

  1. 反问, 互相了解
  2. 小米前端主要的工作是什么?
  3. 实习生工作的主要安排是什么?
  4. 实习的话, 学校那边如何安排?
  5. 期望的工作地点是哪里?
  6. 学习相关的
function transformInput(input) {
  const result = {};
  Object.keys(input).forEach((key) => {
    // 将对象的key进行拆分
    const splitKeys = key.split(".");
    let current = result;

    splitKeys.forEach((k, index) => {
      if (index === splitKeys.length - 1) {
        current[k] = input[key];
      } else {
        current[k] = current[k] || {};
        current = current[k];
      }
    });
  });
  return result;
}

// Example usage:
const input = {
  "a.b.c": 123,
  "a.c": 456,
};

const output = transformInput(input);
console.log(output);

作者:在等offer的刚子很伟大
链接:www.nowcoder.com/discuss/565…
来源:牛客网