2023.11.09 - 2023.11.25 更新前端面试问题总结(16道题)
获取更多面试相关问题可以访问
github 地址: github.com/pro-collect…
gitee 地址: gitee.com/yanleweb/in…
目录:
-
中级开发者相关问题【共计 4 道题】
- 627.[React] 类组件的生命周期, 映射的 hooks 哪些 api ?【热度: 314】【web框架】【出题公司: 腾讯】
- 640.后端一次性返回树形结构数据,数据量非常大, 前端该如何处理?【热度: 171】【web应用场景】【出题公司: 小米】
- 642.前端如何设置请求超时时间 timeout【热度: 890】【网络】【出题公司: 网易】
- 643.http code 中 301 和 302 有啥区别?【热度: 721】【网络】【出题公司: 网易】
-
高级开发者相关问题【共计 8 道题】
- 623.对 babel 的理解?【热度: 551】【工程化】【出题公司: 阿里巴巴】
- 625.[Webpack] Webpack vs Vite的核心差异【热度: 620】【工程化】【出题公司: 腾讯】
- 632.在前端应用如何进行权限设计?【热度: 329】【web应用场景】【出题公司: 阿里巴巴】
- 633.token 进行身份验证了解多少?【热度: 942】【web应用场景】【出题公司: 网易】
- 635.前端日志埋点 SDK 设计思路【热度: 755】【web应用场景】【出题公司: 阿里巴巴】
- 636.对比一下 pnpm、npm、yarn 特性【热度: 399】【工程化】【出题公司: 网易】
- 638.页面加载速度提升(性能优化)应该从哪些方向来思考?【热度: 1,099】【工程化】【出题公司: 网易】
- 639.你认为组件封装的一些基本准则是什么?【热度: 625】【web应用场景】【出题公司: Shopee】
-
资深开发者相关问题【共计 4 道题】
- 628.[低代码] 代码平台一般架构设计如何【热度: 517】【工程化】【出题公司: 阿里巴巴】
- 629.[低代码] 代码平台一般底层协议是怎么设计的【热度: 263】【工程化】【出题公司: 阿里巴巴】
- 630.[低代码] 代码平台一般渲染是如何设计的?【热度: 399】【工程化】【出题公司: 阿里巴巴】
- 641.nodejs 如何充分利用多核 CPU?【热度: 725】【Nodejs】【出题公司: 阿里巴巴】
中级开发者相关问题【共计 4 道题】
627.[React] 类组件的生命周期, 映射的 hooks 哪些 api ?【热度: 314】【web框架】【出题公司: 腾讯】
关键词:生命周期映射 hooks
下面是 React 类组件的生命周期方法和对应的 Hooks API:
constructor:useState可以在函数组件中模拟类组件的constructor。在函数组件内部使用useState声明状态变量,并设置初始值。componentDidMount:useEffect用于模拟componentDidMount。通过在useEffect的回调函数中返回一个清理函数,可以模拟componentWillUnmount。componentDidUpdate:useEffect可以在函数组件中模拟componentDidUpdate。通过使用useEffect的第二个参数,可以指定依赖项的数组,当依赖项发生变化时,useEffect的回调函数会被调用。componentWillUnmount:useEffect的清理函数可以模拟componentWillUnmount。在useEffect的回调函数中返回一个清理函数,它会在组件销毁时执行。shouldComponentUpdate:React.memo可以用于函数组件的性能优化,类似于shouldComponentUpdate的功能。React.memo可以包裹一个组件,并只在组件的 props 发生变化时重新渲染。getDerivedStateFromProps:useState可以通过提供 setter 函数,将 props 的值作为 state 的初始值。在组件重新渲染时,useState不会重置 state 的值。
并不是每一个生命周期方法都有与之对应的 Hooks API。 Hooks 是为了解决函数式组件的状态管理和副作用问题而引入的新特性,因此 Hooks 在某种程度上替换了类组件的生命周期方法。
下面是一个使用表格方式对比 React 类组件的生命周期方法和对应的 Hooks API:
| 生命周期方法 | Hooks API |
|---|---|
| constructor | useState |
| componentDidMount | useEffect(第二个参数为空数组) |
| componentDidUpdate | useEffect(包含依赖项的数组) |
| componentWillUnmount | useEffect 的清理函数 |
| shouldComponentUpdate | React.memo |
| getDerivedStateFromProps | useState(通过提供 setter 函数) |
640.后端一次性返回树形结构数据,数据量非常大, 前端该如何处理?【热度: 171】【web应用场景】【出题公司: 小米】
关键词:大数据处理
当后端一次性返回的树形结构数据量非常大,导致前端一次性计算和渲染会栈溢出的情况时,可以考虑以下几种处理方式:
- 分批处理:将大量的树形数据分为多个批次进行处理和渲染。前端可以通过递归或循环的方式,每次处理一部分数据,并在渲染完成后再处理下一部分数据。这样可以避免一次性处理大量数据造成栈溢出的问题。
- 异步处理:使用异步处理的方式进行数据的计算和渲染。前端可以利用JavaScript的异步特性,将数据处理和渲染任务分为多个异步任务,并通过事件循环机制依次执行这些任务。这样可以避免一次性计算和渲染大量数据导致栈溢出的问题。
- 虚拟化渲染:使用虚拟化渲染技术,只渲染当前可见区域的树节点,而不是全部节点。可以根据页面的滚动位置和用户操作,只渲染当前需要展示的节点,而对于不可见的节点只保留其占位符。这样可以减少实际渲染的节点数量,降低内存占用和渲染时间。
- 数据分级处理:对于树形结构数据,可以考虑对数据进行分级处理。将数据根据节点的层级关系进行分组,每次只处理和渲染当前层级的节点数据。这样可以减少每次处理的数据量,降低栈溢出的风险。
根据具体的业务需求和技术实现情况,可以选择适合的处理方式来解决栈溢出问题。同时,也可以结合多种处理方式来提高页面性能和用户体验。
642.前端如何设置请求超时时间 timeout【热度: 890】【网络】【出题公司: 网易】
关键词:请求超时时间
1. axios全局设置网络超时
axios.defaults.timeout = 10 * 1000; // 10s
2. 单独对某个请求设置网络超时
axios.post(url, params, {timeout: 1000}) .then(res => { console.log(res); }) .catch(err=> { console.log(err); }) })
3.webpack的dev的proxyTable的超时时间设置
dev: {
// Paths
assetsSubDirectory: 'static', // 静态资源文件夹
assetsPublicPath: '/', // 发布路径
// 代理配置表,在这里可以配置特定的请求代理到对应的API接口
// 使用方法:https://vuejs-templates.github.io/webpack/proxy.html
proxyTable: {
'/api': {
timeout: 30000, // 请求超时时间
target: 'http://127.0.0.1:3006', // 目标接口域名
changeOrigin: true, // 是否跨域
pathRewrite: {
'^/api': '' // 重写接口
}
},
// Various Dev Server settings
host: 'localhost', // can be overwritten by process.env.HOST
port: 4200, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
}
4.axios请求超时自动重新请求
有时候因项目需求,要在接口请求超时或者获取数据失败时,重新请求1次,或者更多次。具体的配置步骤和方法如下:
因为是要在请求超时或者获取数据失败时,进行重新请求设置,那么我们肯定是要在请求返回拦截器里面设置
import axios from "axios";
const Axios = axios.create({
// 下面两个属性,用来设置,请求失败或者超时,自动重新请求的次数和间隙时间
retry: 2, // 请求次数
retryInterval: 1000 // 求期间隙
......
});
//请求前拦截
Axios.interceptors.request.use(config => {
return config
},
function(error) {
return Promise.reject(error)
}
);
//请求后返回数据拦截
Axios.interceptors.response.use(res => {
return res
},
function axiosRetryInterceptor(res) {
var config = res.config;
//如果配置不存在或重试属性未设置,抛出promise错误
if (!config || !config.retry) return Promise.reject(res);
//设置一个变量记录重新请求的次数
config.retryCount = config.retryCount || 0;
// 检查重新请求的次数是否超过我们设定的请求次数
if (config.retryCount >= config.retry) {
return Promise.reject(res);
}
//重新请求的次数自增
config.retryCount += 1;
// 创建新的Promise来处理重新请求的间隙
var back = new Promise(function(resolve) {
console.log("接口" + config.url + "请求超时,重新请求")
setTimeout(function() {
resolve();
}, config.retryInterval || 1);
});
//返回axios的实体,重试请求
return back.then(function() {
return Axios(config);
});
}
);
export default Axios
643.http code 中 301 和 302 有啥区别?【热度: 721】【网络】【出题公司: 网易】
关键词:http code 码
在 HTTP 协议中,301和302是两种重定向状态码。它们的区别如下:
- 301 Moved Permanently (永久重定向):当服务器返回301状态码时,表示所请求的资源已经被永久移动到了一个新的位置。浏览器在接收到301响应后,会自动将请求的 URL 地址更新为新的位置,并且将响应缓存起来。以后的请求将会直接访问新的位置。这意味着搜索引擎会将原始 URL 的权重转移到新的位置,且用户访问的 URL 也会发生更改。
- 302 Found (临时重定向):当服务器返回302状态码时,表示所请求的资源暂时被移动到了一个新的位置。与301不同的是,浏览器在接收到302响应后,不会自动更新请求的 URL 地址,而是会保持原始 URL 地址不变。对于搜索引擎而言,会将权重保留在原始 URL 上,而不会转移到新的位置。通常情况下,浏览器会跳转到新的位置,用户会看到新的 URL 地址。
以下是301和302状态码的比较表格:
| 特征 | 301 Moved Permanently | 302 Found |
|---|---|---|
| 持久性 | 是 | 否 |
| 重定向类型 | 永久重定向 | 临时重定向 |
| URL 更新 | 是,浏览器会自动更新 | 否,浏览器保持原始 URL 不变 |
| 响应缓存 | 是,浏览器会缓存响应 | 否,每次请求都会访问原始 URL |
| 搜索引擎权重转移 | 是,权重会转移到新位置 | 否,权重保留在原始 URL 上 |
| 用户可见性 | 可能会看到新的 URL 地址 | 可能会看到新的 URL 地址 |
高级开发者相关问题【共计 8 道题】
623.对 babel 的理解?【热度: 551】【工程化】【出题公司: 阿里巴巴】
关键词:babel 作用、为何要使用 babel
Babel 是一个非常流行的 JavaScript 编译器,用于将最新版本的 ECMAScript 代码转换为向后兼容的 JavaScript 代码,以便在旧版浏览器或环境中运行。
以下是对 Babel 的理解:
- 语法转换:Babel 可以将使用了最新 ECMAScript 标准的代码转换为被广泛支持的旧版 JavaScript 代码。例如,将使用了箭头函数、解构赋值等语法的代码转换为使用 function 关键字和传统赋值的等效代码。
- Polyfill:Babel 可以通过添加 Polyfill 来支持新的全局函数、方法和对象,以确保代码在旧版本的浏览器中正常运行。通过使用 Polyfill,Babel 可以在浏览器中模拟缺失的特性,使旧版浏览器能够运行使用了这些特性的代码。
- 插件系统:Babel 的核心功能可以通过插件进行扩展和定制。Babel 提供了众多的插件,用于实现不同的转换和功能。开发者可以根据自己的需求选择和配置相应的插件,以便将代码转换为特定的目标环境。
- 预设(Presets):Babel 提供了预设的概念,可以一次性地配置一组插件,以实现特定的转换目标。预设是一组插件的集合,可以根据应用程序的需要进行选择和配置。常见的预设包括 "@babel/preset-env" (根据目标环境自动选择转换规则)和 "@babel/preset-react"(用于处理 React 相关的代码)。
- 与构建工具的集成:Babel 可以与构建工具(如 webpack、Rollup 等)无缝集成,作为其转换代码的一部分。通过配置构建工具,可以让 Babel 在构建过程中自动处理源代码,将其转换为目标代码。
总之,Babel 是一个功能强大的 JavaScript 编译器,可以将使用最新 ECMAScript 标准的代码转换为向后兼容的 JavaScript 代码,从而在旧版浏览器或环境中运行。通过插件和预设的配置,Babel 提供了高度的灵活性,使开发者能够根据项目需求定制转换规则。
追问:为何要使用 babel
使用 Babel 的主要原因是为了解决 JavaScript 代码的兼容性问题。以下是一些使用 Babel 的主要理由:
- 兼容旧版浏览器:不同的浏览器对 JavaScript 的支持程度不同,特别是旧版浏览器可能不支持最新的 ECMAScript 标准。通过使用 Babel,可以将使用了最新语法的代码转换为等效的旧版 JavaScript 语法,使代码能够在旧版浏览器中正常运行。
- 支持新特性:JavaScript 不断演进,每年都会发布新的 ECMAScript 标准,引入了很多有用的特性和语法糖。使用 Babel 可以提前使用这些新特性,而不用等待浏览器的支持。Babel 可以将这些新特性转换为旧版 JavaScript 语法,以便在当前的浏览器环境中使用。
- 模块化支持:Babel 可以转换模块化导入和导出的语法,使得开发者可以在浏览器中使用模块化的方式组织和管理代码。这样可以提高代码的可维护性和重用性。
625.[Webpack] Webpack vs Vite的核心差异【热度: 620】【工程化】【出题公司: 腾讯】
关键词:Webpack vs Vite 差异
构建速度:
- Webpack: Webpack的构建速度相对较慢,尤其在大型项目中,因为它需要分析整个依赖图,进行多次文件扫描和转译。
- Vite: Vite以开发模式下的极速构建著称。它利用ES模块的特性,只构建正在编辑的文件,而不是整个项目。这使得它在开发环境下几乎是即时的。
开发模式:
- Webpack: Webpack通常使用热模块替换(HMR)来实现快速开发模式,但配置相对复杂。
- Vite: Vite的开发模式非常轻量且快速,支持HMR,但无需额外配置,因为它默认支持。
配置复杂度:
- Webpack: Webpack的配置相对复杂,特别是在处理不同类型的资源和加载器时。
- Vite: Vite鼓励零配置,使得项目起步非常简单,但同时也支持自定义配置,使其适用于复杂项目。
插件生态:
- Webpack: Webpack拥有庞大的插件生态系统,适用于各种不同的需求。
- Vite: Vite也有相当数量的插件,但相对较小,因为它的开发模式和构建方式减少了对一些传统插件的需求。
编译方式:
- Webpack: Webpack使用了多种加载器和插件来处理不同类型的资源,如JavaScript、CSS、图片等。
- Vite: Vite利用ES模块原生支持,使用原生浏览器导入来处理模块,不需要大规模的编译和打包。
应用场景:
- Webpack: 适用于复杂的大型项目,特别是需要大量自定义配置和复杂构建管道的项目。
- Vite: 更适用于小到中型项目,或者需要快速开发原型和小型应用的场景。
打包原理:
- Webpack: Webpack的打包原理是将所有资源打包成一个或多个bundle文件,通常是一个JavaScript文件。
- Vite: Vite的打包原理是保持开发时的模块化结构,使用浏览器原生的导入机制,在生产环境中进行代码分割和优化。
优缺点:
-
Webpack:
- 优点:灵活、强大、适用于复杂场景、庞大的插件生态。
- 缺点:构建速度较慢、配置复杂、开发体验不如Vite流畅。
-
Vite:
- 优点:极快的开发构建速度、零配置启动、原生ES模块支持、适用于小型项目和快速原型开发。
- 缺点:插件生态相对较小、不太适用于复杂大型项目。
参考文档
632.在前端应用如何进行权限设计?【热度: 329】【web应用场景】【出题公司: 阿里巴巴】
关键词:权限设计
在前端应用的权限设计中,以下是一些建议:
角色与权限分离
将用户的权限分为不同的角色,每个角色拥有特定的权限。 这样可以简化权限管理,并且当需求变化时,只需要调整角色的权限,而不需要逐个修改用户的权限。
在角色与权限分离的设计中,可以按照以下几个步骤进行
- 确定权限集合:首先,需要确定系统中所有的权限,包括操作、功能、资源等。可以根据系统需求、业务流程等确定权限的粒度和层次结构。
- 确定角色集合:根据系统的角色需求,确定不同的角色,例如管理员、普通用户、编辑等。每个角色代表一组权限的集合,可以根据业务需求进行划分。
- 分配权限给角色:将权限与角色进行关联,确定每个角色具备哪些权限。可以通过角色-权限的映射表或者通过角色组的方式进行管理。
- 用户与角色关联:将用户与角色进行关联,确定每个用户属于哪些角色。可以通过用户-角色的映射表或者通过用户组的方式进行管理。
- 权限验证:在系统中,根据用户的角色和权限配置进行权限验证。在用户进行操作或访问受限资源时,根据用户的角色与权限进行验证,决定是否允许执行相应的操作。
功能级权限控制
对于敏感操作或者需要权限控制的功能,需要在前端实现功能级的权限控制。 通过在代码中判断用户是否拥有执行该功能的权限,来决定是否展示或者禁用相关功能。
功能级权限控制是指在系统中对用户进行细粒度的权限控制,即控制用户是否能够执行某个具体的功能或操作。 以下是功能级权限控制的设计步骤:
- 确定功能点:首先,需要明确系统中的各个功能点,例如新增、编辑、删除、查询等。将系统中的所有功能进行明确定义和分类。
- 定义权限:对于每个功能点,定义相应的权限。权限可以使用权限名或者权限码进行标识,例如新增权限可以使用"add"或者权限码"001"进行标识。
- 角色与权限关联:将权限与角色进行关联。确定每个角色具备哪些权限。可以使用角色-权限的映射表进行管理。
- 用户与角色关联:将用户与角色进行关联。确定每个用户属于哪些角色。可以使用用户-角色的映射表进行管理。
- 权限验证:在系统中,对用户进行权限验证。当用户进行某个功能操作时,根据用户的角色与权限进行验证,决定是否允许执行该操作。
- 权限控制界面:提供一个权限控制界面,用于管理角色与权限的关联。管理员可以通过该界面对角色的权限进行配置和管理。
- 动态权限控制:可以考虑将权限控制设计成动态的。即在系统运行时,可以根据用户角色的配置动态控制用户是否具备某个功能的权限。 这样可以灵活地根据业务需求进行权限的调整。
路由级权限控制
对于不同的页面或路由,可以根据用户的角色或权限来进行权限控制。在前端路由中配置权限信息,当用户访问特定路由时,前端会检查用户是否具备访问该路由的权限。
前端路由级权限控制是指在前端页面中根据用户的权限配置,控制用户是否可以访问某个路由或者页面。 以下是前端路由级权限控制的设计方案:
- 定义路由表:首先,需要定义系统中的所有路由和对应的页面组件。将路由按照功能模块进行分类,方便后续的权限管理。
- 定义权限配置:对于每个路由或者页面,定义相应的权限配置。可以使用权限名或者权限码进行标识,例如"add"、"edit"等。可以将权限配置与路由表一起存放在一个配置文件中,或者存放在后端数据库中。
- 获取用户权限:在登录成功后,从后端获取当前用户的权限信息。可以将用户的权限信息存放在前端的状态管理库(如Vuex或Redux)中,以便在全局范围内进行访问。
- 路由守卫:使用前端路由守卫机制,在路由跳转前进行权限验证。在路由守卫中,根据当前用户的权限信息和路由配置进行判断,决定是否允许用户访问该路由。如果用户没有相应的权限,可以进行跳转到无权限提示页面或者其他处理方式。
- 权限控制组件:可以创建一个权限控制组件,在需要进行权限控制的路由组件上使用该组件进行包裹。该组件可以根据当前用户的权限和路由配置,动态显示或隐藏路由组件。
- 动态路由:对于一些有权限控制的路由,可以在用户登录时根据权限配置动态生成。根据用户的权限配置,过滤路由表,生成用户可以访问的路由列表,并将该列表添加到路由配置中。
动态权限管理
在前端应用中,可以实现动态权限管理,即在用户登录时从服务器获取用户的权限信息,并在前端进行缓存。这样可以保证用户权限的实时性,同时也便于后端对权限进行调整和管理。
UI级的权限控制
对于某些敏感信息或操作,可以通过前端的界面设计来进行权限控制。例如,隐藏某些敏感字段或操作按钮,只对具有相应权限的用户可见或可操作。
异常处理与安全验证
在前端应用中,需要实现异常处理机制,当用户越权操作时,需要给予相应提示并记录日志。同时,对于敏感操作,需要进行二次验证,例如通过输入密码或短信验证码等方式进行安全验证。
安全性考虑
在设计前端应用的权限时,需要考虑安全性,例如防止跨站脚本攻击(XSS)、跨站请求伪造(CSRF)等攻击方式。可以采用合适的安全措施,如输入验证、加密传输等来保护应用的安全性。
综上所述,前端应用的权限设计应该考虑角色与权限分离、功能级与路由级的权限控制、动态权限管理、UI级的权限控制、异常处理与安全验证以及安全性考虑等方面。通过合理的权限设计,可以确保系统的安全性和用户权限的灵活管理。
633.token 进行身份验证了解多少?【热度: 942】【web应用场景】【出题公司: 网易】
关键词:身份验证、token 验证
token 概念和作用
Token是一种用于身份验证和授权的令牌。在Web应用程序中,当用户进行登录或授权时,服务器会生成一个Token并将其发送给客户端。客户端在后续的请求中将Token作为身份凭证携带,以证明自己的身份。
Token可以是一个字符串,通常是经过加密和签名的,以确保其安全性和完整性。服务器收到Token后,会对其进行解析和验证,以验证用户的身份并授权对特定资源的访问权限。
Token的使用具有以下特点:
- 无状态:服务器不需要在数据库中存储会话信息,所有必要的信息都包含在Token中。
- 可扩展性:Token可以存储更多的用户信息,甚至可以包含自定义的数据。
- 安全性:Token可以使用加密算法进行签名,以确保数据的完整性和安全性。
- 跨域支持:Token可以在跨域请求中通过在请求头中添加Authorization字段进行传递。
Token在前后端分离的架构中广泛应用,特别是在RESTful API的身份验证中常见。它比传统的基于Cookie的会话管理更灵活,并且适用于各种不同的客户端,例如Web、移动应用和第三方接入等。
token 一般在客户端存在哪儿
Token一般在客户端存在以下几个地方:
- Cookie:Token可以存储在客户端的Cookie中。服务器在响应请求时,可以将Token作为一个Cookie发送给客户端,客户端在后续的请求中会自动将Token包含在请求的Cookie中发送给服务器。
- Local Storage/Session Storage:Token也可以存储在客户端的Local Storage或Session Storage中。这些是HTML5提供的客户端存储机制,可以在浏览器中长期保存数据。
- Web Storage API:除了Local Storage和Session Storage,Token也可以使用Web Storage API中的其他存储机制,比如IndexedDB、WebSQL等。
- 请求头:Token也可以包含在客户端发送的请求头中,一般是在Authorization头中携带Token。
需要注意的是,无论将Token存储在哪个地方,都需要采取相应的安全措施,如HTTPS传输、加密存储等,以保护Token的安全性。
存放在 cookie 就安全了吗?
存放在Cookie中相对来说是比较常见的做法,但是并不是最安全的方式。存放在Cookie中的Token可能存在以下安全风险:
- 跨站脚本攻击(XSS) :如果网站存在XSS漏洞,攻击者可以通过注入恶意脚本来获取用户的Cookie信息,包括Token。攻击者可以利用Token冒充用户进行恶意操作。
- 跨站请求伪造(CSRF) :攻击者可以利用CSRF漏洞,诱使用户在已经登录的情况下访问恶意网站,该网站可能利用用户的Token发起伪造的请求,从而执行未经授权的操作。
- 不可控的访问权限:将Token存放在Cookie中,意味着浏览器在每次请求中都会自动携带该Token。如果用户在使用公共计算机或共享设备时忘记退出登录,那么其他人可以通过使用同一个浏览器来访问用户的账户。
为了增加Token的安全性,可以采取以下措施:
- 使用HttpOnly标识:将Cookie设置为HttpOnly,可以防止XSS攻击者通过脚本访问Cookie。
- 使用Secure标识:将Cookie设置为Secure,只能在通过HTTPS协议传输时发送给服务器,避免明文传输。
- 设置Token的过期时间:可以设置Token的过期时间,使得Token在一定时间后失效,减少被滥用的风险。
- 使用其他存储方式:考虑将Token存储在其他地方,如Local Storage或Session Storage,并采取加密等额外的安全措施保护Token的安全性。
cookie 和 token 的关系
Cookie和Token是两种不同的概念,但它们在身份验证和授权方面可以有关联。
Cookie是服务器在HTTP响应中通过Set-Cookie标头发送给客户端的一小段数据。客户端浏览器将Cookie保存在本地,然后在每次对该服务器的后续请求中将Cookie作为HTTP请求的一部分发送回服务器。Cookie通常用于在客户端和服务器之间维护会话状态,以及存储用户相关的信息。
Token是一种用于身份验证和授权的令牌。它是一个包含用户身份信息的字符串,通常是服务器生成并返回给客户端。客户端在后续的请求中将Token作为身份凭证发送给服务器,服务器通过验证Token的有效性来确认用户的身份和权限。
Cookie和Token可以结合使用来实现身份验证和授权机制。服务器可以将Token存储在Cookie中,然后发送给客户端保存。客户端在后续的请求中将Token作为Cookie发送给服务器。服务器通过验证Token的有效性来判断用户的身份和权限。这种方式称为基于Cookie的身份验证。另外,也可以将Token直接存储在请求的标头中,而不是在Cookie中进行传输,这种方式称为基于Token的身份验证。
需要注意的是,Token相对于Cookie来说更加灵活和安全,可以实现跨域身份验证,以及客户端和服务器的完全分离。而Cookie则受到一些限制,如跨域访问限制,以及容易受到XSS和CSRF攻击等。因此,在实现身份验证和授权机制时,可以选择使用Token替代或辅助Cookie。
635.前端日志埋点 SDK 设计思路【热度: 755】【web应用场景】【出题公司: 阿里巴巴】
关键词:前端埋点监控、埋点 SDK 设计
前端日志埋点 SDK 设计思路
既然涉及到了日志和埋点,分析一下需求是啥:
- 自动化上报 页面 PV、UV。 如果能自动化上报页面性能, 用户点击路径行为,就更好了。
- 自动上报页面异常。
- 发送埋点信息的时候, 不影响性能, 不阻碍页面主流程加载和请求发送。
- 能够自定义日志发送, 日志 scope、key、value。
SDK 设计
sdk 的设计主要围绕以下几个话题来进行:
- SDK 初始化
- 数据发送
- 自定义错误上报
- 初始化错误监控
- 自定义日志上报
最基本使用
import StatisticSDK from 'StatisticSDK';
// 全局初始化一次
window.insSDK = new StatisticSDK('uuid-12345');
<button onClick={() => {
window.insSDK.event('click', 'confirm');
...// 其他业务代码
}}>确认</button>
数据发送
数据发送是一个最基础的api,后面的功能都要基于此进行。这里介绍使用 navigator.sendBeacon 来发送请求;具体原因如下
使用 navigator.sendBeacon() 方法有以下优势:
- 异步操作:
navigator.sendBeacon()方法会在后台异步地发送数据,不会阻塞页面的其他操作。这意味着即使页面正在卸载或关闭,该方法也可以继续发送数据,确保数据的可靠性。 - 高可靠性:
navigator.sendBeacon()方法会尽可能地保证数据的传输成功。它使用浏览器内部机制进行发送,具有更高的可靠性和稳定性。即使在网络连接不稳定或断开的情况下,该方法也会尝试发送数据,确保数据的完整性。 - 自动化处理:
navigator.sendBeacon()方法会自动处理数据的发送细节,无需手动设置请求头、响应处理等。它会将数据封装成 POST 请求,并自动设置请求头和数据编码,使开发者能够更专注于业务逻辑的处理。 - 跨域支持:
navigator.sendBeacon()方法支持跨域发送数据。在一些情况下,例如使用第三方统计服务等,可能需要将数据发送到其他域名下的服务器,此时使用navigator.sendBeacon()方法可以避免跨域问题。
需要注意的是,navigator.sendBeacon() 方法发送的数据是以 POST 请求的形式发送到服务器,通常会将数据以表单数据或 JSON 格式进行封装。因此,后端服务器需要正确处理这些数据,并进行相应的解析和处理。
简单介绍一下 navigator.sendBeacon 用法
语法:
navigator.sendBeacon(url);
navigator.sendBeacon(url, data);
参数
-
url
- url 参数表明 data 将要被发送到的网络地址。
-
data 可选
- data 参数是将要发送的
ArrayBuffer、ArrayBufferView、Blob、DOMString、FormData 或 URLSearchParams类型的数据。
- data 参数是将要发送的
发送代码实现如下
class StatisticSDK {
constructor(productID, baseURL) {
this.productID = productID;
this.baseURL = baseURL;
}
send(query = {}) {
query.productID = this.productID;
let data = new URLSearchParams();
for (const [key, value] of Object.entries(query)) {
data.append(key, value);
}
navigator.sendBeacon(this.baseURL, data);
}
}
用户行为与日志上报
用户行为主要涉及到的是事件上报和 pv 曝光, 借助 send 实现即可。
class StatisticSDK {
constructor(productID, baseURL) {
this.productID = productID;
this.baseURL = baseURL;
}
send(query = {}) {
query.productID = this.productID;
let data = new URLSearchParams();
for (const [key, value] of Object.entries(query)) {
data.append(key, value);
}
navigator.sendBeacon(this.baseURL, data);
}
event(key, value = {}) {
this.send({ event: key, ...value })
}
pv() {
this.event('pv')
}
}
性能上报
性能主要涉及的 api 为 performance.timing 里面的时间内容;
class StatisticSDK {
constructor(productID, baseURL) {
this.productID = productID;
this.baseURL = baseURL;
}
send(query = {}) {
query.productID = this.productID;
let data = new URLSearchParams();
for (const [key, value] of Object.entries(query)) {
data.append(key, value);
}
navigator.sendBeacon(this.baseURL, data);
}
// ....
initPerformance() {
this.send({ event: 'performance', ...performance.timing })
}
}
错误上报
错误上报分两类:
一个是 dom 操作错误与 JS 错误报警, 也是常说的运行时报错。 该类报错直接可以通过 addEventListener('error') 监控即可;
另一个是Promise内部抛出的错误是无法被error捕获到的,这时需要用unhandledrejection事件。
class StatisticSDK {
constructor(productID, baseURL) {
this.productID = productID;
this.baseURL = baseURL;
}
send(query = {}) {
query.productID = this.productID;
let data = new URLSearchParams();
for (const [key, value] of Object.entries(query)) {
data.append(key, value);
}
navigator.sendBeacon(this.baseURL, data);
}
// ....
error(err, errInfo = {}) {
const { message, stack } = err;
this.send({ event: 'error', message, stack, ...errInfo })
}
initErrorListenner() {
window.addEventListener('error', event => {
this.error(error);
})
window.addEventListener('unhandledrejection', event => {
this.error(new Error(event.reason), { type: 'unhandledrejection' })
})
}
}
React 和 vue 错误边界
错误边界是希望当应用内部发生渲染错误时,不会整个页面崩溃。我们提前给它设置一个兜底组件,并且可以细化粒度,只有发生错误的部分被替换成这个「兜底组件」,不至于整个页面都不能正常工作。
React
可以使用类组件错误边界来进行处理, 涉及到的生命周期为:getDerivedStateFromError 和 componentDidCatch;
// 定义错误边界
class ErrorBoundary extends React.Component {
state = { error: null }
static getDerivedStateFromError(error) {
return { error }
}
componentDidCatch(error, errorInfo) {
// 调用我们实现的SDK实例
insSDK.error(error, errorInfo)
}
render() {
if (this.state.error) {
return <h2>Something went wrong.</h2>
}
return this.props.children
}
}
...
<ErrorBoundary>
<BuggyCounter/>
</ErrorBoundary>
Vue
vue也有一个类似的生命周期来做这件事:errorCaptured
Vue.component('ErrorBoundary', {
data: () => ({ error: null }),
errorCaptured(err, vm, info) {
this.error = `${err.stack}\n\nfound in ${info} of component`
// 调用我们的SDK,上报错误信息
insSDK.error(err, info)
return false
},
render(h) {
if (this.error) {
return h('pre', { style: { color: 'red' } }, this.error)
}
return this.$slots.default[0]
}
})
...
<error-boundary>
<buggy-counter/>
</error-boundary>
参考文档
636.对比一下 pnpm、npm、yarn 特性【热度: 399】【工程化】【出题公司: 网易】
关键词:pnpm、npm、yarn 特性对比
| 功能 | pnpm | Yarn | npm |
|---|---|---|---|
| 工作区支持 | ✔️ | ✔️ | ✔️ |
| 隔离的 node_modules | ✔️ - 默认支持 | ✔️ | ✔️ |
| 提升的 node_modules | ✔️ | ✔️ | ✔️ - 默认支持 |
| 自动安装对等依赖 | ✔️ | ❌ | ✔️ |
| Plug'n'Play | ✔️ | ✔️ - 默认支持 | ❌ |
| 零安装 | ❌ | ✔️ | ❌ |
| 修补依赖 | ✔️ | ✔️ | ❌ |
| 管理 Node.js 版本 | ✔️ | ❌ | ❌ |
| 有一个锁文件 | ✔️ - 使用 pnpm-lock.yaml | ✔️ - 使用 yarn.lock | ✔️ - 使用 package-lock.json |
| 覆盖支持 | ✔️ | ✔️ - 通过 resolutions 配置 | ✔️ |
| 可寻址存储 | ✔️ | ❌ | ❌ |
| 动态包执行 | ✔️ - 通过 pnpm dlx | ✔️ - 通过 yarn dlx | ✔️ - 通过 npx |
| 副作用缓存 | ✔️ | ❌ | ❌ |
| 列出许可证 | ✔️ - 通过 pnpm licenses list | ✔️ - 通过插件 | ❌ |
638.页面加载速度提升(性能优化)应该从哪些反向来思考?【热度: 1,099】【工程化】【出题公司: 网易】
关键词:性能提升、加载优化
页面加载优化
「页面加载链路+流程优化+协作方」的多级分类思考
-
页面启动
- service worker 缓存重要的静态资源
- 页面保活
-
资源加载
-
网络连接
-
NDS
- 减少 NDS 解析
- NDA 预解析
-
HTTP
- 开启 HTTP2 多路复用
- 优化核心请求链路
-
-
HTML 加载
-
内容优化
- 代码压缩
- 代码精简(tailwindcss)
- 服务端渲染 SSG
-
流程优化
- 服务端渲染 SSR
- 流式渲染
- 预渲染
-
-
静态资源加载
-
内容优化
- JS、CSS 代码压缩
- 均衡资源包体积:复用代码抽离为一份资源打包、同时开启
- 精简代码
- 雪碧图
- 动态图片降质量
- 动态 polyfill (根据浏览器的支持情况,动态加载需要的 polyfill(填充)脚本)
- 不常变的资源单独打包
- 根据浏览器版本打包, 高版本浏览器, 直接使用 es6 作为输出文件
-
流程优化
- 配置前端缓存: 资源、请求
- 使用 CDN
- CDN 优化
- 协调资源加载优先级
- 动态资源转静态 CDN 链接加载(例如大图片等)
- 静态资源使用 service worker 离线存储
- 非首屏资源懒加载
- 资源和业务请求预加载
- 微前端加载应用
- 微组件加载核心模块资源
-
-
-
代码执行
-
减少执行
- 减少重复渲染
- 大体量计算场景, 尽量使用缓存函数
- 使用防抖节流
-
速度提升
- 使用 worker 多线程加速
- 充分利用异步请求的线下之间来进行核心代码的加载或者执行
- wasm 处理大量计算场景
- 渲染高耗时场景, 迁移到 canvas 、虚拟 dom 等
- 动态渲染:动态渲染可视区内容, 例如图片懒加载等;
-
流程优化
- 非首屏模块, 延迟加载与渲染
- longtask 任务拆分执行
- 利用请求闲暇时间, 请求后续页面资源
-
-
数据获取
-
内容优化
- 减少请求、合并请求、BFF
- 首屏数据使用模板注入到前端应用
-
流程优化
- 数据预请求
- 常量数据缓存
- 非首屏请求,延迟到首屏加载完成之后请求
- 请求并行
-
-
渲染相关
- 虚拟列表
- 延迟渲染
- 减少重绘重排
- 图片预加载到内存
639.你认为组件封装的一些基本准则是什么?【热度: 625】【web应用场景】【出题公司: Shopee】
关键词:组件封装原则
组件封装的一些基本准则包括:
- 单一职责原则:一个组件应该具有单一的功能,并且只负责完成该功能,避免组件过于庞大和复杂。
- 高内聚低耦合:组件内部的各个部分之间应该紧密相关,组件与其他组件之间应该尽量解耦,减少对外部的依赖。
- 易用性:组件应该易于使用,提供清晰的接口和文档,使用户能够方便地使用组件。
- 可扩展性:组件应该具有良好的扩展性,能够方便地添加新的功能或进行修改,同时不影响已有的功能。
- 可重用性:组件应该是可重用的,能够在多个项目中使用,减少重复开发的工作量。
- 高效性:组件应该具有高性能和低资源消耗的特点,不会成为整个系统的性能瓶颈。
- 安全性:组件应该具有安全性,能够防止恶意使用或攻击。
- 可测试性:组件应该容易进行单元测试和集成测试,以保证组件的质量和稳定性。
资深开发者相关问题【共计 4 道题】
628.[低代码] 代码平台一般架构设计如何【热度: 517】【工程化】【出题公司: 阿里巴巴】
关键词:代码平台
代码平台 - 架构综述
分层架构描述
自下而上分别是协议 - 引擎 - 生态 - 平台。
- 底层协议栈定义的是标准,标准的统一让上层产物的互通成为可能。
- 引擎是对协议的实现,同时通过能力的输出,向上支撑生态开放体系,提供各种生态扩展能力。
- 生态就好理解了,是基于引擎核心能力上扩展出来的,比如物料、设置器、插件等,还有工具链支撑开发体系。
- 最后,各个平台基于引擎内核以及生态中的产品组合、衔接形成满足其需求的低代码平台。
引擎内核简述
引擎生态简述
参考
lowcode-engine.cn/site/docs/g…
629.[低代码] 代码平台一般底层协议是怎么设计的【热度: 263】【工程化】【出题公司: 阿里巴巴】
关键词:代码平台协议设计
低代码引擎体系基于三份协议来构建:
参考文档 lowcode-engine.cn/site/docs/g…
630.[低代码] 代码平台一般渲染是如何设计的?【热度: 399】【工程化】【出题公司: 阿里巴巴】
关键词:代码平台渲染设计
渲染设计
渲染核心本质就是: [schema] + [组件] = [页面]
整体架构如下
- 协议层:基于《低代码引擎搭建协议规范》 产出的 Schema 作为我们的规范协议。
- 能力层:提供组件、区块、页面等渲染所需的核心能力,包括 Props 解析、样式注入、条件渲染等。
- 适配层:由于我们使用的运行时框架不是统一的,所以统一使用适配层将不同运行框架的差异部分,通过接口对外,让渲染层注册/适配对应所需的方法。能保障渲染层和能力层直接通过适配层连接起来,能起到独立可扩展的作用。
- 渲染层:提供核心的渲染方法,由于不同运行时框架提供的渲染方法是不同的,所以其通过适配层进行注入,只需要提供适配层所需的接口,即可实现渲染。
- 应用层:根据渲染层所提供的方法,可以应用到项目中,根据使用的方法和规模即可实现应用、页面、区块的渲染。
设计模式渲染(Simulator)
设计模式渲染就是将编排生成的《搭建协议》渲染成视图的过程,视图是可以交互的,所以必须要处理好内部数据流、生命周期、事件绑定、国际化等等。 也称为画布的渲染,画布是 UI 编排的核心,它一般融合了页面的渲染以及组件/区块的拖拽、选择、快捷配置。 画布的渲染和预览模式的渲染的区别在于,画布的渲染和设计器之间是有交互的。 所以在这里我们新增了一层 Simulator 作为设计器和渲染的连接器。 Simulator 是将设计器传入的 DocumentModel 和组件/库描述转成相应的 Schema 和 组件类。再调用 Render 层完成渲染。我们这里介绍一下它提供的能力。
- Project:位于顶层的 Project,保留了对所有文档模型的引用,用于管理应用级 Schema 的导入与导出。
- Document:文档模型包括 Simulator 与数据模型两部分。Simulator 通过一份 Simulator Host 协议与数据模型层通信,达到画布上的 UI 操作驱动数据模型变化。通过多文档的设计及多 Tab 交互方式,能够实现同时设计多个页面,以及在一个浏览器标签里进行搭建与配置应用属性。
- Simulator:模拟器主要承载特定运行时环境的页面渲染及与模型层的通信。
- Node:节点模型是对可视化组件/区块的抽象,保留了组件属性集合 Props 的引用,封装了一系列针对组件的 API,比如修改、编辑、保存、拖拽、复制等。
- Props:描述了当前组件所维系的所有可以「设计」的属性,提供一系列操作、遍历和修改属性的方法。同时保持对单个属性 Prop 的引用。
- Prop:属性模型 Prop 与当前可视化组件/区块的某一具体属性想映射,提供了一系列操作属性变更的 API。
- Settings:SettingField 的集合。
- SettingField:它连接属性设置器 Setter 与属性模型 Prop,它是实现多节点属性批处理的关键。
- 通用交互模型:内置了拖拽、活跃追踪、悬停探测、剪贴板、滚动、快捷键绑定。
模拟器
- 运行时环境:从运行时环境来看,目前我们有 React 生态、Rax 生态。而在对外的历程中,我们也会拥有 Vue 生态、Angular 生态等。
- 布局模式:不同于 C 端营销页的搭建,中后台场景大多是表单、表格,流式布局是主流的选择。对于设计师、产品来说,是需要绝对布局的方式来进行页面研发的。
- 研发场景:从研发场景来看,低代码搭建不仅有页面编排,还有诸如逻辑编排、业务编排的场景。
参考
lowcode-engine.cn/site/docs/g…
641.nodejs 如何充分利用多核 CPU?【热度: 725】【Nodejs】【出题公司: 阿里巴巴】
关键词:nodejs 多CPU使用
总所周知, NodeJS 是单线程执行任务, 不同于 浏览器还可以使用 web worker 等手段多线程执行任务。那么 NodeJS 中, 是如何充分利用物理机的多核 CPU 呢?
有三种方式
在 Node.js 中,JS 也是单线程的,只有一个主线程用于执行任务。但是,在 Node.js 中可以使用多进程来利用多核机器,以充分利用系统资源。
- Node.js 提供了
cluster模块,可以轻松创建子进程来处理任务。通过将任务分配给不同的子进程,每个子进程可以在自己的线程上执行任务,从而实现多核机器的利用。 - Node.js 也提供了
worker_threads模块,可以创建真正的多线程应用程序。这个模块允许开发者创建和管理多个工作线程,每个线程都可以独立地执行任务。 - 利用的是 Node.js 的事件循环机制和异步非阻塞的 I/O 操作。Node.js 使用事件驱动的模型来处理请求,当有请求到达时,Node.js 将其放入事件队列中,然后通过事件循环来处理这些请求。在等待 I/O 操作的过程中,Node.js 不会阻塞其他请求的处理,而是继续处理其他请求。这样,即使 JavaScript 是单线程的,但在实际运行中,多个请求可以同时处理,充分利用了多核系统的能力。
如果 Nodejs 只写同步代码, 是否意味着无法充分利用多核优势?
如果在 Node.js 的开发过程中只使用同步代码而不使用异步代码或集群模块,那么意味着无法充分利用机器多核优势。
Node.js的事件驱动和异步非阻塞的特性使得它在处理大量并发请求时非常高效。当你使用异步代码时,可以在等待 I/O 操作的过程中继续处理其他请求,从而提高系统的吞吐量和响应速度。而同步代码会阻塞事件循环,使得只能按顺序处理请求,无法同时处理多个请求,无法充分利用多核系统的能力。
另外,如果你不使用集群模块,那么只有一个 Node.js 进程在运行,无法充分利用多核系统的资源。使用集群模块可以创建多个子进程,每个子进程在一个核心上运行,从而并行处理请求,提高系统的并发能力。
为何 nodejs 异步代码就可以充分利用多核优势了?
Node.js的异步代码可以充分利用多核优势,主要有两个原因:
- 事件驱动和非阻塞 I/O:Node.js采用事件驱动的模型,通过使用异步非阻塞 I/O 操作,可以在等待 I/O 操作完成的同时继续处理其他请求。这意味着在一个请求等待 I/O 的过程中,Node.js 可以同时处理其他请求,充分利用了 CPU 的多核能力。每个核心可以处理一个请求,从而提高系统的并发能力和吞吐量。
事件循环机制:Node.js的事件循环机制使得异步代码可以高效地处理大量并发请求。事件循环机制通过将请求注册为事件监听器,并在合适的时候触发事件处理函数,从而实现异步处理。这样一来,即使有大量并发请求,也能够通过事件循环机制避免线程切换的开销,提高系统的性能。
需要注意的是,虽然 Node.js 的事件驱动和异步非阻塞的特性使得它能够充分利用多核优势,但是在处理 CPU 密集型任务时,仍然可能受限于单线程的性能。在这种情况下,可以通过使用集群模块来创建多个子进程,在每个核心上运行独立的 Node.js 进程,从而实现并行处理,提高系统的性能。
异步就能充分利用 CPU 原理是啥?
当Node.js使用异步代码时,服务器的其他CPU核心是在工作的。 这是因为Node.js的事件驱动模型和非阻塞I/O使得在等待I/O操作完成时,可以同时处理其他请求。 当一个请求在等待I/O操作时,CPU核心可以被用于处理其他请求,而不是空闲等待。 这种方式可以充分利用服务器上的多个CPU核心,提高系统的并发能力和吞吐量。通过同时处理多个请求,可以更有效地利用服务器的资源,提高系统的性能。