前端面试题详解整理61|移动端适配 减少l,css选择器优先级 强缓存和协商缓存 .输入url到页面渲染过程,路由懒加载,vite和webpack是如何选型,

58 阅读15分钟

03-06 13:42门头沟学院 工学

关注

百度健康春招前端一面

1.介绍一下项目

2.移动端适配

3.css选择器优先级

移动端适配是指将网页或应用程序适配到移动设备(如手机、平板电脑)的屏幕尺寸和分辨率,以提供更好的用户体验。以下是一些常用的移动端适配方法:

  1. 响应式设计(Responsive Design)

    • 使用 CSS3 媒体查询(Media Queries)和弹性布局(Flexbox)等技术,根据设备的屏幕尺寸和分辨率动态调整页面布局和样式,以适应不同大小的屏幕。
  2. 视口设置(Viewport)

    • 使用 <meta> 标签设置视口(viewport),指定页面在移动设备上的显示尺寸和缩放比例,以确保页面在移动设备上正常显示。
  3. REM 或 EM 单位

    • 使用相对单位(如 REM 或 EM)代替固定单位(如 PX),以便根据设备的字体大小和分辨率进行自适应布局,从而实现移动端适配。
  4. Flexbox 布局

    • 使用 Flexbox 布局来实现页面的自适应和灵活布局,使页面在不同尺寸的屏幕上均能正确显示,并具有良好的可伸缩性。
  5. CSS 媒体查询

    • 使用 CSS3 媒体查询来针对不同的设备尺寸和分辨率应用不同的样式,以适配不同大小的屏幕和设备。
  6. 移动端布局框架

    • 使用现成的移动端布局框架(如 Bootstrap、Ant Design Mobile 等)来快速搭建适配移动端的网页和应用程序,提高开发效率。
  7. 设备像素比(Device Pixel Ratio,DPR)

    • 根据设备的像素比(DPR)调整页面元素的大小和样式,以适配高分辨率的移动设备,提高页面显示的清晰度和质量。

综合使用以上方法,可以实现有效的移动端适配,提供良好的用户体验和页面显示效果。 CSS 选择器优先级是确定样式应用的重要规则,它决定了当多个规则同时匹配同一个元素时,哪个样式将被应用到元素上。优先级是通过对选择器进行权重计算来确定的,权重高的规则具有更高的优先级。

CSS 选择器优先级由四个部分组成,按重要性递减的顺序是:

  1. 行内样式(Inline Styles):在元素的 style 属性中直接指定的样式具有最高的优先级,权重为 1000。

  2. ID 选择器(ID Selectors):通过元素的 id 属性选择元素的样式,权重为 100。

  3. 类选择器、属性选择器和伪类选择器(Class Selectors, Attribute Selectors, Pseudo-Class Selectors):包括类选择器(.class)、属性选择器([attribute])和伪类选择器(:hover、:first-child 等),权重为 10。

  4. 元素选择器和伪元素选择器(Element Selectors, Pseudo-Element Selectors):包括元素选择器(如 p、div 等)和伪元素选择器(::before、::after 等),权重为 1。

如果有多个规则应用到同一个元素上,CSS 解析引擎会根据这些规则的权重来确定最终应用的样式。具体的规则是:

  • 样式优先级越高的规则优先被应用。
  • 如果权重相同,则后声明的规则会覆盖前面的规则。
  • 如果权重和声明顺序都相同,则谁在后声明就会覆盖前面的样式。

例如,行内样式会覆盖外部样式表中的样式,而带有 ID 选择器的样式会覆盖类选择器和元素选择器的样式等。

综上所述,了解 CSS 选择器优先级对于正确理解和应用样式非常重要,可以避免样式冲突和提高样式的可维护性。

4.有没有做过性能优化

5.路由懒加载如何实现的

路由懒加载是一种优化前端性能的技术,它可以在需要时按需加载页面组件,而不是一次性加载所有页面组件,从而减少初始加载时间和网络带宽的消耗。下面是实现路由懒加载的一般步骤:

  1. 使用动态导入(Dynamic Import)

    • 路由懒加载通常使用 JavaScript 的动态导入语法(import())来实现。在路由配置中,不直接导入组件,而是使用动态导入语法导入组件模块。
  2. 将组件定义为函数

    • 将需要延迟加载的路由组件定义为一个返回动态导入模块的函数。这个函数在需要加载组件时被调用,返回一个 Promise,当 Promise 被解析时,会加载并返回组件模块。
  3. Webpack 或其他打包工具支持

    • 确保你的打包工具(如 Webpack)支持动态导入功能,并且配置正确。通常情况下,Webpack 会自动将动态导入的模块拆分为单独的代码块(chunk),在需要时按需加载。

下面是一个简单的示例,演示了如何使用 Vue Router 实现路由懒加载:

// 路由配置
const router = new VueRouter({
  routes: [
    {
      path: '/about',
      // 将组件定义为函数,返回动态导入模块
      component: () => import('./views/About.vue')
    },
    {
      path: '/contact',
      // 将组件定义为函数,返回动态导入模块
      component: () => import('./views/Contact.vue')
    }
  ]
})

在上述代码中,import('./views/About.vue')import('./views/Contact.vue') 使用了动态导入语法,将组件定义为返回动态导入模块的函数,实现了路由懒加载的效果。

使用路由懒加载可以有效地减少初始加载时间和页面大小,提高页面的性能和用户体验。

6.vite和webpack你是如何如何选型的

选择使用 Vite 还是 Webpack 取决于项目的需求和特点,以及开发团队的技术栈和偏好。下面是一些考虑因素:

  1. 项目类型

    • 对于传统的大型单页面应用(SPA)或多页面应用(MPA),Webpack 是一个更成熟和稳定的选择,因为它有更多的功能和生态支持。
    • 对于轻量级的项目、快速原型开发或小型应用,Vite 提供了更快的开发环境和更快的热更新速度。
  2. 开发体验

    • Vite 提供了即时编译(Instant Server Start)和快速热更新等功能,使开发者可以享受到更快的开发体验,特别是在开发阶段和调试阶段。
    • Webpack 在构建大型项目时可能会有一定的构建时间,但它提供了更丰富的插件和 loader 生态系统,可以满足更多复杂的需求。
  3. 生态系统

    • Webpack 有一个庞大的生态系统,拥有大量的插件和 loader,可以满足各种不同的需求,例如代码拆分、优化、压缩、模块热替换等。
    • Vite 是一个相对较新的工具,其生态系统可能相对较小,但正在迅速发展,社区也在不断壮大,已经拥有了一些核心插件和工具。
  4. 构建性能

    • Vite 使用了现代的 ES 模块原生支持和基于浏览器原生模块系统的构建方式,因此具有非常快的构建速度,尤其是在开发环境和热更新方面。
    • Webpack 的构建速度可能会比较慢,尤其是在大型项目中,但通过一些优化和配置,可以提升构建性能。
  5. 技术栈和社区支持

    • 如果团队已经熟悉了 Webpack,并且项目对 Webpack 生态系统的某些功能有较高的依赖,那么选择继续使用 Webpack 可能会更加顺畅。
    • 如果团队对现代工具链和 JavaScript 生态系统有浓厚的兴趣,并且希望尝试新的技术和工具,那么选择使用 Vite 可能会更加有趣。

综上所述,选择使用 Vite 还是 Webpack 主要取决于项目的需求、团队的技术栈和开发体验的偏好。可以根据具体情况权衡利弊,选择适合自己项目的构建工具。

7.输入url到页面渲染过程(绘制的过程了解吗)

了解 URL 到页面渲染的过程是前端开发中的基础知识之一。以下是一般情况下的整个过程:

  1. DNS 解析

    • 当用户在浏览器地址栏中输入 URL 后,浏览器会先进行 DNS 解析,将域名解析成对应的 IP 地址。
    • 如果 DNS 缓存中存在对应的 IP 地址,则直接使用缓存,否则会向 DNS 服务器发送 DNS 查询请求。
  2. 建立 TCP 连接

    • 浏览器通过 IP 地址与服务器建立 TCP 连接,该过程包括 TCP 的三次握手,确保客户端和服务器之间的连接建立成功。
  3. 发送 HTTP 请求

    • 连接建立成功后,浏览器向服务器发送 HTTP 请求,请求页面资源,包括 HTML、CSS、JavaScript 文件等。
    • 请求过程中会包含请求头、请求体等信息。
  4. 服务器处理请求

    • 服务器接收到浏览器发送的请求后,会根据请求的 URL 和请求头中的信息进行相应的处理,包括查询数据库、执行业务逻辑等。
  5. 返回 HTTP 响应

    • 服务器处理完成后,会返回 HTTP 响应给浏览器,响应包括响应状态码、响应头、响应体等信息。
    • 响应体中通常包含请求的页面内容以及相关资源(如图片、样式表、脚本文件等)的链接。
  6. 浏览器解析 HTML

    • 浏览器接收到响应后,开始解析 HTML 内容,构建 DOM 树。
    • 在解析 HTML 过程中,如果遇到外部资源(如 CSS、JavaScript 文件等),会向服务器发送新的请求,继续加载这些资源。
  7. 渲染页面

    • 浏览器根据 DOM 树和 CSS 样式表计算出页面的渲染树(Render Tree)。
    • 渲染树确定了页面中每个节点的位置和样式,并将其显示在浏览器窗口中,完成页面渲染的过程。
  8. 执行 JavaScript

    • 如果 HTML 中包含了 JavaScript 代码,浏览器会执行 JavaScript 代码,可能会修改页面的 DOM 结构或样式,进而触发页面重新渲染。
  9. 绘制页面

    • 浏览器根据渲染树和 JavaScript 的执行结果,将页面内容绘制到屏幕上,完成页面的绘制和显示。

以上是从输入 URL 到页面渲染完成的整个过程,其中涉及了 DNS 解析、建立 TCP 连接、发送 HTTP 请求、服务器处理请求、浏览器解析 HTML、渲染页面等多个步骤。

8.强缓存和协商缓存(301和302状态码是什么)

强缓存和协商缓存是 HTTP 缓存机制的两种实现方式,它们分别通过不同的方式控制缓存的有效性和更新机制。

  1. 强缓存

    • 当客户端发起请求时,会先检查本地缓存是否存在该资源以及缓存是否过期。
    • 如果缓存未过期,则直接从本地缓存中加载资源,不会向服务器发起请求,节省了网络带宽和服务器资源。
    • 如果缓存过期,则会向服务器发起请求,服务器会返回状态码为 200(OK),并在响应头中设置缓存控制相关的头部字段(如 Expires、Cache-Control)来指示客户端缓存该资源。
  2. 协商缓存

    • 当客户端发起请求时,会先向服务器发送一个条件请求,询问服务器是否可以使用缓存。
    • 如果服务器认为客户端的缓存仍然有效,则返回状态码为 304(Not Modified),并在响应头中设置缓存控制相关的头部字段(如 Last-Modified、ETag)来指示客户端使用缓存。
    • 如果服务器认为客户端的缓存已经失效,则返回状态码为 200(OK),并返回新的资源内容。
  3. 301 和 302 状态码

    • 301 永久重定向:表示请求的资源已被永久移动到新的 URL,客户端应该更新所有引用该资源的 URL。在浏览器中,会自动跳转到新的 URL。
    • 302 临时重定向:表示请求的资源临时移动到了新的 URL,客户端应该继续使用原始 URL。在浏览器中,会暂时跳转到新的 URL,但不会更新地址栏。

这两种状态码主要用于实现 URL 重定向功能,其中 301 是永久性的重定向,而 302 是临时性的重定向。在使用时需要根据具体的业务需求和情境选择合适的状态码。

9.了解性能指标吗,如何减少lcp

了解性能指标对于前端开发非常重要,其中 LCP(Largest Contentful Paint)是页面加载过程中的一个关键性能指标,表示最大内容渲染时间,即页面上最大的可见内容元素渲染完成所需的时间。优化 LCP 可以提升用户体验和页面加载速度。

以下是一些减少 LCP 的方法:

  1. 优化图片加载:图片通常是页面上最大的可见内容元素之一,因此优化图片加载对于减少 LCP 很重要。可以通过使用适当的图片格式(如 WebP)、懒加载、预加载以及使用正确尺寸的图片等方式来优化图片加载。

  2. 使用字体的最佳实践:字体的加载也可能影响到 LCP。确保使用的字体文件大小适中,并使用字体的最佳加载方式(如使用字体的本地备份、font-display 属性等),以尽量减少字体的加载时间。

  3. 减少关键渲染路径上的阻塞资源:通过减少页面加载过程中的阻塞资源(如 CSS 和 JavaScript 文件)的数量和大小,可以加快页面的渲染速度,从而减少 LCP。

  4. 优化服务器响应时间:优化服务器响应时间可以减少页面加载时间,从而降低 LCP。可以通过优化服务器端代码、使用 CDN 加速、启用 HTTP/2 等方式来减少服务器响应时间。

  5. 减少渲染阻塞元素的数量和大小:在页面加载过程中,避免渲染阻塞元素(如 JavaScript 和 CSS 文件、未优化的图片等)的数量和大小,以尽量减少页面的渲染时间。

  6. 使用预渲染技术:对于一些静态页面或者不常变化的页面,可以使用预渲染技术将页面在构建时已经渲染好,从而减少页面加载时间,降低 LCP。

通过以上方法,可以有效减少 LCP,提升页面加载速度和用户体验。

实现a + b === c兼容小数计算

要实现 a + b === c 兼容小数计算,可以使用 JavaScript 中的浮点数运算,并进行适当的精度控制。下面是一个简单的实现:

function floatAdd(a, b) {
    const precision = Math.max(getDecimalLength(a), getDecimalLength(b));
    const multiple = Math.pow(10, precision);
    return (a * multiple + b * multiple) / multiple;
}

function getDecimalLength(num) {
    // 将数字转换为字符串,以便处理小数点
    const numStr = String(num);
    const decimalIndex = numStr.indexOf('.');
    if (decimalIndex !== -1) {
        // 如果存在小数点,则返回小数点后的位数
        return numStr.length - decimalIndex - 1;
    } else {
        // 如果不存在小数点,则返回 0
        return 0;
    }
}

// 测试
const a = 0.1;
const b = 0.2;
const c = 0.3;

console.log(floatAdd(a, b) === c); // 输出 true

上述代码中,floatAdd 函数用于进行浮点数相加,其中通过 getDecimalLength 函数获取浮点数的小数位数,并根据最大小数位数进行精度控制,以确保计算结果准确。

实现一个EventEmitter类

下面是一个简单的 EventEmitter 类的实现,包含了 onoffemit 方法,并在代码中添加了详细的注释说明:

class EventEmitter {
    constructor() {
        // 使用对象存储事件及对应的回调函数
        this.events = {};
    }

    // 订阅事件
    on(event, callback) {
        // 如果事件不存在,则创建一个空数组
        if (!this.events[event]) {
            this.events[event] = [];
        }
        // 将回调函数添加到对应事件的数组中
        this.events[event].push(callback);
    }

    // 取消订阅事件
    off(event, callback) {
        // 如果事件不存在,则直接返回
        if (!this.events[event]) {
            return;
        }
        // 如果没有传入回调函数,则移除该事件的所有回调函数
        if (!callback) {
            delete this.events[event];
            return;
        }
        // 移除指定的回调函数
        this.events[event] = this.events[event].filter(cb => cb !== callback);
    }

    // 触发事件
    emit(event, ...args) {
        // 如果事件不存在,则直接返回
        if (!this.events[event]) {
            return;
        }
        // 遍历事件的所有回调函数,并依次执行
        this.events[event].forEach(callback => callback.apply(this, args));
    }
}

使用示例:

// 创建一个 EventEmitter 实例
const emitter = new EventEmitter();

// 定义一个事件回调函数
const callback1 = (data) => {
    console.log('Event 1:', data);
};

// 订阅事件
emitter.on('event1', callback1);

// 触发事件
emitter.emit('event1', 'Hello World');

// 取消订阅事件
emitter.off('event1', callback1);

// 再次触发事件,但因为已经取消订阅,所以不会有输出
emitter.emit('event1', 'Hello World');

以上代码实现了一个简单的 EventEmitter 类,可以用于在 JavaScript 中实现自定义事件的订阅、取消订阅和触发功能。