文章目录
- 华为od前端技术面
- 一面
- 算法
- 问题一:
- 问题二:
- 面试题
- 1. 虚拟DOM和真实DOM有什么区别
- 2. React 为什么要采用虚拟DOM
- 3. 各种网页请求的状态码的含义,比如:2XX,3XX,4XX,5XX
- 4. 箭头函数和普通函数的区别
- 5. 防抖和节流是什么,有哪些应用
- 6. JS 为什么要有宏任务和微任务,常见的宏任务和微任务有哪些
- 7. 跨域是什么?那些情况会导致跨域?如何解决跨域
- 8. 在项目中有那些提高前端项目加载速度的方法
- 9. 你在实际项目中如何处理内存泄露,使用过哪些工具去处理
- 10. 页签之间如何进行消息的传递
- 11. 有哪些使元素水平垂直居中的方法
- 总结
- 二面
- 算法
- 面试题:
华为od前端技术面
一面
算法
问题一:
给定一个只包含0、1、2三类数字的数组,最多遍历两次完成排序,示例如下。
输入: [2,0,2,1,1,0]
输出: [0,0,1,1,2,2]
这是从HR那里听到的其他的候选人题目,顺手做了一下,是一个三指针问题。
也可以使用更简单,第一次遍历挑出其中的0,1,2三个数字出现的次数,第二次遍历存储的次数,依次加入到结果中即可。
function sort(arr) {let begin = 0, end = arr.length - 1, cur = 0;while (0 === arr[begin]) {begin++;}while (2 === arr[end]) {end--;}cur = begin;while (cur <= end) {if (0 === arr[cur]) {swap(arr, cur, begin);begin++;} else if (2 === arr[cur]) {swap(arr, cur, end);// 如果交换完成之后为 0,则需要继续进行交换if (0 === arr[cur]) {swap(arr, cur, begin);begin++;}end--;while (2 === arr[end]) {end--;}}cur++;}
}
function swap(arr, i, j) {let tmp = arr[i];arr[i] = arr[j];arr[j] = tmp;
}
const tmp = [0, 2, 1, 1, 2, 0, 2, 0, 1, 2, 2, 2, 1, 2];
sort(tmp);
双指针问题
问题二:
以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。
请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
典型的排序加双指针问题。题目的例子已经暗示需要为原数组按照内部数组的第一位大小进行排序。
排序之后,设置两个指针,分别指向第一个,和第二个。
然后进行遍历,在遍历时,会出现下面的情况:
arr[i]
和arr[j]
区间重合,那么需要将arr[i]
和arr[j]
进行合并。
- 选取
arr[i][0]
和arr[j][1]
,组合成结果数组[arr[i][0], arr[j][1]]
- 替换掉
arr
数组中i
的位置数据- 并将
arr
数组中j
所在位置的数据删除i
和j
保持不变arr[i]
和arr[j]
区间不重合
- 说明该位置的数据无法合并,则将
i
和j
向后挪动一位直到
j
到达arr
最右侧。
示例 1:
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2:
输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。
提示:
1 <= intervals.length <= 104
intervals[i].length == 2
0 <= starti <= endi <= 104
function merge(intervals) {let arr = intervals.sort((a, b) => { return a[0] < b[0] });let i = 0, j = 1;while (i < j && j < arr.length) {const tmpI = arr[i];const tmpJ = arr[j];if (tmpI[1] >= tmpJ[0]) {const tmp = [Math.min(tmpI[0], tmpJ[0]), Math.max(tmpI[1], tmpJ[1])];arr[i] = tmp;arr = [...arr.slice(0, j), ...arr.slice(j + 1)];} else {i++;j++;}}return arr;
}
console.log(merge([[1, 4], [4, 5]]));
面试题
1. 虚拟DOM和真实DOM有什么区别
- 虚拟DOM是利用JS对象来描述真实DOM节点及其属性和内容的层次结构,它是对真实DOM的一种抽象。
- 真实DOM是指浏览器中的渲染树,有HMTL元素、属性、文本和事件组成。
2. React 为什么要采用虚拟DOM
- 最初react使用的是全局刷新技术,即一个状态更新,引起整个页面的刷新。这会导致一个问题,当页面结构变得复杂,频繁的刷新会导致性能变差,用户体验很差。
- 为了解决以上问题,引入了在内存中模拟真实DOM结构的虚拟DOM,通过Diff算法比较两颗虚拟DOM的差异,得出需要更新的部分,可以减少页面渲染的开销,提高性能。
3. 各种网页请求的状态码的含义,比如:2XX,3XX,4XX,5XX
- 1xx(信息性状态码):表示接收的请求正在处理。
- 2xx(成功状态码):表示请求正常处理完毕。
- 3xx(重定向状态码):需要后续操作才能完成这一请求。
- 4xx(客户端错误状态码):表示请求包含语法错误或无法完成。
- 5xx(服务器错误状态码):服务器在处理请求的过程中发生了错误。
4. 箭头函数和普通函数的区别
- 箭头函数的上下文(this)指向创建当前箭头函数的上下文;
- 箭头函数没有 arguments
- 箭头函数不能作为构造函数
- 箭头函数不能作为Generator函数
- 箭头函数的上下文(this)无法通过 bind,apply,call 等方法改变
5. 防抖和节流是什么,有哪些应用
-
防抖
- 防抖是一种延迟执行的技术。它的原理是,当时间被触发时,延迟执行事件处理函数,并且在延迟时间内如果时间再次被处罚,则重新开始计时。只有当时间在指定的时间内没有再次触发,时间处理函数才会执行。这样可以避免高频率的操作被频繁触发,从而提高性能。
- 场景
- 点击按钮,向后端拉取数据,为了防止数据被重复拉取,当点击事件停止一段时间之后,才能发送请求。
- 键盘输入,联想关键词,只有当连续触发keyup事件之后,才能发送请求。
-
节流
-
节流是一种限制函数执行频率的技术。它的原理是,当时间被频繁触发时,函数会按照一定的事件间隔执行了,而不是每次触发事件都执行。换句话说,在一个时间段内,只会执行一次事件处理函数。
-
场景
- 页面滚动,用户多次触发scroll事件,使用节流限制处理函数执行的次数。
- 监听窗口变化,限制浏览器响应式页面重绘次数,提高体验。
-
6. JS 为什么要有宏任务和微任务,常见的宏任务和微任务有哪些
- 引入宏任务和微任务的概念主要是为了优化异步操作的执行顺序,提高用户体验和程序的响应速度。具体原因如下:
- 提升响应性:
- 宏任务和微任务的区分使得浏览器可以在执行完一个宏任务后立即处理所有待处理的微任务,然后再渲染页面。这样可以确保用户界面的更新更加及时,提升用户体验。
- 避免阻塞
- 微任务在宏任务完成后立即执行,不会被新的宏任务打断。这有助于减少长时间运行的任务对其他任务的影响,避免页面卡顿。
- 更好的异步控制
- 通过宏任务和微任务的机制,开发者可以更精确地控制异步操作得执行顺序。例如,
Promise
的then
方法会在当前任务技术后立即执行,而setTimeout
会在下一个红人执行中执行。
- 通过宏任务和微任务的机制,开发者可以更精确地控制异步操作得执行顺序。例如,
- DOM更新的优化
- 在一个宏任务中,即使多次修改DOM,浏览器也只会执行一次重绘或重排。这减少了不必要的性能开销。
- 提升响应性:
- 常见的宏任务和微任务
- 宏任务
- setTimeout
- setInterval
- I/O操作
- UI 渲染
- 微任务
- Promise.then
- MutationObserver
- process.nextTick(Node.js 环境)
- 宏任务
7. 跨域是什么?那些情况会导致跨域?如何解决跨域
-
跨域是什么?
- 跨域(
Cross-Origin Resouce,CORS
)是指浏览器中,一个域下的文档或者脚本请求另一个域下的资源时,浏览器处于安全考虑对其进行限制的行为。这种限制是为了防止恶意网站通过脚本读取另一个网站的数据,保护用户的隐私和安全。
- 跨域(
-
导致跨域的情况
- 不同协议
- 不同域名
- 不同端口
-
解决跨域的方法
-
服务器设置响应头
Access-Control-Allow-Origin
,允许特定的域访问资源// Nodejs + Express app.use((req, res, next) => {res.header('Access-Control-Allow-Origin', '*');// 允许所有域res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');next(); })
-
JSONP(JSON with Padding)
-
通过动态创建
<script>
标签来绕过跨域限制,适用于只支持GET
请求的场景<script>function handleResponse(data) {console.log(data);} </script> <script src="http://api.example.com/data?callback=handleResponse"></script>
-
-
代理服务器
- 在客户端和目标服务器之间设置一个代理服务,客户端请求代理服务器,代理服务器在请求目标服务器
- 前端开发环境可以使用 webpack 的 webpack server 进行反向代理
- 生产环境可以使用 nginx 的反向代理
- 在客户端和目标服务器之间设置一个代理服务,客户端请求代理服务器,代理服务器在请求目标服务器
-
websocket
-
websocket 协议本身不受同源策略的限制,可以实现跨域通信。
const socket = new WebSocket('ws://api.example.com'); socket.onmessage = function(event) {console.log(event.data); };
-
-
8. 在项目中有那些提高前端项目加载速度的方法
-
项目打包方面:
-
压缩
Javascript
和CSS
文件-
使用工具如 UglifyJs、Terser、CSSNano 等压缩代码,减少文件大小。
const TerserPlugin = require('terser-webpack-plugin'); const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');module.exports = {optimization: {minimize: true,minimizer: [new TerserPlugin(), new OptimizeCSSAssetsPlugin()],}, };
-
-
压缩项目本地图片
- 使用工具如 ImageOptim、TinyPNG 等压缩图片文件
-
代码分割和按需加载
- react中使用 React.lazy 将代码分割成小块,按需加载
-
利用 webpack 尽量减少 CSS 和 Javascript 文件的数量,尽量合并
-
利用
preload
和prefetch
来优化性能,减少交互延迟-
什么是 preload,什么是 prefetch
- preload 是一个声明式的fetch,可以强制浏览器在不阻塞 document 的 onload 事件的情况下请求资源。
- prefetch 告诉浏览器这个资源将来可能需要,但是什么时间加载这个资源由浏览器来决定。
-
何时使用 preload,何时使用 prefetch
-
preload 是告诉浏览器预先请求当前页面需要的资源(关键的脚本,字体,主要图片等)。
-
prefetch 应用场景稍微又有不同 —— 用户将来可能跳转到其他页面需要使用到的资源。如果 A 页面发起一个B页面的 prefetch 请求,这个资源获取过程和导航请求可能是同步进行的,而如果我们用 preload 的话,页面 A 离开时它就会立即停止。
-
-
-
-
项目运行期间
- 图片懒加载
- 静态资源使用CDN
- 首屏请求不多的情况下,使用 Promise.all() 合并并发请求
- 图片根据浏览器是否支持 webp 格式,选择支持 webp,来减少图片大小
9. 你在实际项目中如何处理内存泄露,使用过哪些工具去处理
-
利用浏览器DevTools中对React项目进行内存泄露排查
-
Performance Monitor定性
Performance Monitor 能够在较小的性能代价下展示出网站应用的若干个影响性能和体验的关键参数随着时间变化(用户操作)的趋势,其关键指标如下所示
CPU usage 网页使用的CPU百分比。默认显示。
JS heap size JavaScript程序在页面上使用的内存量。默认显示。
DOM Nodes 浏览器中DOM节点的数量(跨选项卡)。默认显示。
JS event listeners 浏览器中JavaScript事件监听器的数量(跨选项卡)。
Documents 浏览器中文档对象的数量(跨选项卡)。
Document Frames 浏览器中文档框架的数量(跨选项卡)。
Layouts / sec 浏览器引擎每秒构建页面布局的次数。
Style recalcs / sec 浏览器引擎每秒计算页面CSS样式的次数。针对内存泄漏问题,可以重点关注 JavaScript 堆大小和 DOM 节点数的变化趋势,并根据以下原则对内存泄漏进行初步的定性判断:
- 其中任何一个出现只增不减的趋势,则可以定性判断存在内存泄漏的情况
- 如果
javscript
堆大小只增不减,而 DOM 节点数量趋于平稳,则可以定性只在Javascript
上下文中出现了内容泄漏 - DOM 节点只增不减往往会伴随着 JS 堆大小的只增不减。此时需要关注二者增加的趋势是否同比(增长速度一致)同频(增长时机一致)
- 如果同比同频,可以定性只有DOM元素卸载未清理引发的内存泄露,JS 堆大小的变化只是半生现象
- JS 堆大小增加趋势更加陡峭,可以定性同时存在两个内存泄漏的源头
当二者的变化趋势满足同比同频,基本可以确定是对DOM元素的引用没有清理导致的内存泄漏问题。
-
Detached Elements定位(现在已经在 Memory 功能中)
Detached Elements 的功能很明确,即找到所有灭有挂在在 DOM 树上,同时还没有被浏览器引擎垃圾回收的DOM元素。因为浏览器的垃圾回收本身就是周期性的行为。所以在进行问题排查前,必须手动触发一次垃圾回收行为,保证剩下的就是要排查分析的目标元素。
DOM 对象是占用内存最高的一类对象之一,因此如果在应用程序中频繁地创建和销毁DOM对象,就容易导致内存泄漏。游离的DOM引用指的是已经不在文档中的DOM节点的引用,但是这些引用仍然被保存在 JS 变量、数组和对象中,因此这些DOM节点无法被垃圾回收器回收,从而导致内存泄漏。
-
Memory 分析
Memory 能够简历当前应用的 JS 堆快照,用于进一步分析页面的 JS 对象以及相互之间的引用关系。在已经定位了泄漏源的基础上,可以借助该工具查明目标 DOM 被什么 JS 对象支持有了引用导致无法被垃圾回收。
-
10. 页签之间如何进行消息的传递
-
LocalStorage 或 SessionStorage
-
使用 Web 存储机制可以在不同标签页之间共享数据。一个标签页可以将数据存储在LocalStorage或SessionStorage中,其他标签可以监听存储事件来获取更新数据。
-
案例
// 在一个标签页中写入数据到 LocalStorage 或 SessionStorage localStorage.setItem("sharedData", "Hello from Tab 1");// 在其他标签页中监听存储事件,并获取更新的数据 window.addEventListener("storage", function(event) {if (event.key === "sharedData") {const newData = event.newValue;console.log('Received updated data: ', newData);} })// 另一个标签中更新数据 localStorage.setItem("sharedData", "Hello from Tab2");
在这个例子中,首先在一个标签页中通过
localStorage.setItem()
或sessionStorage.setItem()
方法将数据写入到LocalStorage
或SessionStorage
中。然后,在其他标签页中通过监听 storage 事件来捕获存储事件,并判断事件的 key 是否为我们共享的数据 sharedData,如果是,则获取更新的数据 newValue 并进行处理。接下来,在另一个标签页中通过
localStorage.setItem()
或sessionStorage.setItem()
方法更新数据。
-
-
Broadcast Channel API
-
Broadcast Channel API 允许不同标签页之间通过共享的通道进行消息广播和接受。一个标签页可以通过通道发送消息,其他订阅了相同通道的标签页可以接受这些消息。
-
案例
// 创建一个广播通道 const channel = new BroadCastChannel('myChannel'); // 发送消息 channel.postMessage('Hello from Tab 1');// 接受消息的标签页中 const channel = new BroadCastChannel('MyChannel'); channel.onmessage = function(event) {const message = event.data;console.log('Received message', message); }
首先在发送消息的标签页中创建一个
Broadcast Channel,
并指定一个唯一的通道名称(这里使用 ‘myChannel’)。通过channel.postMessage()
方法发送消息到该通道。在接收消息的标签页中,同样创建一个具有相同通道名称的
Broadcast Channel。
然后,通过为 channel.onmessage 赋值一个函数,来监听消息事件。当接收到消息时,事件对象 event 中的 data 属性将包含发送的消息内容,我们可以在监听函数中获取并处理该消息。
-
-
SharedWorker
-
SharedWorkder 是一种在多个标签页之间共享的后台线程。标签页可以通过 SharedWorker 进行通信,发送消息和接收消息。这种方式需要使用 Javascript 的 Worker API。
-
案例
// 创建一个 SharedWorker const worker = new SharedWorker('worker.js');// 发送消息 worker.port.postMessage('Hello from Tab 1');// worker.js // 监听连接事件 self.onconnect = function(event) {const port = event.ports[0];// 监听消息事件port.onmessage = function(event) {const message = event.data;console.log('Received message:', message);};// 发送消息port.postMessage('Hello from Worker'); };
-
-
Window.postMessage
-
Window.postMessage() 方法允许在不同的窗口或标签页之间安全地传递消息。通过调用 postMessage() 方法并指定目标窗口的 origin,可以将消息发送到其他标签页,并通过监听 message 事件来接收消息。
-
案例
// 发送消息的标签页中 // 监听消息事件 window.addEventListener('message', function(event) {// 确保消息来自预期的源if (event.origin !== 'http://example.com') {return;}const message = event.data;console.log('Received message: ', message); })// 发送到其他标签页中 const targetWindow = window.open('http://example.com/otherpage', '_blank'); targetWindow.postMessage('Hello from Tab 1', 'http://example.com');// 在接受信息的标签页中 window.addEventListener('message', function(event) {// 确保消息来自预期的源if (event.origin !== 'http://example.com') {return;}const message = event.data;console.log('Received message: ', message);// 回复消息event.source.postMessage('Hello from Other Tab', event.origin); })
在发送消息的标签页中通过使用
window.addEventListener('message', ...)
监听消息事件。在事件处理函数中,可以用event.origin
来验证消息的来源是否符合预期。然后,可以用event.data
获取到发送的消息内容,并进行相应的操作。在发送消息的标签页中,用
window.open()
打开了一个新的标签页(http://example.com/otherpage
),然后通用targetWindow.postMessage()
向该标签页发送消息。在这里,我们指定了消息的目标窗口和预期的来源(即目标标签页的 URL)。在接收消息的标签页中,同样通过
window.addEventListener('message', ...)
监听消息事件,并在事件处理函数中进行相应的操作。
-
11. 有哪些使元素水平垂直居中的方法
-
父元素设置 CSS 属性
.parent {display: flex;justify-content: center;align-items: center; }
-
父元素设置CSS属性
position: relative
,子元素设置CSS属性.child {position: absolute;left: 50%;right: 50%;transform: translate(-50%, -50%); }
-
父元素以及子元素的宽高确定的情况下,父元素设置CSS
.parent {margin: auto; }
总结
华为 OD 技术一面,主要还是侧重于基础。
对付一面,刷 leetcode 和背八股文就行了。
二面
算法
给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
每个右括号都有一个对应的相同类型的左括号。
示例 1:
输入:s = “()”
输出:true
示例 2:
输入:s = “()[]{}”
输出:true
示例 3:
输入:s = “(]”
输出:false
示例 4:
输入:s = “([])”
输出:true
leetcode 里面的简单题,主要考验的是对栈的理解。栈 - 先进后出
以下是解法的关键点:
首先判断字符串为空,或者字符串长度为奇数
为空,或者长度为奇数,括号不能配对,不合法。
不为空且长度为偶数
创建字典,以
右括号
为键,左括号
为值。创建一个栈,用以存放左括号。
遍历字符串
如果字符串为空,则遍历完成。
如果字符串不为空:
如果是
右括号
,则弹出左括号
若弹出的左括号不和右括号对应,则字符串无效
对应,则继续下一个遍历
如果弹出的是
左括号
- 将该括号压入栈
判断栈的长度,如果长度为0,则合法,不为0,左括号多余,则字符串不合法。
function isVaild(s) {if (s.length % 2 === 1) {return false;}const dic = new Map();dic.set(")", "(");dic.set("]", "[");dic.set("}", "{");let stk = [];for (let i = 0; i < s.length; i++) {let char = s[i];if (dic.has(char)) {if (stk.length > 0 && stk[stk.length - 1] !== dic.get(char)) {return false;}stk.pop();} else {stk.push(char);}}return !stk.length;
}
面试题:
面试题是从你以前经历过的项目,按照你简历上所写,来考的,你的简历越丰富,越难。
我反正是被问懵了,这里就举一个例子吧。
我的简历上写了,在搭建公司的基础平台的时候,状态管理库使用的是 jotai
。
那么,面试官的问题就是
- react 为什么要使用状态管理工具?
- react 有什么api能够充当管理工具,为什么不用?有什么缺点?
- jotai 相比起以前的状态管理工具,比如 redux 或者 mobx,有哪些优势?
从以上来看,技术二面会对你的项目技术选型提出疑问,首先是基础方面的内容,然后是使用的技术栈的基础知识的考查,再是你对该技术选型的考究,以及对比,综合考察你的项目经验和技术栈理解的深度。
所以在面技术二面的准备的时候,需要根据自己的项目经验,老老实实地去排查里面可能出现的知识点,否则可能就像我一样,一脸懵逼的开始,一脸懵逼的结束。
最后,祝正在寻求机会的各位,一帆风顺;正在岗位上发光发热的各位,稳步高升。