【前端面经 | 百度】2024 面试复盘第六弹——百度

135 阅读17分钟

一面

1. 描述一个项目中大概负责的功能点以及实现的效果?

3. 文件上传进度跟踪 如何实现?

4. css 选择器优先级?

5. 正方形会随着浏览器窗口的拖拽调整大小比例 浏览器宽度缩小 大小比例会自动缩小 如何做 vw?

<style>
  #square {
    background-color: #333;
    /* 初始大小设置,可能会被JavaScript覆盖 */
    width: 100vw;
    height: 100vw;
    max-width: 100%; /* 防止正方形过大超出视口宽度 */
    max-height: 100vh; /* 防止正方形过大超出视口高度 */
  }
</style>

<div id="square"></div>

<script>
  function resizeSquare() {
    const square = document.getElementById("square");
    const sideLength = Math.min(window.innerWidth, window.innerHeight); // 取较小的值以保持正方形
    square.style.width = `${sideLength}px`;
    square.style.height = `${sideLength}px`;
  }

  // 初始设置大小
  resizeSquare();

  // 监听窗口大小变化
  window.addEventListener("resize", resizeSquare);
</script>

6. 实现两个边框 回字形?

7. 基本数据类型有哪些?

8. js 的基本数据类型 存储方式有哪些?

9. 为什么复杂数据类型(引入数据类型)存放在数据堆中?

  1. 复杂数据类型的大小是动态的 在运行过程中也会发生变化(增删数组或对象的元素) 堆内存允许动态分配或重新分配内存 而栈内存通常分配固定大小的空间
  2. 实现数据共享 将复杂数据类型存放在堆中 并通过栈中的引用来访问他们 可以实现多个变量指向同一个复杂数据对象 节省内存 简化数据传递
  3. 栈的效率和限制 由于栈的大小相对有限 将复杂且大小可变的数据移动到堆中 可以避免栈溢出
  4. 内存由运行时环境(如 JS 引擎的垃圾回收机制)管理 它能够自动处理内存的分配和回收 (复杂数据类型的生命周期往往不如简单数据类型那样容易预测)

10. 判断数组的方法?

11. v-if 和 v-show 的区别?

12. v-for 中 key 值的作用?

13. 在 vue 组件中 会设置不同的样式 怎么保证各个组件中样式不会相互污染呢?

14. scoped 防止组件之间样式的相互污染 这个原理是什么?

scoped 通过在编译时为组件内的每个元素添加唯一标识属性 data-v-xxxx,并修改 CSS 选择器使其包含这一属性 例如.class 变为[ data-v-xxx ].class 从而实现样式局部作用域 防止组件间样式的相互污染

15. vue 中初始的化没有监听某个数据 后面想再次监听 应该怎么做

16. 如果没有在 data 中进行声明这个对象 后面直接给他赋值 这个时候 watch 会监听到这个数据吗?

结果:如果你在组件的生命周期内动态添加了一个未在 data 选项中声明的属性,Vue 默认不会追踪这个属性的变化,因此 watch 也不会自动监听到这个新属性的改变。

17. 为什么不会监听到?

原因:因为 Vue 在组件初始化时会对 data 对象进行响应式处理,即通过 Object.defineProperty 方法将数据转换成响应式属性,这样就可以追踪数据的变化并触发视图的更新。对于在 data 之外动态添加的属性,由于它们在组件初始化时不存在,所以不会经过这一响应式处理过程。

this.$set(this, "newProperty", value);
Object.assign(this.$data, { newProperty: value }); // 使用 vm.$data 直接修改:

18. 事件循环

// 写出下方代码打印顺序 react
console.log("start");
setTimeout(() => {
  console.log("s1");
}, 0);

new Promise((resolve) => {
  console.log("p1");
  resolve();
}).then((v) => {
  console.log("t1");
  setTimeout(() => {
    console.log("s2");
  }, 0);
  new Promise((resolve) => {
    console.log("p2");
    resolve();
  }).then((v) => {
    console.log("t2");
  });
  console.log("t3");
  setTimeout(() => {
    console.log("s3");
  }, 0);
});
console.log("end");

16. TS 中 type 和 interface 的区别?

17. 地图添加 marker 称为撒点 渲染多个撒点 页面加载比较慢 性能优化有什么办法优化?(继续理解)

  1. 延迟加载:当用户滚动或拖动地图的时候 不会立即加载所有的撒点 只加载当前视口内的撒点

    // 假设你已经有了一个百度地图实例变量map
    var map = new BMap.Map("mapContainer");
    var markersData = [
      /* 你的撒点数据数组 */
    ];
    
    // 初始化地图(这里只是示例代码,你需要根据实际情况设置中心点、缩放级别等)
    map.centerAndZoom(new BMap.Point(116.404, 39.915), 11);
    
    // 标记集群(可选)
    var markerClusterer = new BMapLib.MarkerClusterer(map, []); // 初始化时传入空数组,稍后再添加标记
    
    // 加载当前视口内的撒点
    function loadMarkersInView() {
      var bounds = map.getBounds(); // 获取当前地图视口的边界框
      var markersToShow = []; // 存储要显示的标记
    
      // 遍历撒点数据,判断每个点是否在边界框内
      for (var i = 0; i < markersData.length; i++) {
        var point = new BMap.Point(markersData[i].lng, markersData[i].lat); // 假设你的数据包含lng和lat属性
        if (bounds.containsPoint(point)) {
          var marker = new BMap.Marker(point); // 创建标记
          // 设置标记的样式、事件等(可选)
          // ...
          markersToShow.push(marker);
          // 如果使用了标记集群,则添加到集群中
          // markerClusterer.addMarker(marker);
          map.addOverlay(marker); // 将标记添加到地图上
        }
      }
    
      // 如果使用了标记集群,则不需要单独添加标记到地图上
      // 注意:如果你使用了标记集群,请确保不要在事件处理函数外调用markerClusterer.addMarker(),而是在这里统一添加
    }
    
    // 监听地图移动结束事件
    map.addEventListener("moveend", loadMarkersInView);
    
    // 初始加载视口内的撒点
    loadMarkersInView();
    
  2. 使用标记群体:如果标记比较密集的时候 将多个标记组合成一个集群标记 当用户放大视图的时候 集群会分解标记

  3. webWorker:因为标记处理非常耗时 使用 webWorker 在后台线程中处理数据 避免页面卡顿

    // worker.js   创建webWork脚本
    self.onmessage = function(event) {
        // 假设我们接收到一个包含标记数据的数组
        var markersData = event.data.markersData;
    
        // 在这里处理你的标记数据
        // 例如,你可以进行过滤、转换或其他计算
    
        // 假设我们处理完数据后,将结果发送回主线程
        var processedData = /* 处理后的数据 */;
    
        // 发送数据回主线程
        self.postMessage(processedData);
    };
    // main.js
    // 创建一个新的 Worker 对象
    var worker = new Worker('worker.js');
    
    // 当 Worker 发送消息时
    worker.onmessage = function(event) {
        // 接收到的数据
        var processedData = event.data;
    
        // 在这里,你可以使用处理后的数据来更新你的地图标记
        // 例如,清除现有标记并添加新标记
    
        // 假设你有一个函数来更新地图标记
        updateMapMarkers(processedData);
    };
    
    // 假设这是你的原始标记数据
    var markersData = /* 你的标记数据 */;
    
    // 发送数据到 Worker
    worker.postMessage({ markersData: markersData });
    
    // 更新地图标记的函数(需要你自己实现)
    function updateMapMarkers(data) {
        // ... 实现更新地图标记的代码 ...
    }
    

18. 为什么选择前端?

二面

3. vue 里面数据展示和处理 他是怎么进行监听的?

响应式数据处理

  1. 数据劫持:
    1. Vue 使用 Object.defineProperty()方法来实现数据劫持。当 Vue 实例被创建时,它会递归地遍历 data 对象的所有属性,并使用 Object.defineProperty()将这些属性转化为 getter 和 setter。
    2. Getter 用于在访问属性时,Vue 可以检查该属性是否已经被监听,如果没有,则将其添加到监听列表中。
    3. Setter 则用于在修改属性时,Vue 能够捕获到这种变化,并执行相应的操作,如更新视图。
  2. 发布订阅模式
    1. Vue 采用发布订阅模式来处理数据的变化和视图的更新。当数据发生变化时,Vue 会触发一个事件,通知所有订阅了该数据的监听器。
    2. Vue 使用一个事件队列来维护所有的订阅者和事件处理函数。当数据发生变化时,Vue 会将变化事件添加到事件队列中,并逐个执行对应的处理函数。
  3. 计算属性
    1. Vue 的计算属性是一种基于它们的依赖进行缓存的属性。只有当它的相关依赖发生改变时才会重新求值。这使得计算属性非常适合进行复杂的数据处理或计算。
    2. 当计算属性的依赖发生变化时,Vue 会重新计算该属性,并触发相应的视图更新
  4. 侦听器 Watcher
    1. Vue 的侦听器(watchers)是一种更通用的方式来观察和响应 Vue 实例上的数据变化。你可以使用 watch 选项或 watch 方法来观察特定的数据变化,并执行相应的操作。
  5. Proxy vue3
    1. Proxy 可以代理整个对象,而不仅限于属性的劫持,同时也可以监控数组的变化。这使得 Vue 3 在处理数据变化时更加高效和灵活。

4. definePProperty 如果没有 proxy 如何实现数组和对象的监听?

对象:this.$set() 数组:原型链上的方法

5. vue 里面对 dom 事件大概是怎样的处理过程?

通过 v-on 或 @ 指令将 DOM 事件绑定到组件方法上,当事件被触发时,Vue 会自动调用相应的方法并执行其中的逻辑。如果方法内修改了组件的数据,Vue 的响应式系统会确保视图和数据保持同步更新。此外,Vue 还提供了事件修饰符来修改事件监听器的行为,并支持自定义事件用于组件间通信。

6. vue 中监听事件 这个事件怎么映射到浏览器原始的 dom 上的过程?

  1. 事件绑定:通过 v-on 指令(或简写为 @)在 Vue 组件的模板中绑定 DOM 事件到 Vue 实例的方法上。
  2. 编译阶段:Vue 的编译器(Compiler)在构建虚拟 DOM(Virtual DOM)的过程中,会识别这些事件绑定,并将它们转换为可以在浏览器 DOM 上实际监听的事件监听器。
  3. 添加事件监听器:当 Vue 实例挂载(mount)到真实的 DOM 元素上时,Vue 会遍历虚拟 DOM 中的事件绑定,并在对应的真实 DOM 元素上添加事件监听器。这些监听器会调用 Vue 实例中定义的方法。
  4. 事件触发:当用户在浏览器上触发 DOM 事件(如点击、输入等)时,浏览器会调用这些事件监听器。
  5. vue 方法执行:事件监听器被触发后,会调用 Vue 实例中对应的方法。这些方法可以访问 Vue 实例的数据、方法和其他属性,执行相应的逻辑。
  6. 响应式更新:如果 Vue 方法中修改了 Vue 实例的数据,Vue 的响应式系统会检测到这些变化,并自动更新视图,确保视图和数据保持一致。

7. vue-router 前端的路由都有哪些个事件发射?

  1. hashChange 事件(Hash 模式特有):
    1. 当使用 Hash 模式的前端路由时,路由的变化是通过监听 URL 中#(hash)部分的变化来实现的。因此,当 hash 值发生变化时,会触发 hashChange 事件。
    2. 可以通过监听 window 对象的 hashChange 事件来捕捉 hash 值的变化,并执行相应的操作。
  2. popstate 事件(History 模式特有):
    1. 当使用 History 模式的前端路由时,路由的变化是通过调用 history.pushState 或 history.replaceState 方法并监听 popstate 事件来实现的。
    2. popstate 事件在用户点击浏览器的前进或后退按钮时触发,或者在 JavaScript 代码中调用 history.back()、history.forward()或 history.go()方法时触发。
    3. 可以通过监听 window 对象的 popstate 事件来捕捉路由的变化,并执行相应的操作。
  3. vue-router 的导航守卫
    1. 全局前置守卫(beforeEach)、全局后置守卫(afterEach)、路由独享的守卫(beforeEnter)和组件内的守卫(beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave)等导航守卫。
    2. 这些守卫可以在路由跳转的不同阶段执行代码,如检查用户登录状态、获取数据等

8. 上传文件 如何读取文件?是直接读取全部的内容然后分片上传?

通过 slice 先切分文件 hash...

9. 断点上传的主要实现原理?

通过将大文件分割成多个小文件块进行分段传输,并在传输过程中记录已上传的分片信息。在上传中断时,客户端可以发送恢复上传请求,服务器根据已上传的分片信息继续接收剩余的分片,并最终将所有分片合并成完整的文件。同时,还需要实现错误处理和重试机制、校验和完整性验证以及进度显示等功能来提高上传的可靠性和效率。

  1. 文件分片:将大文件分割成多个较小的文件块(通常是固定大小的块)通过 JavaScript 的 Blob 对象或 File API 来实现,个文件块都有一个唯一的标识符,用于记录文件块的传输状态
  2. 上传请求:客户端发起上传请求,并将文件分片按顺序上传到服务器。、
  3. 上传状态记录:服务器端需要记录上传的状态,包括已接收的分片、分片的顺序和完整文件的大小等信息。这可以通过数据库、文件系统或其他存储机制来实现。
  4. 中断处理:如果上传过程中发生中断(例如网络中断、用户主动中止等),客户端可以记录已上传的分片信息,以便在恢复上传时使用。
  5. 恢复上传:
    1. 当上传中断后再次开始上传时,客户端可以发送恢复上传请求,并将已上传的分片信息发送给服务器
    2. 服务器接收到恢复上传请求后,根据已上传的分片信息,判断哪些分片已经上传,然后继续接收剩余的分片
  6. 文件合并:
    1. 当所有分片都上传完成后,服务器将所有分片按顺序组合成完整的文件。这通常涉及读取每个分片的内容,并将其写入一个新的文件中
  7. 校验和完整性验证:
    1. 使用校验和(如 MD5 或 SHA-1)来验证文件的完整性。在上传过程中,客户端可以计算每个分片的校验和,并在服务器端进行验证。如果校验和不匹配,说明文件在传输过程中可能发生了损坏或修改。
  8. 进度显示:在客户端显示上传进度。这可以通过监听上传过程中的事件(如进度事件)来实现,并将进度信息展示给用户。

10. this 指向有哪些?

11. 如果是严格模式(('use strict'))下 this 指向问题?

  1. 全局上下文中的 this:严格模式下全局上下文中的 this 不再是全局对象(window)。而是 undefined(原因:如果你在不属于任何对象的函数(即全局函数)中使用了 this,并且这个函数在严格模式下运行,那么 this 将是 undefined)

    // 全局
    'use strict';
    function globalFunction() {
        console.log(this); // undefined
    }
    globalFunction();
    // new
    'use strict';
    function Constructor() {
        console.log(this instanceof Constructor); // true
    }
    new Constructor();
    // 普通函数
    'use strict';
    function ordinaryFunction() {
        console.log(this); // undefined
    }
    ordinaryFunction();
    // 函数作为对象的方法调用
    'use strict';
      var obj = {
          method: function() {
              console.log(this === obj); // true
          }
      };
      obj.method();
    
  2. 构造函数和 new 操作符:this 仍然指向新创建的对象实例

  3. 函数作为普通函数调用:若函数内部的代码试图将 this 绑定到全局对象,严格模式会阻止这种行为,并使 this 的值为 undefined。

  4. 函数作为对象的方法调用:this 仍然指向调用该函数的对象

12. apply bind call 的区别?

13. 如果对一个函数执行了 bind 操作 改变为 this1 对这个返回的值又执行了一次 bind 更改为 bind2 返回了一个新的函数 那么最终 bind 会指向什么?

在 JavaScript 中,当你对一个函数使用.bind()方法时,你会创建一个新的函数,这个新函数在被调用时会有指定的 this 值,以及可选的预先设定的参数序列。如果你对已经绑定过的函数再次使用.bind(),每次都会返回一个新的函数,并且这个新函数的 this 值会是你最近一次.bind()调用时指定的值。

function originalFunction() {
  console.log(this.value);
}

var obj1 = { value: 'this1' };
var obj2 = { value: 'this2' };

// 第一次 bind,将 this 绑定到 obj1
var boundFunction1 = originalFunction.bind(obj1);

// 第二次 bind,将 this 绑定到 obj2(注意:这里不会改变 boundFunction1 的 this)
var boundFunction2 = boundFunction1.bind(obj2);

// 调用 boundFunction1,this 指向 obj1
boundFunction1(); // 输出 "this1"

// 调用 boundFunction2,this 指向 obj2(因为我们在第二次 bind 时指定了)
boundFunction2(); // 输出 "this2"

14. 小程序里面的 this 和前端 web 里面的 this 有什么不同?

  1. 小程序中的 this 和 Web 前端中的 this 主要区别在于上下文环境和作用域规则:小程序里的 this 通常指向当前页面或组件实例,便于访问数据和方法,在 Page 对象的方法或生命周期函数内部(如 onLoad、onShow、onReady 等),this 指向当前的 Page 对象,可以访问页面定义的 data、methods、properties 等。在自定义组件中,this 指向组件实例,可以访问组件的属性、方法等。
  2. Web 前端中的 this 行为更依赖于 JavaScript 的执行上下文,如在对象方法中指向对象,在事件处理器或构造函数中可能指向不同的实体

15. node 在文件处理里面 在读文件和写文件时 可能会直接 read 也可能是 readStream 流式的读取 那么流式的和直接读有什么区别? 这个流式的是个什么概念?

  1. 直接读取
    1. 过程:Node.js 会一次性将整个文件内容读入内存,然后你可以对该内容执行各种操作。
    2. 缺点:只适用于小文件 不适用于多文件和大文件,因为文件内容过大或过多 可能会占用大量内存,甚至超过 Node.js 进程可以分配的内存限制 导致内存不足
  2. 流式读取
    1. 使用场景:处理大文件或需要同时处理多个文件
    2. 过程:文件内容被分成小块(称为“数据块”或“块”),并逐个从文件中读取到内存中。然后,你可以对每个数据块执行所需的操作,并在处理完一个数据块后释放该数据块占用的内存,以便为下一个数据块腾出空间
    3. 优势:
      1. 允许你以较小的内存占用处理大文件。由于你一次只处理文件的一个小部分,因此你不需要将整个文件内容加载到内存中。
      2. 流式读取还允许你同时处理多个文件,因为你可以为每个文件创建一个单独的流,并在需要时释放每个流占用的内存。
      const fs = require("fs");
      const readableStream = fs.createReadStream("example.txt");
      readableStream.on("data", (chunk) => {
        console.log(chunk.toString()); // 处理数据块
      });
      readableStream.on("end", () => {
        console.log("文件读取完毕");
      });
      readableStream.on("error", (err) => {
        console.error(`读取文件时出错: ${err.message}`);
      });
    

16. http 协议 握手和加密的过程有了解吗?

tcp 连接+TLs 加密

17. 怎么验证密钥的有效性?

在整个过程中,如果任何一个步骤失败(如证书验证失败、密钥交换过程中出现问题等),TLS 握手过程就会失败,并且断开所有的连接,从而确保 TLS 中对称密钥的有效性。这个过程综合了密码学中的多种技术,如非对称加密、哈希函数、数字签名等,以确保通信的安全性和密钥的有效性。

  1. 握手开始:客户端向服务器发送支持的 TLS 版本、加密套件列表和随机数等。

  2. 服务器响应:服务器从客户端提供的加密套件列表中选择一个,并发送自己的公钥证书、一个随机数以及选择的加密套件给客户端。

  3. 客户端验证:客户端验证服务器发送的公钥证书的有效性,这通常包括检查证书链、证书颁发机构(CA)的有效性以及证书是否未被撤销等。

  4. 密钥交换:一旦服务器证书验证通过,客户端会生成一个对称密钥(即预主密钥),并使用服务器的公钥对这个密钥进行加密,然后发送给服务器。只有服务器能够使用其私钥解密这个密钥。

  5. 生成会话密钥:服务器解密出对称密钥后,双方使用之前交换的随机数和这个对称密钥,通过预定的密钥派生函数(KDF)生成会话密钥(即对称密钥)。

  6. 加密通信:在握手过程完成后,客户端和服务器使用生成的会话密钥对通信内容进行加密和解密,确保通信内容的安全性和机密性。

  7. webPack pagin 和 loader 有什么区别? Loaders 专注于文件的转换,而 Plugins 则关注于构建过程的扩展和自定义。 Loaders 处理文件级别上的转换,而 Plugins 则在更宏观的层面上影响整个构建过程。

    1. Loaders(加载器)
      1. Loaders 主要用于转换文件内容,使 Webpack 能够理解和处理不同类型的资源。例如,它们可以将 Sass 编译为 CSS,或将 TypeScript 编译为 JavaScript,甚至处理图片和字体文件。
      2. Loaders 是在 Webpack 解析模块依赖树的过程中被执行的,也就是说,当 Webpack 遇到一个需要加载的文件时,它会检查是否有适用的 loader 来处理该文件类型。
      3. Loaders 可以串联使用(链式加载),前一个 loader 的输出可以作为下一个 loader 的输入。
    2. Plugins(插件)
      1. Plugins 则更加灵活,它们可以扩展 Webpack 的功能,包括但不限于优化输出、实时编译、热模块替换、文件生成、环境变量注入等。
      2. Plugins 在整个 Webpack 的构建周期中都有机会介入,通过监听 Webpack 的特定事件或钩子(hooks)来触发自己的功能。
      3. Plugins 可以访问 Webpack 的内部 API,这意味着它们可以修改和控制 Webpack 的构建流程,甚至改变最终的输出结果。
  8. 有些过一些 webpack 插件之类的东西吗? 比如说 ac 创客 概念之类的东西?

  9. webPack 的整个文件处理 怎么跟这些 loader 进行交互的?

    1. 模块解析: 当 Webpack 开始构建过程时,从一个或多个入口点(entry points)开始解析。每个入口点是一个模块,Webpack 会递归地查找并解析这个模块所依赖的所有其他模块。
    2. Loader 调用: 在解析过程中,每当遇到一个非 JavaScript 或 JSON 文件时,Webpack 会检查配置文件(通常是 webpack.config.js)中是否定义了相应的 Loader 来处理该类型的文件。如果找到匹配的 Loader,Webpack 将调用这个 Loader 并传递文件内容给它。
    3. Loader 执行顺序: 如果配置了多个 Loader,它们会按照从右到左、从下到上的顺序执行。及最后一个定义的 Loader 最先运行,而第一个定义的 Loader 最后运行。确保文件内容在被最终处理之前能被多个 Loader 依次转换。
    4. Loader 处理文件: Loader 收到文件内容后,可以对其进行任何必要的转换。
      1. babel-loader 可以将 ES6 代码转换为浏览器可识别的 ES5 代码
      2. css-loader 可以处理 CSS 文件中的导入语句
      3. file-loader 或 url-loader 可以处理图片和字体文件,将其嵌入到输出文件中或生成单独的文件。
    5. Loader 返回结果: Loader 完成转换后会将结果返回给 Webpack。这个返回的内容会被视为一个新的模块,然后 Webpack 继续解析这个新模块的依赖关系。
    6. 循环处理: Webpack 会继续解析和处理新生成的模块,直到所有的依赖都被解析和转换完成。这可能涉及到多次调用 Loader 来处理不同类型的文件。
    7. 打包输出: 一旦所有模块及其依赖都被处理完毕,Webpack 会将它们打包成一个或多个输出文件(chunks),这些文件可以是 JavaScript、CSS 或其他类型的资源,具体取决于配置。

21. webPack 的热更新 刷新的时候修改代码 不需要刷新页面就能把改动推送到打开的页面里?

22. 防抖和节流有什么区别 使用场景?

23. 【手撕代码】 实现防抖