【前端面经 | 腾讯】2024 面试复盘第一弹——腾讯

471 阅读11分钟

总结

腾讯的面试进来便直接手撕了两个代码,不过题目都属于LeetCode上的,比较常见。后面就先考察了一些八股文,还有些项目和项目上的扩展问题。感觉很多面试官都喜欢基于项目进行扩展,包括追究底层原理或者进行新场景的问题,有什么解决方案。还是很喜欢问扩展类的,答起来也是很爽的。

总而言之,有些地方没有答上来,最后还是凉了。不过还是很开心,有这样一次很好的学习机会。

手撕代码

最长子序列

面试的时候 在dp数组初始化时想当然均赋值为0 导致后面输出结果有问题 不过面试官很好 提醒了下“dp数组设置的含义” 后面就改正过来了

   function longer(nums) {
     if (nums.length === 0) return [];

     // 初始化dp数组,长度与nums相同,默认值为1(因为每个元素本身都可以看作是一个长度为1的递增子序列)
     const dp = new Array(nums.length).fill(1);
     // 用于记录最长递增子序列的构造过程
     const prev = new Array(nums.length).fill(-1);

     let maxLength = 1; // 最长递增子序列的长度
     let endIndex = 0; // 最长递增子序列的最后一个元素的索引

     for (let i = 1; i < nums.length; i++) {
       for (let j = 0; j < i; j++) {
         // 如果当前元素大于前一个元素,并且以当前元素结尾的递增子序列长度比之前更长
         if (nums[i] > nums[j] && dp[i] < dp[j] + 1) {
           dp[i] = dp[j] + 1;
           prev[i] = j; // 记录前一个元素的索引
         }
       }

       // 更新最长递增子序列的长度和最后一个元素的索引
       if (dp[i] > maxLength) {
         maxLength = dp[i];
         endIndex = i;
       }
     }

     // 根据prev数组回溯构建最长递增子序列
     const lis = [];
     let k = endIndex;
     while (k !== -1) {
       lis.unshift(nums[k]); // 从后向前添加元素到lis数组中
       k = prev[k];
     }

     return lis;
   }

   // 示例用法
   const nums = [10, 9, 2, 5, 3, 7, 101, 18];
   const lis = longer(nums);
   console.log(lis); // 输出:[2, 3, 7, 101] 或者其他可能的递增子序列,只要它是最长的

版本号比较大小

面试的时候 由于传递的是字符串 导致'10'和‘9’的大小比较存在问题 后面测试时修正过来了。

   function compareVersions(version1, version2) {
     let v1 = version1.split(".");
     let v2 = version2.split(".");

     for (let i = 0; i < Math.max(v1.length, v2.length); i++) {
       // 如果某个版本号缺少部分,则视为0
       // 必须number 字符串中10<9
       let num1 = Number(v1[i]) || 0;
       let num2 = Number(v2[i]) || 0;

       // 比较数字
       let diff = num1 - num2;
       if (num1 > num2) return 1;
       if (num1 < num2) return -1;
     }

     // 所有数字都相同,视为版本号相等
     return 0;
   }
   
    // 示例
   console.log(compareVersions("1.2.3", "1.2.3")); // 输出 0
   console.log(compareVersions("1.10.3", "1.9.4")); // 输出 -1
   console.log(compareVersions("1.2.3", "1.1.3")); // 输出 1

八股文

1 vue 双向绑定 v-model 原理是什么?

本质为语法糖 在输入框中使用 v-model 相当于同时设置了 v-bind:value 来绑定数据 并添加了 v-on:input 监听并处理数据变化

双向绑定的实现:当用户在输入框输入内容时 会触发 v-on:input 事件 vue 会捕获这个事件并更新绑定的数据属性。同时 vue 会监听数据变化 一旦数据变化 vue 会自动更新视图。

2 如何实现 input 中 v-model 方法?

   <input id="myInput" type="text" />
   <p id="displayText">Value:</p>

   <script>
     // 获取输入框和显示文本的元素
     const input = document.getElementById("myInput");
     const displayText = document.getElementById("displayText");

     let currentValue = ""; // 初始化绑定的变量

     // 更新显示的文本
     function updateDisplay() {
       displayText.textContent = "Value: " + currentValue;
     }

     // 监听输入框的输入事件
     input.addEventListener("input", function (event) {
       currentValue = event.target.value; // 更新数据
       updateDisplay(); // 更新显示的文本
     });

     // 如果需要从数据变化出发更新输入框(模拟Vue的响应式)
     // 可以想象有一个地方会改变currentValue的值
     // 模拟一个数据改变的场景
     setTimeout(() => {
       currentValue = "Hello from timeout";
       updateDisplay();
       input.value = currentValue; // 更新输入框的值以反映数据变化
     }, 700);
   </script>

3 为什么不直接操作虚拟 dom 若直接确定修改真实 dom 会不会比虚拟 dom 快

不会更快 因为真实 DOM 的操作涉及到了浏览器的布局和渲染过程

  1. 减少重排与重绘:直接修改真实 DOM 可能导致不必要的重排(layout)和重绘(paint)虚拟 DOM 通过批量计算差异(diff 算法),仅对必要的部分进行最小化的真实 DOM 更新,从而减少重排和重绘。
  2. 批量更新:虚拟 DOM 允许你在一个批次中累积多个状态变化,然后一次性应用到真实 DOM 上,这样可以减少与 DOM 交互的次数,提高效率。
  3. 计算效率:虚拟 DOM 是轻量级的 JavaScript 对象结构,比真实的 DOM 树操作更高效。修改 JavaScript 对象的速度远快于修改真实 DOM

4 https 中 TLS(SSL) 具体是怎么加密的?

HTTPS 中的TLS加密,就是在客户端与服务器建立连接时,通过 TLS 握手协议安全地协商出一个对称加密密钥。这个过程中,服务器会提供一个证书证明其身份,客户端验证证书后,使用证书中的公钥加密并发送一个对称密钥给服务器;服务器用私钥解密得到该对称密钥。之后通过这个共享的对称密钥进行数据传输

  1. 对称密钥:加密和解密过程使用相同的密钥
  2. 非对称密钥:包含一对密钥:公钥(Public Key)和私钥(Private Key) 公钥用于加密 私钥用于解密
  3. 会话密钥:在一次会话或交流中临时使用的对称密钥

5 Web Worker 有了解吗?

【占坑】会出一期单独介绍

  1. 含义:Web Worker 是 HTML5 引入的一个 API,允许在浏览器后台独立于主线程运行 js 脚本
  2. 作用:可以执行繁重的计算任务(图像处理、音频处理、大数运算、离线计算等)保证页面的流畅性
  3. 使用步骤:
    1. 创建 worker 实例 参数为 worker 脚本的 URL 目的:启动一个后台任务
    2. 通过.postMessage() 实现主线程和 worker 之间的通信
    3. worker 接收消息 通过.onMessage()进行事件处理
    4. 处理完成后 通过.postMessage()将信息返回给主线程
    5. 主线程通过监听.onMessage()接收结果
    6. 最后 任务完成时 通过 terminate 关闭 worker
  4. 缺点:
    1. 不能直接访问 dom 不能使用浏览器默认的方法和属性(alert 等)

6 proxy 的监听原理有了解吗?

Proxy 可以创建目标对象的代理 来实现数据更新。Proxy 在访问(get)或修改(set)对象属性时 可以拦截这些操作并添加自定义处理逻辑 在数据变化时自动执行副作用函数 使得数据变化时进行视图更新 而无需手动监听每个属性

7 闭包是如何延长变量的生命周期

因为闭包是函数内部返回函数 内部的函数有权访问其自身作用域、外部函数作用域以及全局作用域中的变量

  1. 作用域链的形成:当外部函数返回内部函数时,内部函数(即闭包)会保留对外部函数作用域变量的引用
  2. 声明周期延长:正常情况下函数执行完后,局部变量会随着执行上下文的销毁而被回收。但闭包使得外部函数的局部变量在外部函数执行结束后仍然存活。只要闭包存在,这些变量就一直有效

8 XSS是什么 XSS如何预防?

XSS 跨站脚本攻击,是指浏览器中执行恶意脚本(无论是跨域还是同域),从而拿到用户的信息并进行操作。(攻击者想尽一切办法将恶意的 html 代码插入到网页中,当用户访问时,该代码就会被执行,从而达到攻击用户的目的)

  1. 出现场景:
    1. 窃取 Cookie。
    2. 监听用户行为(输入账号密码后直接发送到黑客服务器)。
    3. 修改 DOM 伪造登录表单。
    4. 在页面中生成浮窗广告。
  2. 类型:
    1. 存储型:攻击者通过可输入区域,将恶意代码永久存在数据库中(评论、私信等)
    2. 反射型:存在 url 上,用户需要主动打开 url 才可以(网站跳转、搜索)
    3. DOM 型:存在 url 上,同反射型,区别为取出和执行恶意代码由浏览器端完成
  3. 预防方法:
    1. 【宗旨】:防止攻击者提交恶意代码,防止浏览器执行恶意代码
    2. 过滤所有的非法字符
    3. 对链接跳转,进行内容检测
    4. 限制输入长度
    5. 输入内容加密

9 小程序双线程架构有了解吗?

【占坑】 不太了解

项目

10 大型文件和多文件上传如何实现?

【大型文件上传】

  1. 整体过程:定义切片原则(Math.ceil取整) ,利用file.slice进行切片,将切片的整体hash名、文件以对象的形式添加到切片数组中。循环遍历这个数组调接口进行文件上传,当全部文件上传后调用接口合并上传的文件。

  2. 获取文件的hash值和后缀:利用FileReader的readAsArrayBuffer方法获取文件的buffer数据,利用spark-md5的ppendChild方法获取hash值(SparkMD5.ArrayBuffer( ) )文件名则为hash值与后缀的拼接

  3. 断点续传:定义数组用来接收已经上传的片段。在上传函数中进行判断,如果已上传的数组中包含切片的hash名则调用完成的函数(进度条样式+调用接口合并切片,传值:hash和总数量) 并return

【多文件上传】

  1. 选择文件:将获取文件的伪数组集合转为数组Array from,并循环遍历数组给每个文件添加唯一标识的自定义属性(随机数*时间.toString(16) 16进制),利用innerHTML设置前端显示情况。利用事件委托(parentNode父节点)、filter实现删除文件功能。

  2. 上传文件:循环发送上传请求,利用axios的onUploadProgress中的e.loaded已上传量、e.total需传的总量来监听进度,最后利用Promise.all设置所有文件上传成功的提示。

11 大文件如何实现断点续传 大文件和多文件上传失败的时候怎么做?

【占坑】 会整理出全部的方法 单独出一期

12 如何实现秒传?

  1. 秒传:如果服务器上已经存在相同的文件,则不需要重新上传整个文件,而是直接完成上传过程,或者仅上传少量的校验数据以验证文件是否一致。
  2. 文件哈希值计算:客户端在上传文件之前,首先计算文件的哈希值(如 MD5、SHA-1 等)。哈希值是根据文件内容生成的唯一标识符,只要文件内容相同,哈希值就相同。(使用 JavaScript 库(如 SparkMD5、crypto-js 等)在客户端计算文件的哈希值)
  3. 检查哈希值是否存在:客户端将计算出的哈希值发送到服务器 服务器在数据库中或文件系统中查找该哈希值是否已存在
  4. 文件秒传:如果服务器找到与客户端提供的哈希值匹配的文件,服务器会告诉客户端文件已经存在,无需再次上
  5. 文件上传:如果服务器没有找到匹配的哈希值,客户端需要上传整个文件,服务器接收文件,计算文件的哈希值,并将其与文件名、文件路径等信息一起存储在数据库中
  6. 错误处理:如果在哈希值计算、文件上传或服务器响应过程中发生错误,客户端需进行处理
  7. 优化性能:
    1. 对于大型文件,计算哈希值可能需要一些时间。为了提高用户体验,可以在用户选择文件后立即开始计算哈希值,并在上传过程中显示进度。
    2. 如果服务器经常需要处理大量上传请求,可能需要考虑使用缓存或分布式存储来加速哈希值的查找过程。