前端面试题详解整理16|虚拟dom的理解 ,TCP三次握手 ,如何登录 ,tree shaking原理,vite 打包和webpack区别,为什么vite构建

98 阅读11分钟

秋招--腾讯金融科技前端一面(凉凉)

七点面试,时间一个小时多。#腾讯前端面试#我太菜了,哭了,好多问题都没答上来,鹅子就是不一样!

1. 先手撕三个题、flat(指定深度)、Promise.all、写一个异步任务队列,可以指定同时指定的队列长度

下面是经过详细注释的代码示例:

1. 深度控制的数组扁平化函数:

function flat(arr, depth = 1) {
    return arr.reduce((acc, val) => {
        if (Array.isArray(val) && depth > 0) { // 检查当前元素是否为数组且深度仍可控制
            return acc.concat(flat(val, depth - 1)); // 递归调用 flat 函数,深度减一
        } else {
            return acc.concat(val); // 将当前元素添加到结果数组中
        }
    }, []);
}

// 示例
const arr = [1, [2, [3, [4, 5]]]];
console.log(flat(arr)); // [1, 2, [3, [4, 5]]]
console.log(flat(arr, 1)); // [1, 2, [3, [4, 5]]]
console.log(flat(arr, 2)); // [1, 2, 3, [4, 5]]
console.log(flat(arr, 3)); // [1, 2, 3, 4, 5]

2. Promise.all 的手动实现:

function myPromiseAll(promises) {
    return new Promise((resolve, reject) => {
        if (!Array.isArray(promises)) {
            return reject(new TypeError('Argument must be an array')); // 非数组类型的参数将被拒绝
        }
        
        const results = [];
        let completed = 0;
        
        promises.forEach((promise, index) => {
            Promise.resolve(promise).then((result) => {
                results[index] = result; // 将每个 Promise 的结果存入结果数组中
                completed++;
                
                if (completed === promises.length) { // 当所有任务都完成时,返回结果数组
                    resolve(results);
                }
            }).catch(reject); // 如果任何一个 Promise 失败,整个 Promise.all 将被拒绝
        });
    });
}

// 示例
const promise1 = Promise.resolve(1);
const promise2 = new Promise((resolve) => setTimeout(() => resolve(2), 1000));
const promise3 = new Promise((resolve) => setTimeout(() => resolve(3), 2000));

myPromiseAll([promise1, promise2, promise3]).then((results) => {
    console.log(results); // [1, 2, 3] after 2 seconds
});

3. 异步任务队列,可以指定队列长度的实现:

class AsyncQueue {
    constructor(maxLength = Infinity) {
        this.maxLength = maxLength; // 初始化队列最大长度,默认为无穷大
        this.queue = []; // 任务队列
        this.running = false; // 是否正在执行任务
    }
    
    addTask(task) {
        if (this.queue.length < this.maxLength) { // 检查队列长度是否已达到最大值
            this.queue.push(task); // 将任务添加到队列中
            if (!this.running) { // 如果队列当前为空闲状态,则开始执行任务
                this.runNextTask();
            }
        } else {
            console.log('Queue is full'); // 如果队列已满,打印提示信息
        }
    }
    
    async runNextTask() {
        if (this.queue.length > 0) { // 检查是否还有任务需要执行
            this.running = true; // 设置当前正在执行任务的状态为 true
            const task = this.queue.shift(); // 从队列中取出首个任务
            try {
                await task(); // 等待任务执行完成
            } catch (error) {
                console.error('Task error:', error); // 如果任务执行出错,打印错误信息
            } finally {
                this.running = false; // 任务执行完毕后,将当前任务执行状态设为 false
                this.runNextTask(); // 继续执行下一个任务
            }
        }
    }
}

// 示例
const asyncQueue = new AsyncQueue(2); // 最多同时执行两个任务

asyncQueue.addTask(async () => {
    console.log('Task 1 start');
    await new Promise((resolve) => setTimeout(resolve, 1000));
    console.log('Task 1 end');
});

asyncQueue.addTask(async () => {
    console.log('Task 2 start');
    await new Promise((resolve) => setTimeout(resolve, 1500));
    console.log('Task 2 end');
});

asyncQueue.addTask(async () => {
    console.log('Task 3 start');
    await new Promise((resolve) => setTimeout(resolve, 500));
    console.log('Task 3 end');
});

以上代码中添加了详细的注释,以便更好地理解每个部分的功能和实现原理。

3. vite 打包和webpack区别,为什么vite构建更快一些

Vite 和 Webpack 是两种不同的前端构建工具,它们在设计和实现上有一些显著的区别,其中最明显的区别之一就是构建性能。

  1. 打包方式:

    • Webpack: Webpack 是一种传统的构建工具,它使用的是单入口模式,即从一个入口文件开始,递归地构建整个应用程序的依赖关系树,然后将所有的模块打包到一个或多个输出文件中。
    • Vite: Vite 则是一种基于 ES Module 的构建工具,它利用浏览器原生的 ES Module 支持,通过使用现代浏览器提供的原生 ES Module 动态导入功能,以及在开发环境下使用原生 ES Module 作为构建产物,从而实现了即时编译的效果,无需将所有代码打包成一个或多个输出文件。
  2. 构建速度:

    • Webpack: Webpack 的构建速度受到多个因素的影响,包括项目规模、配置复杂度、模块解析等。通常情况下,Webpack 在大型项目中的构建速度较慢,尤其是在开发环境下,因为需要重新构建整个依赖图。
    • Vite: Vite 的构建速度相对较快,特别是在开发环境下,因为它利用了现代浏览器的原生 ES Module 支持,无需将所有代码打包成一个或多个输出文件,而是按需编译和传输,仅需要重新编译发生变化的模块,因此可以实现秒级的热更新。
  3. 原理差异:

    • Webpack: Webpack 在开发环境下通常使用热模块替换(Hot Module Replacement,HMR)来实现热更新,但仍然需要将所有模块打包到一个输出文件中,并在每次修改时重新编译整个文件。
    • Vite: Vite 利用浏览器原生的 ES Module 动态导入和热更新机制,无需打包,而是直接在浏览器中运行原始的 ES Module 代码,并在需要时动态加载依赖项。这种即时编译和传输的方式使得 Vite 在开发环境下能够实现更快的构建速度。

综上所述,Vite 在构建速度上比 Webpack 更快,主要是由于其基于现代浏览器原生的 ES Module 支持,以及采用即时编译和传输的方式,无需打包整个应用程序。

5. webpack tree shaking原理

Webpack 的 Tree Shaking 是一种优化技术,用于移除 JavaScript 代码中未被使用的部分,以减少最终打包输出的文件大小。

其原理主要基于 ES6 模块的静态结构特性和标记未使用的模块变量,具体包括以下几个步骤:

  1. 静态分析: Tree Shaking 的第一步是对代码进行静态分析,以确定哪些模块和变量被引入到了代码中。

  2. 标记依赖: Webpack 在打包过程中会遍历整个模块依赖图,并标记出每个模块导出的变量以及被引入到的变量。

  3. 识别未使用的变量: 通过分析代码的引入和导出关系,Webpack 能够识别出哪些变量在代码中没有被使用,即未被引用。

  4. 移除未使用的变量: 在打包过程的优化阶段,Webpack 会根据标记的信息,从打包输出中移除未被使用的模块和变量,以减少输出文件的体积。

需要注意的是,Tree Shaking 只对 ES6 模块(静态导入/导出)有效,因为它们具有静态结构,可以在编译时进行静态分析。对于 CommonJS 模块(require/exports),由于它们的导入和导出是动态的,无法进行静态分析,因此无法有效地进行 Tree Shaking。

在配置 Webpack 时,需要确保开启了 ES6 模块语法的支持,并且使用了压缩工具(如 UglifyJS、Terser)来进一步优化输出的文件大小。另外,如果使用的是第三方库,需要确保这些库支持 ES6 模块,并且已经进行了 Tree Shaking 优化。

7. 有没有读过react源码
8. 微信小程序如何登录

在微信小程序中,登录流程通常涉及以下步骤:

  1. 用户授权登录: 首先,小程序需要获取用户的授权,以获取用户的基本信息。通常情况下,开发者可以通过调用 wx.login() 方法触发用户登录操作,并在成功后获取到用户的临时登录凭证(code)。

  2. 获取临时登录凭证: 使用 wx.login() 方法成功后,会返回一个临时登录凭证(code)。开发者需要将这个临时登录凭证发送给服务器端,以便服务器端可以根据该凭证获取用户的唯一标识和会话密钥。

  3. 服务端登录验证: 小程序开发者通常需要搭建一个后端服务,该服务用于接收并处理小程序传递的临时登录凭证,然后向微信服务器发送请求,获取用户的唯一标识(OpenID)和会话密钥(Session Key)。

  4. 获取用户信息: 在获得用户的唯一标识和会话密钥后,开发者可以通过调用 wx.getUserInfo() 方法获取用户的基本信息,包括昵称、头像等。

  5. 登录状态维护: 一般情况下,开发者可以将用户的唯一标识和会话密钥保存在服务器端,用于维护用户的登录状态。在后续用户访问时,可以根据用户的唯一标识判断用户的登录状态,并进行相应的操作。

需要注意的是,微信小程序的登录流程涉及到前端和后端的交互,其中后端的登录验证逻辑由开发者自行实现。在实际开发中,开发者通常会采用微信提供的登录凭证验证接口(如 code2Session 接口)来进行登录验证。

10. TCP三次握手

TCP三次握手是建立TCP连接时使用的一种协议,用于确保通信双方都能够收发数据。下面是TCP三次握手的详细步骤:

  1. 客户端发送SYN报文: 客户端首先向服务器发送一个SYN(同步)报文,其中包含客户端的初始序列号(ISN)。SYN报文的目的是告诉服务器客户端准备好发送数据,并且客户端生成的序列号。

  2. 服务器响应SYN报文并发送ACK报文: 服务器收到客户端的SYN报文后,会发送一个ACK(确认)报文作为响应,并且也会包含自己的初始序列号(ISN)。服务器还会在这个ACK报文中确认收到客户端的SYN报文,因此ACK报文中会包含客户端的序列号加一(ACK=客户端的序列号+1)。此时,服务器进入SYN_RECEIVED状态。

  3. 客户端发送ACK报文: 客户端收到服务器的ACK报文后,会再次向服务器发送一个ACK报文。这个ACK报文中会确认收到服务器的SYN报文,并且也会包含服务器的序列号加一(ACK=服务器的序列号+1)。此时,客户端和服务器都进入ESTABLISHED状态,TCP连接建立完成。

这样,通过三次握手,客户端和服务器建立了可靠的TCP连接,可以进行数据的双向传输。三次握手的目的是为了确保通信双方都能够正常收发数据,并且防止已失效的连接请求报文段突然又传送到了服务端,从而产生错误。

12. 虚拟dom的理解

虚拟DOM(Virtual DOM)是一种用JavaScript对象表示真实DOM结构的技术。它是许多现代JavaScript框架(如React、Vue.js等)中的核心概念之一。下面是对虚拟DOM的理解:

  1. 概念: 虚拟DOM是一个虚拟的、内存中的DOM表示,它是真实DOM的轻量级映射。通过使用虚拟DOM,框架可以在内存中维护一颗DOM树,然后在适当的时候将其与真实的DOM进行比较和同步。

  2. 工作原理: 当应用程序状态发生变化时,框架会使用虚拟DOM来重新渲染整个组件树。这个过程包括创建一个新的虚拟DOM树,然后将其与之前的虚拟DOM树进行比较。通过比较,框架可以找到需要更新的部分,并且只对这些部分进行真实DOM的更新操作。

  3. 优势

    • 性能优化:虚拟DOM可以帮助框架减少对真实DOM的操作次数,从而提高页面渲染的性能。框架可以批量更新真实DOM,减少重排和重绘的次数。
    • 跨平台兼容性:虚拟DOM的概念使得框架可以更容易地在不同的平台上运行,包括浏览器、服务器端渲染等。
    • 简化复杂度:使用虚拟DOM可以将复杂的DOM操作抽象为简单的JavaScript对象操作,使得代码更易于理解和维护。
  4. 实现方式: 虚拟DOM通常是一个树形结构的JavaScript对象,它的节点包含了真实DOM节点的信息,如标签名、属性、文本内容等。框架会提供一组API来创建、更新和比较虚拟DOM,以及将虚拟DOM映射到真实DOM。

  5. 框架应用: 许多现代JavaScript框架,如React和Vue.js,都使用了虚拟DOM技术。在这些框架中,开发者通常不直接操作真实DOM,而是通过操作虚拟DOM来管理页面的状态和UI更新。框架会负责将虚拟DOM转换为真实DOM,并且进行高效的更新操作。

总之,虚拟DOM是一种用于优化页面渲染性能和简化复杂度的技术,它通过在内存中维护一个轻量级的DOM表示,使得页面的更新操作更加高效和可控。

14. AI?剩下记不得了,记忆力越来越差了
问项目

作者:又是一只小菜鸟
链接:www.nowcoder.com/feed/main/d…
来源:牛客网