2023.07.23 - 2023.07.29 更新前端面试问题总结(20道题)
获取更多面试相关问题可以访问
github 地址: github.com/pro-collect…
gitee 地址: gitee.com/yanleweb/in…
目录:
-
初级开发者相关问题【共计 1 道题】
- 487.typeof null 的输出结果是什么,为什么?【热度: 93】【JavaScript】【出题公司: Shopee】
-
中级开发者相关问题【共计 14 道题】
- 484.proxy 有那些实际使用场景【热度: 849】【JavaScript】【出题公司: 滴滴】
- 485.script 标签上有那些属性,分别作用是啥?【热度: 744】【web应用场景】【出题公司: Shopee】
- 486.如何冻结一个 JS 对象【热度: 949】【JavaScript】【出题公司: Shopee】
- 488.[Vue] Vue2.0 和 Vue3.0 有什么区别【web框架】
- 490.DOM 树解析过程【热度: 416】【浏览器】
- 492.实现管道函数【热度: 540】【JavaScript】【出题公司: Shopee】
- 493.为什么 SPA 应用都会提供一个 hash 路由,好处是什么?【热度: 681】【web应用场景】【出题公司: 快手】
- 494.HTML5 的 History API 进行导航时,页面真的进行了一个切换吗?【热度: 424】【浏览器】【出题公司: 滴滴】
- 495.原生 js 如何进行监听路由的变化【热度: 906】【浏览器】【出题公司: 网易】
- 496.[React] 如何进行路由变化监听【热度: 698】【web应用场景】【出题公司: 百度】
- 497.onpopstate可以监听到一个pushstate的事件吗【热度: 546】【浏览器】【出题公司: 百度】
- 500.根据 path 来解析数组,生成多维度的数组对象【算法题】【JavaScript】
- 501.TCP/IP五层协议是什么?【热度: 548】【网络】【出题公司: 美团】
- 503.如何判断一个单向链表是否是循环链表?【热度: 975】【JavaScript】【出题公司: 美团】
-
高级开发者相关问题【共计 5 道题】
- 489.[Vue] 你做过哪些性能优化【热度: 969】【web框架】【出题公司: Shopee】
- 491.如何优化 DOM 树解析过程【热度: 414】【浏览器】
- 498.一般项目里面对请求 request 都会做哪些统一封装?【热度: 916】【网络】【出题公司: 阿里巴巴】
- 499.如何封装一个请求,让其多次调用的时候,实际只发起一个请求的时候,返回同一份结果【热度: 636】【网络】【出题公司: 阿里巴巴】
- 502.浏览器本身是不支持模块化的, webpack 是如何通过文件打包,让浏览器可以读取到前端各个模块的代码的?【热度: 1,153】【工程化】【出题公司: 美团】
初级开发者相关问题【共计 1 道题】
487.typeof null 的输出结果是什么,为什么?【热度: 93】【JavaScript】【出题公司: Shopee】
关键词:typeof null 输出结果
在 JavaScript 中,typeof null 的输出结果是 "object"。
这是因为在 JavaScript 中,null 被视为一个特殊的空值对象。尽管 null 实际上不是一个对象,它是一个原始类型的值,但 typeof null 返回 "object" 是由于历史原因以及 JavaScript 的设计缺陷。
在 JavaScript 的早期版本中,null 被错误地标记为一个 "object" 类型,这个错误一直延续至今,以保持与早期版本的兼容性。所以,当我们使用 typeof 操作符来检查 null 时,它会返回 "object"。
需要注意的是,这个返回值是一个历史遗留问题,不应该用来判断一个变量是否为 null。为了准确地检查一个变量是否为 null,我们应该使用严格相等运算符 ===,如 myVariable === null。
中级开发者相关问题【共计 14 道题】
484.proxy 有那些实际使用场景【热度: 849】【JavaScript】【出题公司: 滴滴】
关键词:proxy 应用场景、proxy 作用是什么
JavaScript的Proxy对象提供了一种拦截并定制JavaScript对象底层操作的机制。它允许你在对象上定义自定义行为,例如访问、赋值、函数调用等操作。Proxy对象包裹着目标对象,并拦截对目标对象的访问,使你能够自定义处理这些操作。
Proxy可以用于实现很多功能,包括:
- 属性验证和拦截:可以拦截对象属性的读取、写入和删除操作,并进行验证和处理。例如,你可以拦截对属性的访问,验证属性的值是否符合特定规则。
- 对象扩展和变形:可以拦截对象属性的读取和写入操作,并根据需求进行变形或扩展。例如,你可以在访问对象属性时,动态生成属性的值。
- 函数调用的拦截:可以拦截函数的调用和构造,以便进行自定义处理。例如,你可以在函数调用之前或之后执行额外的逻辑。
- 数组操作的拦截:可以拦截数组的操作,如push、pop、shift等,允许你对数组的操作进行自定义处理。例如,你可以在数组操作之后触发其他逻辑。
通过使用Proxy对象,你可以拦截和修改对象的底层操作,实现更加灵活和定制化的行为。然而需要注意的是,Proxy对象的使用可能会导致性能上的一些影响,所以在使用时要谨慎考虑。
Proxy的实际使用场景有很多,以下是一些常见的示例:
- 数据验证和过滤:你可以使用
Proxy来拦截对对象属性的访问和修改,从而进行数据验证和过滤。例如,你可以使用Proxy来确保一个对象的属性只能是特定的类型或范围。
const person = {
name: 'Alice',
age: 25
};
const personProxy = new Proxy(person, {
set(target, key, value) {
if (key === 'age' && (typeof value !== 'number' || value < 0)) {
throw new Error('Invalid age');
}
target[key] = value;
return true;
}
});
personProxy.age = -10; // 抛出错误:Invalid age
- 计算属性:你可以使用
Proxy来动态计算属性的值,而无需实际存储它们。这对于需要根据其他属性的值来计算衍生属性的情况非常有用。
const person = {
firstName: 'Alice',
lastName: 'Smith'
};
const personProxy = new Proxy(person, {
get(target, key) {
if (key === 'fullName') {
return `${target.firstName} ${target.lastName}`;
}
return target[key];
}
});
console.log(personProxy.fullName); // Alice Smith
- 资源管理和延迟加载:你可以使用
Proxy来延迟加载资源,直到它们被真正需要。这在处理大型数据集或昂贵的资源时非常有用,可以节省内存和提高性能。
const expensiveResource = {
// 一些昂贵的操作
};
const expensiveResourceProxy = new Proxy(expensiveResource, {
get(target, key) {
// 在需要的时候才加载资源
if (!target.loaded) {
target.load();
target.loaded = true;
}
return target[key];
}
});
console.log(expensiveResourceProxy.someProperty); // 加载资源并返回属性值
- 日志记录和调试:你可以使用
Proxy来记录对象属性的访问和修改,以便进行调试和日志记录。
const person = {
name: 'Alice',
age: 25
};
const personProxy = new Proxy(person, {
get(target, key) {
console.log(`Getting property '${key}'`);
return target[key];
},
set(target, key, value) {
console.log(`Setting property '${key}' to '${value}'`);
target[key] = value;
return true;
}
});
personProxy.age; // 记录:Getting property 'age'
personProxy.age = 30; // 记录:Setting property 'age' to '30'
这些只是Proxy的一些实际使用场景示例,Proxy的强大之处在于它提供了对对象的底层操作的拦截和自定义能力,可以根据具体需求进行灵活的应用。
485.script 标签上有那些属性,分别作用是啥?【热度: 744】【web应用场景】【出题公司: Shopee】
关键词:script 标签属性、script 标签属性作用、常用 script 标签属性
在HTML中,<script>标签用于引入或嵌入JavaScript代码。<script>标签可以使用以下属性来调整脚本的行为:
常用属性
src:指定要引入的外部JavaScript文件的URL。例如:<script src="script.js"></script>。通过这个属性,浏览器会下载并执行指定的外部脚本文件。async:可选属性,用于指示浏览器异步加载脚本。这意味着脚本会在下载的同时继续解析HTML文档,不会阻塞其他资源的加载。例如:<script src="script.js" async></script>。defer:可选属性,用于指示浏览器延迟执行脚本,直到文档解析完成。这样可以确保脚本在文档完全呈现之前不会执行。例如:<script src="script.js" defer></script>。type:指定脚本语言的MIME类型。通常是text/javascript或者module(用于ES6模块)。如果未指定该属性,浏览器默认将其视为JavaScript类型。例如:<script type="text/javascript">...</script>。charset:指定外部脚本文件的字符编码。例如:<script src="script.js" charset="UTF-8"></script>。integrity:用于指定外部脚本文件的Subresource Integrity(SRI)。SRI可以确保浏览器在加载脚本时验证其完整性,防止通过恶意更改文件来执行潜在的攻击。例如:<script src="script.js" integrity="sha256-qznLcsROx4GACP2dm0UCKCzCG+HiZ1guq6ZZDob/Tng="></script>。
不常用属性
crossorigin:正常的 script 元素将最小的信息传递给 window.onerror,用于那些没有通过标准 CORS 检查的脚本。要允许对静态媒体使用独立域名的网站进行错误记录,请使用此属性。参见 CORS 设置属性。fetchpriority:提供一个指示,说明在获取外部脚本时要使用的相对优先级。nomodule: 这个布尔属性被设置来标明这个脚本不应该在支持 ES 模块的浏览器中执行。实际上,这可用于在不支持模块化 JavaScript 的旧浏览器中提供回退脚本。nonce: 在script-src Content-Security-Policy (en-US)中允许脚本的一个一次性加密随机数(nonce)。服务器每次传输策略时都必须生成一个唯一的 nonce 值。提供一个无法猜测的 nonce 是至关重要。referrerpolicy: 表示在获取脚本或脚本获取资源时,要发送哪个 referrer。
可以参考文档:developer.mozilla.org/zh-CN/docs/…
486.如何冻结一个 JS 对象【热度: 949】【JavaScript】【出题公司: Shopee】
关键词:Object.freeze、Object.freeze作用、深度冻结对象
冻结对象
要冻结一个 JavaScript 对象,以防止别人更改它,可以使用Object.freeze()方法。Object.freeze() 方法会递归地冻结一个对象的所有属性,使其变为只读的,并防止更改、删除或添加新属性。以下是使用Object.freeze()方法冻结对象的示例:
const obj = {
prop1: 1,
prop2: 'Hello',
};
Object.freeze(obj);
// 尝试更改属性的值
obj.prop1 = 2; // 不会生效,obj.prop1仍然为1
// 尝试删除属性
delete obj.prop2; // 不会生效,obj仍然包含prop2属性
// 尝试添加新属性
obj.prop3 = true; // 不会生效,obj不会添加新属性
console.log(obj);
在上述示例中,通过调用Object.freeze(obj)方法,将obj对象冻结,使其变为只读。此后,无论是更改、删除还是添加属性,都不会对对象产生任何影响。最后,通过console.log(obj) 输出对象,可以看到对象保持不变,即使尝试进行更改。
需要注意的是,Object.freeze()方法只会冻结对象的直接属性,而不会冻结嵌套对象的属性。如果需要递归地冻结嵌套对象的属性,可以编写一个递归函数来处理。
深度冻结
要冻结嵌套属性,可以使用一个递归函数来处理。该函数会遍历对象的所有属性,并对每个属性进行冻结。以下是一个示例:
function deepFreeze(obj) {
// 首先冻结当前对象
Object.freeze(obj);
// 遍历对象的所有属性
for (let key of Object.keys(obj)) {
let value = obj[key];
// 如果属性是对象类型,则递归调用deepFreeze函数
if (typeof value === 'object' && value !== null) {
deepFreeze(value);
}
}
return obj;
}
const obj = {
prop1: 1,
prop2: {
nestedProp1: 'Hello',
nestedProp2: [1, 2, 3],
},
};
const frozenObj = deepFreeze(obj);
// 尝试更改嵌套属性的值
frozenObj.prop2.nestedProp1 = 'World'; // 不会生效,嵌套属性仍然为'Hello'
console.log(frozenObj);
在上述示例中,我们定义了一个名为deepFreeze的递归函数。该函数首先会对当前对象进行冻结(调用Object.freeze(obj)),然后遍历对象的所有属性。如果属性是对象类型,则递归调用deepFreeze 函数,对嵌套对象进行冻结。
通过调用deepFreeze(obj)函数,我们将obj对象及其嵌套属性都冻结,并将结果赋值给frozenObj。尝试更改嵌套属性的值后,输出frozenObj,可以看到对象保持不变,嵌套属性的值没有被更改。
需要注意的是,deepFreeze函数并不会修改原始对象,而是返回一个新的冻结对象。如果需要修改原始对象,可以将冻结的属性逐个复制到一个新对象中。
488.[Vue] Vue2.0 和 Vue3.0 有什么区别【web框架】
主要从以下方面做对比
- 响应式系统的重新配置,使用proxy替换Object.defineProperty
- typescript支持
- 新增组合API,更好的逻辑重用和代码组织
- v-if和v-for的优先级
- 静态元素提升
- 虚拟节点静态标记
- 生命周期变化
- 打包体积优化
- ssr渲染性能提升
- 支持多个根节点
- 参考文档: juejin.cn/post/685855…
490.DOM 树解析过程【热度: 416】【浏览器】
关键词:DOM 树解析过程、DOM 树解析
DOM树的生成是由浏览器解析HTML文档时自动生成的。下面是DOM树生成的一般过程:
- 解析HTML:浏览器从上到下逐行解析HTML文档,将文档分解为一系列的标记(tokens)。
- 构建DOM节点:解析器根据标记构建DOM节点,并将这些节点连接到树形结构中。每个标记对应一个DOM节点,包括元素节点、文本节点、注释节点等。
- 构建父子关系:解析器根据标记的嵌套关系,将构建的DOM节点连接成一个树形结构。嵌套关系表示了标记之间的父子关系,即一个节点可以成为另一个节点的子节点。
- 处理样式和脚本:当解析器遇到样式(CSS)和脚本(JavaScript)时,会调用相关的解析器或执行器来处理并应用样式和脚本。
- 生成渲染树:浏览器根据DOM树和样式信息生成渲染树(Render Tree),渲染树是用于页面渲染和绘制的树形结构。
- 布局和绘制:浏览器根据渲染树进行布局(Layout)和绘制(Painting),确定每个节点在屏幕上的位置和样式,并将其绘制到屏幕上。
492.实现管道函数【热度: 540】【JavaScript】【出题公司: Shopee】
关键词:JS 管道函数、JS 管道函数实现
管道函数是一种函数编程的概念,它可以将多个函数串联起来,将前一个函数的输出作为后一个函数的输入。以下是一个简单的实现示例:
// 简化版的管道函数实现
function pipe(...fns) {
return function(input) {
return fns.reduce((output, fn) => fn(output), input);
};
}
// 示例函数
function addOne(num) {
return num + 1;
}
function double(num) {
return num * 2;
}
function square(num) {
return num ** 2;
}
// 创建一个管道函数
const myPipe = pipe(addOne, double, square);
// 使用管道函数进行计算
const result = myPipe(2); // 2 -> addOne -> 3 -> double -> 6 -> square -> 36
console.log(result); // 输出 36
在上述示例中,我们首先定义了三个简单的示例函数:addOne、double和square。然后,通过调用pipe函数,将这三个函数串联起来创建了一个管道函数myPipe。最后,我们可以通过调用myPipe函数并传入初始值2,得到最终的计算结果36。
在管道函数的实现中,使用了ES6的扩展运算符(...)和Array的reduce方法。reduce方法接受一个累加器函数和初始值,并将累加器函数应用于数组的每个元素,返回最终的累积结果。在这里,累加器函数将前一个函数的输出作为后一个函数的输入,从而实现了函数的串联。
493.为什么 SPA 应用都会提供一个 hash 路由,好处是什么?【热度: 681】【web应用场景】【出题公司: 快手】
关键词:hash路由优势、hash路由和history路由区别
SPA(单页应用)通常会使用 hash 路由的方式来实现页面的导航和路由功能。这种方式将路由信息存储在 URL 的片段标识符(hash)中,例如:www.example.com/#/home。
以下是使用 hash 路由的 SPA 的一些好处:
- 兼容性:Hash 路由对浏览器的兼容性非常好,可以在所有主流浏览器上运行,包括较旧的浏览器版本。这是因为 hash 路由不需要对服务端进行特殊的配置或支持。
- 简单实现:实现 hash 路由非常简单,只需要在页面中添加一个监听器来监听
hashchange事件,然后根据不同的 hash 值加载对应的页面内容。这种方式不需要对服务器进行特殊配置,服务器只需传送一个初始页面,之后的页面切换完全由前端控制。 - 防止页面刷新:使用 hash 路由可以防止页面的完全刷新。因为 hash 路由只改变 URL 的片段标识符,不会引起整个页面的重新加载,所以用户在不同页面之间切换时,不会丢失当前页面的状态和数据。
- 前进后退支持:由于 hash 路由不会引起页面的刷新,因此可以方便地支持浏览器的前进和后退操作。浏览器的前进和后退按钮可以触发
hashchange事件,从而实现页面的导航和页面状态的管理。 - 无需服务端配置:使用 hash 路由,不需要对服务端进行特殊的配置。所有的路由和页面切换逻辑都由前端控制,服务器只提供一个初始页面。这样可以减轻服务器的负担,并且可以将更多的逻辑放在前端处理,提升用户体验。
虽然 hash 路由有一些好处,但也有一些局限性。例如,hash 路由的 URL 不够美观,也不利于 SEO(搜索引擎优化)。为了解决这些问题,现代的 SPA 框架通常使用更先进的路由方式,例如 HTML5 的 History API,它可以在不刷新整个页面的情况下改变 URL。不过,hash 路由仍然是一个简单可靠的选择,特别适用于简单的 SPA 或需要兼容较旧浏览器的情况。
494. HTML5 的 History API 进行导航时,页面真的进行了一个切换吗?【热度: 424】【浏览器】【出题公司: 滴滴】
关键词:History 导航、History 导航页面切换、History 页面切换
当使用 HTML5 的 History API 进行导航时,页面实际上没有进行完全的刷新。相反,只是通过 JavaScript 动态地更改 URL,并通过这个新的 URL 加载相应的内容。
这种方式被称为前端路由,因为页面的切换是在前端处理的,而不是通过向服务器请求新的页面。在导航期间,浏览器会保留当前页面的状态和数据,以便在返回时恢复。
这种页面切换的方式有以下几个特点:
- 前端渲染:页面的内容是通过 JavaScript 动态渲染的,可以实现无刷新的页面切换效果。
- 只加载部分内容:仅加载页面中需要更新的部分,而不是整个页面的内容。
- 保留页面状态:页面切换后,不会丢失当前页面的状态和数据,可以在返回时恢复。
虽然页面实际上没有进行完全的切换和刷新,但对于用户而言,他们会感知到页面的切换效果,因为 URL 和页面内容发生了变化。这种方式能够提供更流畅的用户体验,并提高了应用的性能。
需要注意的是,使用 History API 进行导航时,需要确保服务器配置正确,以便在直接访问 URL 或刷新页面时能够正确地返回相应的内容。这通常需要在服务器端设置一个后备规则,以便将所有请求都指向应用的入口文件,例如 index.html,从而实现前端路由的正常工作。
495.原生 js 如何进行监听路由的变化【热度: 906】【浏览器】【出题公司: 网易】
关键词:原生路由监听
在原生 JavaScript 中,可以使用 window 对象上的 popstate 事件来监听路由的变化。popstate 事件在浏览器的历史记录发生变化时触发,包括当用户点击浏览器的前进或后退按钮、调用 history.pushState() 或 history.replaceState() 方法等。
下面是一个简单的示例代码,演示如何使用 popstate 事件监听路由的变化:
// 监听 popstate 事件
window.addEventListener('popstate', function(event) {
// 在这里可以执行路由变化后的处理逻辑
console.log('路由发生了变化');
});
// 修改 URL 并添加一条历史记录
history.pushState(null, null, '/new-route');
// 或者使用 history.replaceState() 方法替换当前历史记录
// history.replaceState(null, null, '/new-route');
在上面的代码中,当 popstate 事件触发时,回调函数会被执行。你可以在回调函数中添加适当的处理逻辑,例如更新页面内容、重新渲染视图等。
需要注意的是,popstate 事件不会在页面加载时触发,因此如果你需要在页面加载时执行一些初始化的路由处理逻辑,可以将该逻辑封装为一个函数,并在加载时调用一次,然后再通过 popstate 事件监听路由的变化。
另外,还可以使用 history.state 属性来获取当前历史记录的状态对象,该对象可以在调用 history.pushState() 或 history.replaceState() 方法时传入。这样可以在 popstate 事件回调函数中访问和使用该状态对象。
window.addEventListener('popstate', function(event) {
var state = history.state;
// 在这里可以访问和使用历史记录的状态对象
});
通过监听 popstate 事件,可以在原生 JavaScript 中轻松地监听和响应路由的变化,从而实现相应的页面切换和处理逻辑。
496.[React] 如何进行路由变化监听【热度: 698】【web应用场景】【出题公司: 百度】
关键词:React 路由、React 路由监听
在 React 中,你可以使用 React Router 库来进行路由变化的监听。React Router 是 React 的一个常用路由库,它提供了一组组件和 API 来帮助你在应用中管理路由。
下面是一个示例代码,演示如何使用 React Router 监听路由的变化:
然后,在你的 React 组件中,使用 BrowserRouter 或 HashRouter 组件包裹你的应用:
import React from 'react';
import { BrowserRouter, HashRouter } from 'react-router-dom';
function App() {
return (
// 使用 BrowserRouter 或 HashRouter 包裹你的应用
<BrowserRouter>
{/* 在这里编写你的应用内容 */}
</BrowserRouter>
);
}
export default App;
当使用函数组件时,可以使用 useEffect 钩子函数来监听路由变化。下面是修改后的示例代码:
import React, { useEffect } from 'react';
import { withRouter } from 'react-router-dom';
function MyComponent(props) {
useEffect(() => {
const handleRouteChange = (location, action) => {
// 路由发生变化时执行的处理逻辑
console.log('路由发生了变化', location, action);
};
// 在组件挂载后,添加路由变化的监听器
const unlisten = props.history.listen(handleRouteChange);
// 在组件卸载前,移除监听器
return () => {
unlisten();
};
}, [props.history]);
return (
<div>
{/* 在这里编写组件的内容 */}
</div>
);
}
// 使用 withRouter 高阶组件将路由信息传递给组件
export default withRouter(MyComponent);
在上面的代码中,我们使用了 useEffect 钩子函数来添加路由变化的监听器。在 useEffect 的回调函数中,我们定义了 handleRouteChange 方法来处理路由变化的逻辑。然后,通过 props.history.listen 方法来添加监听器,并将返回的取消监听函数赋值给 unlisten 变量。
同时,我们还在 useEffect 返回的清理函数中调用了 unlisten 函数,以确保在组件卸载时移除监听器。
需要注意的是,由于 useEffect 的依赖数组中包含了 props.history,所以每当 props.history 发生变化时(即路由发生变化时),useEffect 的回调函数会被调用,从而更新路由变化的监听器。
总结起来,通过使用 useEffect 钩子函数和 props.history.listen 方法,可以在函数组件中监听和响应路由的变化。
497.onpopstate可以监听到一个pushstate的事件吗【热度: 546】【浏览器】【出题公司: 百度】
关键词:popstate
onpopstate 事件只能监听到浏览器历史记录的前进和后退操作,无法直接监听到 pushState 或 replaceState 的调用。这是因为 pushState 和 replaceState 方法可以修改浏览器历史记录而不触发 onpopstate 事件。
但是,您可以在调用 pushState 或 replaceState 之后手动触发 popstate 事件,来模拟类似的效果。示例如下:
// 监听 popstate 事件
window.addEventListener('popstate', function(event) {
console.log('popstate event triggered');
});
// 调用 pushState 方法
window.history.pushState(null, null, '/new-url');
// 手动触发 popstate 事件
var popStateEvent = new PopStateEvent('popstate', { state: null });
window.dispatchEvent(popStateEvent);
在上述示例中,我们首先通过 addEventListener 方法监听 popstate 事件。然后,我们调用 pushState 方法来修改浏览器历史记录,并在之后手动创建一个 PopStateEvent 对象,并使用 dispatchEvent 方法来触发 popstate 事件。
这样就可以实现在调用 pushState 或 replaceState 之后手动触发一个事件来模拟监听到 pushState 的效果。
500.根据 path 来解析数组,生成多维度的数组对象【算法题】【JavaScript】
请手写一个函数, 将下面的树形结构, 进行转换:
输入数据结构
const data = [
{ id: 0, label: '测试 - 0', path: 'demo.info' },
{ id: 1, label: '测试 - 1', path: 'demo.info' },
{ id: 2, label: '测试 - 2', path: 'common.base' },
{ id: 3, label: '测试 - 3', path: 'common.base' },
{ id: 4, label: '测试 - 4', path: 'demo.info' },
{ id: 5, label: '测试 - 5', path: 'demo.info' },
{ id: 6, label: '测试 - 6', path: 'common' },
{ id: 7, label: '测试 - 7', path: 'common' },
{ id: 8, label: '测试 - 8', path: 'common.address' },
{ id: 9, label: '测试 - 9', path: 'common.address' },
{ id: 10, label: '测试 - 10', path: 'demo.info' },
{ id: 11, label: '测试 - 11', path: 'demo.sence' },
{ id: 12, label: '测试 - 12', path: 'demo.sence' },
{ id: 13, label: '测试 - 13', path: 'demo.hash' },
{ id: 14, label: '测试 - 14', path: 'demo.hash' },
{ id: 15, label: '测试 - 15', path: 'demo.hash' },
{ id: 16, label: '测试 - 16', path: 'demo' },
{ id: 17, label: '测试 - 17', path: 'demo' },
{ id: 18, label: '测试 - 18', path: 'demo.info' },
{ id: 19, label: '测试 - 19', path: 'demo.info' }
];
输出数据结构
[ { "value": "demo", "label": "Demo", "children": [ { "value": "info", "label": "Info", "children": [ { "value": 0, "label": "测试 - 0" }, { "value": 1, "label": "测试 - 1" }, { "value": 4, "label": "测试 - 4" }, { "value": 5, "label": "测试 - 5" }, { "value": 10, "label": "测试 - 10" }, { "value": 18, "label": "测试 - 18" }, { "value": 19, "label": "测试 - 19" } ]
},
{
"value": "sence",
"label": "Sence",
"children": [
{
"value": 11,
"label": "测试 - 11"
},
{
"value": 12,
"label": "测试 - 12"
}
]
},
{
"value": "hash",
"label": "Hash",
"children": [
{
"value": 13,
"label": "测试 - 13"
},
{
"value": 14,
"label": "测试 - 14"
},
{
"value": 15,
"label": "测试 - 15"
}
]
},
{
"value": 16,
"label": "测试 - 16"
},
{
"value": 17,
"label": "测试 - 17"
}
]
},
{
"value": "common",
"label": "Common",
"children": [
{
"value": "base",
"label": "Base",
"children": [
{
"value": 2,
"label": "测试 - 2"
},
{
"value": 3,
"label": "测试 - 3"
}
]
},
{
"value": 6,
"label": "测试 - 6"
},
{
"value": 7,
"label": "测试 - 7"
},
{
"value": "address",
"label": "Address",
"children": [
{
"value": 8,
"label": "测试 - 8"
},
{
"value": 9,
"label": "测试 - 9"
}
]
}
]
}
]
实现如下:
function convertToThreeDimensionalArray(data) {
const result = [];
// Create a map to store the path hierarchy
const pathMap = new Map();
// Iterate through the data
for (let i = 0; i < data.length; i++) {
const item = data[i];
const pathArr = item.path.split('.'); // Split the path into an array of sub-paths
let parent = result;
for (let j = 0; j < pathArr.length; j++) {
const subPath = pathArr[j];
// Check if the subPath exists in the parent
let child = parent.find(obj => obj.value === subPath);
if (!child) {
// Create a new child object
child = {
value: subPath,
label: capitalizeFirstLetter(subPath),
children: [],
};
// Add the child object to the parent
parent.push(child);
}
// Update the parent to be the child's children array
parent = child.children;
}
// Add the item to the final child array
parent.push({
value: item.id,
label: item.label,
});
}
return result;
}
function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
const threeDimensionalArray = convertToThreeDimensionalArray(data);
console.log(threeDimensionalArray);
501.TCP/IP五层协议是什么?【热度: 548】【网络】【出题公司: 美团】
关键词:TCP/IP协议
当提到五层协议时,通常是指TCP/IP模型的五层协议,即物理层、数据链路层、网络层、传输层和应用层。下面是对每一层的详细解释以及一些应用场景的例子:
- 物理层(Physical Layer):物理层是网络通信的最底层,它负责传输比特流,将数据从一个节点通过物理介质传输到另一个节点。它包括了电缆、光纤、无线信号等物理媒介以及相关的传输设备和接口。应用场景包括有线以太网、Wi-Fi、蓝牙、光纤通信等。
- 数据链路层(Data Link Layer):数据链路层负责将比特流划分为数据帧,并提供可靠的数据传输。它通过物理地址(MAC地址)识别网络设备,进行数据传输的控制和错误检测。应用场景包括以太网、局域网(LAN)、无线局域网(WLAN)、以及网桥、交换机等设备。
- 网络层(Network Layer):网络层负责将数据包从源主机传输到目标主机,并提供寻址、路由选择和分组转发等功能。它使用IP(Internet Protocol)协议来实现这些功能,同时支持多种路由算法和IP寻址方案。常见的应用场景包括互联网、路由器、IP地址分配等。
- 传输层(Transport Layer):传输层负责在网络中的两个主机之间提供端到端的可靠或不可靠的数据传输。主要有TCP(Transmission Control Protocol)和UDP(User Datagram Protocol)两个协议。TCP提供可靠的数据传输,适用于需要保证数据完整性和顺序的应用场景,如网页浏览、文件传输等。UDP则提供不可靠但更快速的数据传输,适用于实时性要求较高的应用场景,如音视频传输、在线游戏等。
- 应用层(Application Layer):应用层负责特定应用程序之间的通信服务。它定义了数据的格式和表示方式,包括HTTP(网页浏览)、FTP(文件传输)、SMTP(电子邮件)、DNS(域名系统)等协议。应用层协议提供了用户与网络之间的接口,使得应用程序能够通过网络进行通信。
这些是TCP/IP五层协议的详细解释和一些常见的应用场景示例。通过分层的设计,这些协议能够协同工作,实现可靠、高效的网络通信。
503.如何判断一个单向链表是否是循环链表?【热度: 975】【JavaScript】【出题公司: 美团】
关键词:循环链表
要判断一个单向链表是否成循环链表,可以使用快慢指针的方法。
快慢指针是两个指针,一个指针每次移动两个节点,另一个指针每次移动一个节点。如果链表中存在循环,那么快指针最终会追上慢指针,两个指针会相遇。
具体的判断过程如下:
- 初始化快指针和慢指针,都指向链表的头节点。
- 进入一个循环,每次迭代中,慢指针移动一个节点,快指针移动两个节点。
- 检查快指针和慢指针是否相遇,如果相遇,则链表是循环链表;如果快指针为null或者快指针的下一个节点为null,则链表不是循环链表。
下面是一个示例的实现代码(假设链表的节点定义为Node类,其中包含一个next指针指向下一个节点):
使用JavaScript实现的代码:
function isCyclicLinkedList(head) {
if (!head) {
return false;
}
let slow = head;
let fast = head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if (slow === fast) {
return true;
}
}
return false;
}
这段代码与之前给出的Python代码实现相同,使用快慢指针的方法判断单向链表是否成循环链表。只需遍历链表一次,时间复杂度为O(n),其中n是链表的节点数。
高级开发者相关问题【共计 5 道题】
489.[Vue] 你做过哪些性能优化【热度: 969】【web框架】【出题公司: Shopee】
关键词:vue 项目优化、vue 开发优化
1、v-if和v-show
- 频繁切换时使用
v-show,利用其缓存特性 - 首屏渲染时使用
v-if,如果为false则不进行渲染
2、v-for的key
- 列表变化时,循环时使用唯一不变的
key,借助其本地复用策略 - 列表只进行一次渲染时,
key可以采用循环的index
3、侦听器和计算属性
- 侦听器
watch用于数据变化时引起其他行为 - 多使用
compouter计算属性顾名思义就是新计算而来的属性,如果依赖的数据未发生变化,不会触发重新计算
4、合理使用生命周期
- 在
destroyed阶段进行绑定事件或者定时器的销毁 - 使用动态组件的时候通过
keep-alive包裹进行缓存处理,相关的操作可以在actived阶段激活
5、数据响应式处理
- 不需要响应式处理的数据可以通过
Object.freeze处理,或者直接通过this.xxx = xxx的方式进行定义 - 需要响应式处理的属性可以通过
this.$set的方式处理,而不是JSON.parse(JSON.stringify(XXX))的方式
6、路由加载方式
- 页面组件可以采用异步加载的方式
7、插件引入
- 第三方插件可以采用按需加载的方式,比如
element-ui。
8、减少代码量
- 采用
mixin的方式抽离公共方法 - 抽离公共组件
- 定义公共方法至公共
js中 - 抽离公共
css
9、编译方式
- 如果线上需要
template的编译,可以采用完成版vue.esm.js - 如果线上无需
template的编译,可采用运行时版本vue.runtime.esm.js,相比完整版体积要小大约30%
10、渲染方式
- 服务端渲染,如果是需要
SEO的网站可以采用服务端渲染的方式 - 前端渲染,一些企业内部使用的后端管理系统可以采用前端渲染的方式
11、字体图标的使用
- 有些图片图标尽可能使用字体图标
491.如何优化 DOM 树解析过程【热度: 414】【浏览器】
关键词:DOM 树解析过程、DOM 树解析、优化 DOM 树解析
以下是一些优化DOM树解析的方法:
- 减少DOM元素数量:尽可能减少页面上的DOM元素数量,可以通过合并或删除不必要的元素、使用CSS样式代替多个元素等方式来实现。
- 使用语义化的HTML结构:使用合适的HTML标签和语义化的结构,可以提高解析的效率,减少解析错误的可能性。
- 避免深层嵌套的DOM结构:避免过深的DOM嵌套,因为深层嵌套会增加DOM节点的数量,解析和渲染的时间也会增加。
- 使用外部脚本和样式表:将JavaScript代码和CSS样式表尽可能地外部引入,可以避免在解析过程中阻塞DOM树的构建。
- 使用异步加载脚本:将需要的脚本使用async或defer属性进行异步加载,可以让DOM树的解析和脚本加载并行进行,提高解析的效率。
- 优化CSS选择器:避免使用复杂的CSS选择器,因为复杂的选择器需要进行更多的计算和匹配,会影响解析的速度。
- 批量修改DOM:避免对DOM进行频繁的修改,尽量使用批量操作的方式来修改DOM,可以减少浏览器的重排和重绘。
- 使用文档片段(DocumentFragment):将需要频繁操作的DOM元素先添加到文档片段中,然后再一次性地将文档片段添加到文档中,可以减少重排和重绘的次数。
- 使用虚拟DOM:在一些前端框架中,使用虚拟DOM可以减少对真实DOM的直接操作,通过比较虚拟DOM树的差异来进行最小化的DOM操作,从而提高效率。
498.一般项目里面对请求 request 都会做哪些统一封装?【热度: 916】【网络】【出题公司: 阿里巴巴】
关键词:request封装、request封装功能、request封装作用
- 统一处理错误:可以在请求封装中统一处理错误,例如网络错误、超时等,并进行统一的错误提示或处理逻辑。
- 统一处理认证和授权:可以在请求中添加认证信息,例如在请求头中添加 token,或者在每个请求中验证用户权限。
- 统一处理请求配置:可以在请求封装中设置一些全局的请求配置,例如请求超时时间、请求头部信息等。
- 统一处理请求拦截和响应拦截:可以在请求发送前和响应返回后进行一些统一的处理,例如请求拦截器可以添加 loading 状态,响应拦截器可以对返回数据进行预处理等。
- 统一处理请求取消:可以实现一个请求取消的机制,可以取消重复的请求或者在组件卸载时取消未完成的请求,避免造成资源浪费或者潜在的问题。
- 统一处理请求缓存:可以实现请求结果的缓存机制,可以在多次请求相同数据时,直接从缓存中获取,避免重复发送请求。
- 统一处理请求重试:在网络不稳定或请求失败时,可以设置请求重试的机制,可以通过封装请求函数来自动进行重试,提高请求的成功率。
- 统一处理请求日志:可以在请求封装中添加请求日志记录,方便追踪和排查问题。
- 统一处理请求埋点:可以在请求发送前后加入一些埋点逻辑,例如统计请求的次数、请求时长等,方便进行性能分析和优化。
- 统一处理请求参数加密:可以将敏感数据进行加密,并在请求封装中进行解密操作,提高数据安全性。
- 统一处理请求数据格式化:可以对请求的数据进行格式化,例如将请求参数转换为指定的数据格式(如 JSON、XML),或者进行数据的序列化和反序列化操作。
- 统一处理请求的并发限制:可以设置请求并发数的限制,避免同时发送过多的请求导致服务器压力过大。
- 统一处理请求的响应缓存:可以对请求的响应结果进行缓存,减少对服务器的请求压力,提高性能。
- 统一处理请求的重定向:可以对请求的重定向进行统一处理,例如自动跳转到指定的页面或进行指定的操作。
- 统一处理请求的跨域问题:可以在请求封装中对跨域请求进行处理,例如设置 CORS 头信息、使用代理等方式来解决跨域问题。
499.如何封装一个请求,让其多次调用的时候,实际只发起一个请求的时候,返回同一份结果【热度: 636】【网络】【出题公司: 阿里巴巴】
关键词:defer函数、请求结果缓存在JS内存
最优解: 使用deferred思想来实现请求的等待队列,可以借助Promise和async/await语法。
下面是使用deferred思想来实现的代码示例:
class Deferred {
constructor() {
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
}
}
// 创建一个全局的锁标识
let lock = false;
// 创建一个缓存对象
const cache = {};
// 创建一个等待队列数组
const waitingRequests = [];
// 封装请求函数
async function request(url, params) {
const cacheKey = `${url}-${JSON.stringify(params)}`;
// 判断锁的状态
if (lock) {
const deferred = new Deferred();
// 如果锁已经被占用,将请求添加到等待队列中
waitingRequests.push({
deferred,
cacheKey
});
await deferred.promise;
return cache[cacheKey];
}
// 设置锁的状态为true,表示当前请求正在执行
lock = true;
try {
// 发起实际的请求
const response = await fetch(url, params);
const data = await response.json();
// 将结果存入缓存对象
cache[cacheKey] = data;
return data;
} finally {
// 释放锁,将锁的状态设置为false
lock = false;
// 处理等待队列中的请求
if (waitingRequests.length > 0) {
const request = waitingRequests.shift();
request.deferred.resolve(cache[request.cacheKey]);
}
}
}
// 调用请求函数
request('https://api.example.com/data', { method: 'GET' })
.then(data => {
// 处理请求结果
console.log(data);
});
// 同时发起另一个请求
request('https://api.example.com/data', { method: 'GET' })
.then(data => {
// 直接从缓存中获取结果,而不发起实际的请求
console.log(data);
});
在上述代码中,Deferred类用于创建一个延迟对象,其中promise属性是一个Promise对象,resolve和reject方法分别用于解决和拒绝该延迟对象的promise。通过await 关键字等待延迟对象的promise完成,当锁被占用时,将请求添加到等待队列中,并使用await等待对应的延迟对象的promise完成后再返回结果。当请求完成后,解锁并处理等待队列中的请求。
502.浏览器本身是不支持模块化的, webpack 是如何通过文件打包,让浏览器可以读取到前端各个模块的代码的?【热度: 1,153】【工程化】【出题公司: 美团】
关键词:webpack模块化、浏览器模块化支持
浏览器本身不支持模块化的特性,无法直接读取和执行模块化的代码。Webpack通过使用一种称为"模块化打包"的方式,将模块化的代码转换为浏览器可以执行的形式。
Webpack使用了一个称为"模块系统"的机制,通过对模块的依赖关系进行分析,将所有依赖的模块打包为一个或多个包含所有依赖关系的文件。这些打包后的文件被称为“打包产物”或“bundle”。
在打包过程中,Webpack会根据配置文件中的入口点(entry point)来确定应用程序的起始模块。然后,它将从该模块开始递归地解析所有的依赖关系,包括其他模块或文件。Webpack通过识别模块之间的依赖关系,将它们合并到一个或多个打包产物中。
Webpack还会对打包产物进行一系列的优化,例如代码压缩、拆分和按需加载等,以提高应用程序的性能和加载速度。
当浏览器加载打包产物时,它会执行其中包含的代码,并通过模块系统来解决模块之间的依赖关系。Webpack为浏览器提供了一个称为"运行时"的文件,它是Webpack打包产物的一部分,负责处理模块之间的依赖关系,并提供加载和执行模块的功能。
通过Webpack的打包过程,浏览器可以读取和解析打包产物中的模块化代码,并顺利执行应用程序的逻辑。这样,开发者可以在编写代码时使用模块化的语法,而无需担心浏览器的兼容性问题。