前端面试题详解整理85| 嵌套结构的对象的key从_分割改为HTTP 1.1 / 2 / 3  Vue2 / Vue3  响应式系统 ,DIFF算法,缓存,

93 阅读15分钟

阿里飞猪前端

一面:

Summary:难度简单,基础八股 + 项目。

1. 实习技术产出

2. Vue2 / Vue3  响应式系统

Vue2和Vue3的响应式系统在设计和实现上有一些区别,下面是它们的主要特点和区别:

Vue2的响应式系统

  1. 基于Object.defineProperty:Vue2的响应式系统是基于Object.defineProperty实现的。
  2. 递归遍历:在Vue2中,当数据对象被传入Vue实例中时,Vue会遍历对象的所有属性,并使用Object.defineProperty对每个属性进行劫持,从而实现数据的响应式。
  3. 性能优化限制:Vue2的响应式系统在处理大量数据时可能会存在性能问题,因为它需要递归遍历整个对象,劫持每个属性,而且对于数组的变化检测较为复杂。

Vue3的响应式系统

  1. 基于Proxy:Vue3的响应式系统使用了ES6的Proxy对象来实现响应式,相比Vue2的Object.defineProperty,Proxy提供了更灵活和强大的拦截功能。
  2. 懒代理:Vue3的Proxy在对象上创建了一个懒代理,只有当访问对象的属性时才会触发代理对象的拦截器,这种方式避免了Vue2中递归遍历对象的性能问题。
  3. 支持Map和Set:Vue3的响应式系统支持Map和Set等原生JavaScript对象,而Vue2只能劫持普通的对象和数组。
  4. 更好的类型推断:由于使用了Proxy,Vue3可以更准确地推断对象的类型,提供更好的类型支持和错误提示。

总的来说,Vue3的响应式系统相比Vue2更加高效和灵活,使用了Proxy对象来实现响应式,避免了Vue2中存在的一些性能问题,并提供了更好的类型推断和支持。

4. DIFF算法 复杂度分析

DIFF算法是一种用于比较两个树结构(通常是虚拟DOM树)的算法,以便找出两个树之间的差异,并在UI更新时只更新需要更新的部分,从而提高性能。DIFF算法的复杂度分析取决于其具体实现方式,一般来说,它的时间复杂度为O(n^3),其中n是树中节点的数量。

DIFF算法的一种简单实现是递归地比较两棵树的每个节点,并根据节点的类型和属性进行比较,以确定节点是否需要更新。这种实现方式的时间复杂度取决于树的深度和每个节点的子节点数量。在最坏的情况下,需要比较所有节点,因此时间复杂度为O(n^2)。

为了提高DIFF算法的性能,可以采用一些优化策略,如添加节点的唯一标识符、使用虚拟DOM、采用哈希表等。这些优化可以降低比较的复杂度,并减少不必要的比较操作。通过合理设计DIFF算法和采用优化策略,可以在保证性能的同时实现高效的UI更新。

5. HTTP缓存 

HTTP缓存是通过在客户端(浏览器)和服务器之间缓存资源的副本来提高Web性能和减少网络流量的一种机制。HTTP缓存可以在客户端、服务器或两者之间的任何地方实现。它可以减少网络传输,减少服务器负载,并改善用户体验。

HTTP缓存可以分为两种类型:强缓存和协商缓存。

  1. 强缓存

    • 强缓存是指客户端在请求资源时,直接从本地缓存中获取资源,而不需要再向服务器发起请求。
    • 通过设置Cache-ControlExpires响应头来控制资源的强缓存。
    • Cache-Control中的max-age指定了资源在本地缓存中的最大存储时间,Expires是一个过时日期,在此日期之前,浏览器都会认为资源有效。
    • 如果资源的缓存未过期,则直接从本地缓存中获取资源,不会向服务器发送请求。
  2. 协商缓存

    • 协商缓存是指客户端在请求资源时,需要向服务器发送一个验证请求,由服务器来决定是否可以使用缓存。
    • 通过设置ETagLast-Modified响应头来实现协商缓存。
    • 客户端在请求资源时,会将上次获取的资源的ETagLast-Modified值发送给服务器,服务器根据这些值来判断资源是否有更新。
    • 如果资源没有更新,服务器返回304 Not Modified状态码,并且响应中不包含资源的实际内容,客户端直接从本地缓存中获取资源。

综合使用强缓存和协商缓存可以有效地减少网络流量,提高网站性能。通过合理设置缓存策略,可以根据资源的特性和更新频率来平衡性能和实时性。

6. HTTP 1.1 / 2 / 3 

HTTP(Hypertext Transfer Protocol)是用于传输超文本的应用层协议,它是Web通信的基础。目前广泛使用的版本包括HTTP/1.1、HTTP/2和HTTP/3,它们之间有一些重要的区别:

  1. HTTP/1.1

    • HTTP/1.1是目前使用最广泛的HTTP协议版本,它是1999年发布的。
    • 特点是基于文本的协议,每个请求和响应都是一个独立的TCP连接,请求和响应的头部信息较多,存在头部重复传输、队头阻塞等问题。
    • 使用多个TCP连接来并行传输资源,但由于TCP连接数量限制和队头阻塞问题,性能不高。
  2. HTTP/2

    • HTTP/2是HTTP/1.1的进化版本,由Google推出,基于SPDY协议。
    • HTTP/2采用二进制格式传输数据,头部压缩和多路复用等技术,大幅提升了性能。
    • 支持服务端推送、头部压缩、流量控制等新特性,减少了网络延迟和带宽消耗。
  3. HTTP/3

    • HTTP/3是基于QUIC协议的HTTP协议的最新版本,目的是进一步提高网络传输的效率。
    • HTTP/3使用UDP而不是TCP作为传输协议,通过QUIC协议在UDP上构建安全、可靠的连接。
    • HTTP/3采用了新的流量控制和拥塞控制机制,解决了TCP的队头阻塞问题,提高了网络吞吐量和连接的稳定性。

总的来说,HTTP/2和HTTP/3相比HTTP/1.1都有较大的性能提升,而HTTP/3相比HTTP/2则进一步提高了传输效率和连接的稳定性,但由于HTTP/3是相对较新的协议,目前还在逐步推广中。

7. TCP 三次握手/四次挥手

作者提到的公司

作者:被团子嫌弃的纯小丑
链接:www.nowcoder.com/feed/main/d…

阿里控股钉钉前端一二面

9.11一面电话面
1、应该没八股;
2、问了怎么学前端,工作期待和规划,之类的

3、发个链接做一道题,把嵌套结构的对象的key从_分割改为驼峰:

写了个深拷贝,拷贝的同时把对象的key改为驼峰
以下是一个将嵌套结构对象的key从下划线分割改为驼峰形式的JavaScript函数:

function convertKeysToCamelCase(obj) {
    // 判断传入的参数是否为对象
    if (typeof obj !== 'object' || obj === null) {
        return obj;
    }

    // 创建一个新对象,用于存储转换后的数据
    let newObj = Array.isArray(obj) ? [] : {};

    // 遍历原对象的所有属性
    for (let key in obj) {
        // 如果属性是原对象的自有属性
        if (obj.hasOwnProperty(key)) {
            // 将key转换为驼峰形式
            let camelKey = key.replace(/_([a-z])/g, function(match, p1) {
                return p1.toUpperCase();
            });

            // 如果属性的值仍然是一个对象,则递归调用转换函数
            if (typeof obj[key] === 'object' && obj[key] !== null) {
                newObj[camelKey] = convertKeysToCamelCase(obj[key]);
            } else {
                // 否则直接赋值给新对象的相应属性
                newObj[camelKey] = obj[key];
            }
        }
    }

    return newObj;
}

使用示例:

const obj = {
    user_name: 'John',
    user_info: {
        address_info: {
            street_address: '123 Main St',
            city_name: 'Anytown'
        },
        phone_number: '555-1234'
    }
};

const convertedObj = convertKeysToCamelCase(obj);
console.log(convertedObj);

此函数能够对传入的嵌套结构对象进行转换,将所有的key从下划线分割改为驼峰形式。

下面是一个JavaScript函数,可以实现深拷贝并将对象的key改为驼峰形式:

function deepCopyAndCamelCase(obj) {
    // 判断传入的参数是否为对象
    if (typeof obj !== 'object' || obj === null) {
        return obj;
    }

    // 创建一个新对象,用于存储拷贝后的数据
    let newObj = Array.isArray(obj) ? [] : {};

    // 遍历原对象的所有属性
    for (let key in obj) {
        // 如果属性是原对象的自有属性
        if (obj.hasOwnProperty(key)) {
            // 将key转换为驼峰形式
            let camelKey = key.replace(/_([a-z])/g, function(match, p1) {
                return p1.toUpperCase();
            });

            // 如果属性的值仍然是一个对象,则递归调用深拷贝函数
            if (typeof obj[key] === 'object' && obj[key] !== null) {
                newObj[camelKey] = deepCopyAndCamelCase(obj[key]);
            } else {
                // 否则直接赋值给新对象的相应属性
                newObj[camelKey] = obj[key];
            }
        }
    }

    return newObj;
}

使用示例:

const obj = {
    user_name: 'John',
    user_age: 30,
    user_info: {
        address_info: {
            street_address: '123 Main St',
            city_name: 'Anytown'
        },
        phone_number: '555-1234'
    }
};

const copiedObj = deepCopyAndCamelCase(obj);
console.log(copiedObj);

此函数能够对传入的对象进行深拷贝,并将对象的key改为驼峰形式。

4、反问了面试官对前端框架的看法

9.12二面电话面
1、也是全程无八股;
2、拷打项目:
项目是一个canvas画板,问了重做撤回是怎么做的,讲了canvas压缩图片;

   在一个Canvas画板项目中,实现重做(Redo)和撤回(Undo)功能是很常见的需求,通常可以通过以下方式实现:
  1. 状态管理:使用状态管理来跟踪用户操作的历史记录。可以维护一个状态堆栈,记录用户每次操作后的画布状态,包括绘制的图形、颜色、位置等信息。

  2. 撤回功能:当用户执行撤回操作时,从状态堆栈中弹出最近一次的状态,并将其应用到画布上,实现撤回操作。同时更新画布上的显示内容,让用户看到撤回后的效果。

  3. 重做功能:当用户执行重做操作时,可以重新应用被撤回的操作,从而恢复到之前的状态。可以将被撤回的状态保存在另一个堆栈中,当用户执行重做操作时,从该堆栈中弹出状态并应用到画布上。

  4. 状态合并:为了节省内存和提高性能,可以将相邻的相似状态合并,从而减少状态堆栈的大小。例如,如果用户连续绘制了多个相同颜色和形状的图形,可以将它们合并成一个状态。

  5. 用户交互:在实现撤回和重做功能时,要考虑用户交互的方式。通常可以通过按钮、快捷键或上下文菜单等方式提供撤回和重做功能,并及时更新UI以反映当前的操作状态。

至于Canvas图片的压缩,可以使用Canvas提供的toDataURL()方法将Canvas内容转换为Base64编码的图像数据,然后可以使用压缩算法对Base64编码的图像数据进行压缩,从而减小图像文件的大小。压缩后的图像数据可以保存到服务器或本地存储,以实现图片的压缩和节省带宽的效果。

现场设计一个在线协同编辑的方案,冲突怎么解决(能不能合并);

5设计一个在线协同编辑方案需要考虑多方同时编辑同一个文档时可能出现的冲突以及如何解决这些冲突的问题。下面是一个简单的在线协同编辑方案:

  1. 实时同步编辑:使用WebSocket等实时通信技术,实现多用户之间的实时同步编辑。当某个用户对文档进行编辑时,系统即时将其编辑操作同步给其他用户,从而实现多方同时协同编辑的功能。

  2. 文档版本管理:为每个文档维护一个版本历史记录,记录每个用户的编辑操作以及编辑时间戳等信息。当发生冲突时,可以根据版本历史记录来进行冲突解决。

  3. 冲突检测与解决:在用户编辑文档时,系统可以实时检测是否存在冲突。当多个用户同时编辑同一个文档的同一部分时,可能会产生冲突。系统可以通过比较不同用户的编辑操作,检测是否存在冲突,并在发现冲突时给出相应的提示。

  4. 自动合并与手动解决:对于简单的冲突,系统可以自动进行合并操作,将不同用户的编辑内容合并到同一个文档中。对于复杂的冲突,系统可以提供手动解决的方式,让用户自行选择如何处理冲突,例如保留其中一个版本、手动合并内容等。

  5. 冲突回滚与恢复:当用户选择解决冲突后,系统应该及时更新文档的版本信息,并向所有用户同步最新的文档内容。同时,系统还应该提供冲突回滚和恢复的功能,让用户可以回滚到之前的版本或恢复到解决冲突后的版本。

  6. 实时协作功能:除了协同编辑外,系统还应该提供实时的用户在线状态、光标位置、选区等信息,以增强用户之间的协作体验。用户可以看到其他用户正在编辑的部分,从而避免重复编辑和冲突。

综上所述,一个在线协同编辑的方案应该包括实时同步编辑、文档版本管理、冲突检测与解决、自动合并与手动解决、冲突回滚与恢复、实时协作功能等功能,以实现多方同时协同编辑的需求。

讲到了面向对象,问怎么防止继承两个冲突的基类(A.a=true,B.a=false,这两个类的a属性是冲突的),提示了可以在ESlint做一些限制;

面向对象编程中,确保继承两个具有冲突属性的基类不会导致意外行为是很重要的。可以采取一些措施来防止这种情况的发生:

  1. 避免命名冲突:在设计基类时,避免使用与其他基类相同的属性名,尽量保持属性名的唯一性,以减少继承时可能出现的冲突。

  2. 使用命名空间:可以将属性封装在命名空间中,以防止命名冲突。例如,在基类A和基类B中,将属性a分别放置在命名空间A和命名空间B中,可以避免冲突。

  3. 限制继承:可以在代码中使用ESLint等静态代码检查工具来限制继承具有冲突属性的基类,从而在编译或运行前捕获这种潜在的问题。

  4. 使用组合而非继承:如果可能,可以考虑使用组合而非继承的方式来复用代码。通过将属性封装在对象中,并在需要时将其传递给其他对象,可以减少继承带来的潜在冲突。

  5. 文档化:在编写基类时,应该清晰地文档化每个属性的含义和用途,并在继承时警告潜在的冲突问题,让开发人员能够及时发现和解决问题。 综上所述,通过避免命名冲突、使用命名空间、限制继承、使用组合而非继承、文档化等方式,可以有效地防止继承两个冲突的基类导致意外行为的发生。同时,利用静态代码检查工具等辅助工具也可以帮助开发人员及时发现和解决潜在的问题。 3、也问了对工作的期待,描述你对一天的工作的期待(答大公司平台好什么的应该太泛了);
    4、面试官怕我不知道反问什么,详细地介绍了钉钉做的事情,主要做平台和AI相关的
    5、反问有多少论面试:4到5轮。真嘟假嘟,不是说阿里今年大部分开p4吗,也要这么多面吗,牛油有没有懂哥
    面试下来感觉都很不错,面试官的态度都非常好,面试就像一个交流的过程,问了很多开放性或者没那么八股的技术问题,也介绍了很多公司的业务。但也感受到了对候选人的要求也很高。
    也有一些启发吧:
    比如你对做的业务有什么了解和期待(这个问题在以前的面试也有问过,就是有没有想过怎么回答这种问题); 对工作的期待:就是你认为工作是有价值的才能够持续的工作,(就是不要把兴趣变成工作这类问题的思考,当然我也知道前提是先找到工作!,总的来说就是想一下喜欢做什么业务类型、工作类型(不管是为了自己还是为了应对面试官问这个问题,我觉得都值得思考一下吧)


有很多忘了,后面回去回忆下在更新
面试有感而发,所以分享一下
面完二面好像就泡池子了,感觉得泡死在那了

作者:dioee
链接:www.nowcoder.com/discuss/532…
来源:牛客网