字节前端工程师一面 面试分享给大家
哈喽哈喽,大家好,我是你们的金樽清酒。在一个平常的一天,我上着班,突然接到一个电话。您好,我是字节跳动的hr,我们这边是抖音电商部门,请问您还在考虑找工作嘛。yes,yse,I do。ofcase。好的,那我这边给您发个邮件。中间有点小插曲,我戴着耳机多次误碰把电话挂了。哈哈哈哈哈。
小插曲
每次面试的时候,我都习惯提前进入会议。在等了面试官五分钟的时候。hr打电话过来了。同学,你还记得今天的面试嘛。我说,啊,我不是在会议嘛。好家伙我跟面试官进的不是同一个会议。可能是个实习生小姐姐,哈哈哈哈哈,蛮可爱的。
面试题
一、自我介绍
自我介绍要说些什么呢?自我介绍的时间一般不能很长。要言简意赅的表达。主要表达清楚三个东西。我是谁?我会什么?我有什么经历或优势。
二、介绍在公司实习做的事情?
这个问题,一般是问你在公司处理过什么有难度的事情。比如性能优化呀。封装过什么有意义的组件。做过什么有意义的项目。肯定是挑最有技术难度的,而不是记流水账,一股脑没有重点。条理不清。
三、微前端的作用?为什么选用MicroApp,还有其他微前端的框架嘛?
前期呢,我是在公司用echarts做一些数据可视化,完善后台的功能。后面呢,是重构后台,将原本主排期的后台变成排期开发部署一套流程。由于人手不够,不可能重开开发。这个时候我们可以用微前端技术将其他后台接入我们的后台,大大节省开发时间。
那什么是微前端? 微前端是一种将Web应用由单一的单体应用转变为多个小型前端应用聚合为一的手段。它借鉴了微服务的架构理念,将微服务的概念扩展到了前端。具体来说,微前端将一个大型的前端应用拆分成多个模块,每个模块可以由不同的团队进行管理,并可以自主选择框架,同时每个模块有自己的仓库,可以独立部署上线。也就是说,没有技术栈的限制。比如一个后台用的react技术栈,一个用的是vue技术栈。两个项目呢也可以很好的嵌套。这也是微前端的优势。独立部署,可以将一个巨石应用分成多个模块。
为什么选用MicroApp?其实选用MicroApp是因为MicroApp相对比较简单一点,它基于类WebComponent渲染,从组件化的思维实现微前端,低成本接入,不需要像其他一些框架(如single-spa和qiankun)要求子应用修改渲染逻辑并暴露出方法,也不需要修改webpack配置。在人手不够的情况下,容易上手,节省了学习的成本。当然还有很多微前端框架。如qiankun和single-spa,它们各自有自己适合的场景。
四、为什么会跨域,什么是同源,怎么解决跨域问题?
为什么会谈到跨域问题呢?因为用的MicroApp,将已有的项目嵌套。请求其他项目的页面,必然会发生跨域,不然的话就无法获取到资源。那为什么会跨域呢?是受浏览器同源策略的影响。
什么是同源策略? 同源策略(Same-Origin Policy,SOP)是浏览器为了保障用户信息安全而实施的一种安全策略。它限制了一个源(即协议、域名和端口的组合)的文档或脚本如何与另一个源的资源进行交互。
什么是同源? 同源是协议名相同如http与https就不同源。域名相同和端口号相同,这三者都相同说明是同源。
怎么解决跨域问题?
- JSONP:通过动态创建
<script>标签来实现跨域请求。 - CORS(Cross-Origin Resource Sharing) :通过服务器设置特定的HTTP头部来允许跨域请求。
- PostMessage API:允许不同源的窗口之间进行安全的消息传递。
- 代理服务器:通过在同源服务器上设置代理来转发跨域请求。
面试官让我说出几种方法即可。但是大家还是需要自己去查找一下解决方案,如何去实施。
五、你知道option请求嘛?什么时候会发生预检?
这题问到我心趴上了。因为前两天我的mentor就教了我这个东西。因为碰到过这个问题,我在请求其他端的接口的时候,请求体里面没有拿到东西。经过mentor一顿研究发现,在我发送post请求之前,还发生了一个option请求,option请求没有通过。
什么是option请求?option请求也叫预检请求。通常发生在跨域的时候。当浏览器需要发送一个非简单请求(complex request)时,会在实际请求之前发送一个OPTIONS请求,以确认服务器是否允许该请求。这种机制有助于防止潜在的安全风险和意外的数据修改。
什么时候会发生预检请求呢?
首先肯定是需要跨域。PTIONS预检请求是CORS机制的一部分,用于确保跨域请求的安全性和有效性。其次在浏览器发生非简单请求的时候。什么是非简单请求呢?
根据CORS规范,非简单请求是指那些不符合以下条件的请求:
- 请求方法是
GET、HEAD或POST。 - 请求头信息只包含以下字段:
Accept、Accept-Language、Content-Language、Content-Type(仅限于application/x-www-form-urlencoded、multipart/form-data、text/plain)。 - 请求中没有使用
ReadableStream对象。
当时是因为修改了请求头里面的内容,两个服务器协定好了一个字段用于登陆的验证,所以发生了预检,且预检不通过,因为请求头和响应头里面的字段没有完全对上。
六、浏览器的协商缓存和强缓存,如何不发生浏览器缓存?
强缓存是指浏览器在缓存有效期内直接使用缓存,而不向服务器发送请求。
启用强缓存可以在相应头设置Cache-Control: max-age=3600,这里是设置3600s,以s为单位
或者设置相应头Expires: Sat, 21 Dec 2024 23:12:28 GMT,给一个过期的时间。
不发生强缓存则可以设置为Cache-Control: no-store,或者Expires设置为一个过期时间。
协商缓存是指浏览器在缓存过期后,会向服务器发送请求,验证缓存数据是否过期。
启动协商缓存则设置Last-Modefied一个文件最后的修改时间,和ETag设置资源的唯一标识符。
不发生协商缓存则可以设置Cache-Control: no-store,或者将Last-Modified, ETag这俩响应头设置为无效值或者不设置。
七、Cache-Control里面有哪些属性?
为什么面试官这么问。现在才发现他是在引导我。
Cache-Control响应头用于控制浏览器和其他中间缓存如何缓存和重新使用已缓存的响应。
它有这些属性
public | 所有内容都将被缓存(客户端和代理服务器都可缓存)。 |
|---|---|
private | 内容只缓存到私有缓存中(仅客户端可以缓存,代理服务器不可缓存)。 |
no-cache | 指示浏览器忽略资源缓存副本,强制到服务器获取资源(但允许缓存协商)。 |
no-store | 禁止使用任何缓存策略,客户端每次请求都需要服务端给予最新的响应。 |
max-age=<seconds> | 缓存的内容将在指定的秒数后失效,这个选项只在HTTP/1.1中可用。 |
must-revalidate | 如果缓存的内容失效,请求必须发送到服务器进行重新验证。 |
proxy-revalidate | 类似于must-revalidate,但仅适用于共享缓存(如代理服务器)。 |
八、webpack和vite的区别?
webpack和vite的区别主要体现在以下几个方面:
-
构建速度:
- Webpack:作为一个通用的构建工具,Webpack需要对整个项目进行分析和构建,因此在启动和构建时间上可能比较慢,尤其是对于大型项目和复杂的构建配置而言。
- Vite:采用了一种新颖的开发模式,利用了浏览器自身的原生ES模块支持,将构建的过程延迟到了开发环境的运行时。这种分离的方式使得Vite具有非常快的冷启动速度和即时热更新。Vite底层基于esbuild(Go语言实现),因为Go语言的操作是纳秒级别,而JavaScript是以毫秒计数,所以Vite比用JavaScript编写的打包器快10-100倍2。
-
开发模式:
- Webpack:使用传统的开发模式,在开发阶段需要将所有的代码打包成一个或多个bundle,然后在浏览器中进行动态加载。这种模式需要使用热加载或者修改文件后手动刷新浏览器才能看到更新的效果。
- Vite:采用了ES模块原生的开发模式,在开发阶段不需要将所有代码打包成一个bundle,而是以原生ES模块的方式直接在浏览器中加载和运行文件。这个特性使得Vite能够实现更快的冷启动和热更新,修改文件后无需刷新浏览器即可立即看到更新的效果。
-
生产构建:
- Webpack:在生产环境下会将所有代码打包成一个或多个bundle,以便进行优化、压缩和代码拆分等操作,以提高性能和加载速度。
- Vite:在生产环境下仍然保持了开发时的原生ES模块导入方式,不会将所有代码打包成一个bundle。相反,它会保留第三方依赖的单独引用,以便在浏览器端实现更快的加载速度。
-
插件生态系统:
- Webpack:拥有广泛的插件生态系统,有大量的插件可以满足不同的构建需求,并能与其他工具和框架良好地集成。
- Vite:作为一个相对较新的项目,其插件生态系统相对较小,但依然可以满足常见的构建需求,并且在逐渐增长。
只能说webpack和vite很重要,但是小编我还没有深入的去了解过。得花时间去了解一下。
九、commJs和ES规范的区别?
为什么谈到这个呢?他又想引导我。在问到webpack和vite的区别的时候,vite不是用ES模块原生的开发模式嘛,那肯定得了解ESMOdule。
CommonJS和ES模块是JavaScript中两种不同的模块系统,它们在语法、执行时机、动态导入等方面存在显著差异。
1. 语法
-
CommonJS:
- 使用
require()同步加载模块。 - 使用
module.exports或exports导出模块成员。
- 使用
-
ES模块:
- 使用
import语句导入模块。 - 使用
export语句导出模块成员。
- 使用
执行时机
-
CommonJS:
- 模块加载和执行是同步的。
require()会阻塞代码执行,直到模块加载完成。- 模块的执行顺序与它们在代码中出现的顺序一致。
-
ES模块:
- 模块加载和执行是异步的。
import语句不会阻塞代码执行,浏览器会在后台加载模块。- 模块的执行顺序取决于模块之间的依赖关系和浏览器的加载策略。
3. 动态导入
-
CommonJS:
- 支持动态导入,但需要使用
require(),并且是同步的。
Javascript 复制 const modulePath = './module' + someVariable; const myModule = require(modulePath); - 支持动态导入,但需要使用
-
ES模块:
- 支持动态导入,使用
import()函数,并且是异步的,返回一个 Promise。
Javascript 复制 import(modulePath).then((myModule) => { // 使用 myModule }); - 支持动态导入,使用
4. 顶层作用域
-
CommonJS:
- 每个模块都有自己的顶层作用域。
- 在一个模块中定义的变量、函数等不会污染全局作用域,也不会被其他模块直接访问,除非显式导出。
-
ES模块:
- ES模块的顶层作用域是共享的。
- 在 ES 模块顶层声明的变量、函数等,在其他导入该模块的地方是可见的,类似于全局变量。
- 但是,ES 模块的设计理念仍然鼓励尽可能地使用
export和import来管理模块之间的依赖关系,而不是依赖共享的顶层作用域。
5. 兼容性
-
CommonJS:
- 主要用于 Node.js 环境。
- 浏览器端需要使用构建工具(如 Webpack、Browserify)进行转换。
-
ES模块:
- 是 JavaScript 的标准模块系统。
- 现代浏览器都原生支持 ES 模块。
十、为什么要用打包工具?
1. 模块化开发
现代前端开发通常采用模块化的方式,将代码拆分成多个独立的模块。每个模块负责特定的功能,这样可以提高代码的可维护性和复用性。然而,浏览器并不直接支持模块化开发,因此需要打包工具将这些模块合并成一个或多个浏览器可以识别的静态文件。
2. 性能优化
打包工具可以对代码进行各种优化,以提高页面加载速度和用户体验。例如:
- 代码压缩:去除代码中的空格、注释等不必要的字符,减小文件大小。
- 代码分割:将代码分割成多个小文件,按需加载,减少初始加载时间。
- Tree Shaking:移除未使用的代码,进一步减小文件大小。
- 缓存优化:通过哈希值等方式,使浏览器能够缓存静态资源,加快后续加载速度。
3. 多种文件类型的处理
前端项目不仅包含 JavaScript 文件,还可能包含 CSS、图片、字体等多种类型的文件。打包工具可以处理这些不同类型的文件,并将它们合并成一个或多个浏览器可以识别的静态文件。
4. 开发效率提升
打包工具通常集成了各种开发工具和插件,可以帮助开发者提高开发效率。例如:
- 热更新:在代码发生变化时,自动重新编译并刷新浏览器,减少开发者的等待时间。
- 代码检查:在编译过程中自动检查代码中的错误和潜在问题,帮助开发者及时发现和修复问题。
- 自动化测试:在编译过程中自动运行测试用例,确保代码质量。
5. 兼容性处理
不同的浏览器对 JavaScript 和 CSS 的支持程度不同。打包工具可以通过转译(transpiling)等方式,将现代 JavaScript 和 CSS 转换为兼容性更好的版本,确保在各种浏览器中都能正常运行。
6. 生态系统支持
现代前端开发通常依赖于各种第三方库和框架。打包工具可以自动下载和管理这些依赖,并将它们合并到最终的打包文件中,简化了项目的依赖管理。
十一、什么是CDN,它有什么作用?
CDN(Content Delivery Network),即内容分发网络,是一种通过在全球范围内部署数据中心,利用内容缓存、内容分发、内容管理等技术,实现内容的快速、稳定传输的网络技术1。其目的是通过在现有的Internet中增加一层新的CACHE(缓存)层,将网站的内容发布到最接近用户的网络边缘的节点,使用户可以就近取得所需的内容,提高用户访问网站的响应速度2。
CDN的作用
1. 加速静态资源加载
对于前端开发来说,CDN最重要的作用就是加速静态资源的加载。比如,你的网站有很多图片、CSS、JavaScript文件,这些文件都可以通过CDN来加速加载1。
2. 减轻服务器压力
使用CDN后,大部分的用户请求都会被CDN的数据中心处理,只有少部分请求会回源到原始服务器,因此可以大大减轻服务器的压力1。
3. 提高网站性能
通过减少数据传输的距离和时间,CDN可以显著提高网站的加载速度,从而提高网站的性能和用户体验1。
4. 提高网站安全性
CDN还可以提供一些安全功能,如DDoS防护、SSL加密等,提高网站的安全性1。
5. 解决网络带宽小、用户访问量大、网点分布不均等问题
CDN通过对网络的优化,解决了由于网络带宽小、用户访问量大、网点分布不均等原因导致的访问速度慢的问题2。
6. 缓解网络压力
CDN可以缓解服务器端的第一公里问题,消除不同运营商之间互联的瓶颈,减轻各省的出口带宽压力,缓解骨干网的压力2。
十二、get请求和post请求的区别?
get请求和post请求的区别。这是一个老生常谈的问题了。
首先看语义哦。get就是获取,post就是发送。get就是获取资源的请求,post就是设计来发送资源的请求,当然这只是语义方面。实际上是都可以用的。
其次,看传递的参数。get通过url携带参数,url?后面带参数&连接参数如:request.get('https:xxxx?name=xiaomin&age=18'),post请求是通过请求体携带参数。所以get请求的参数受url地址栏的限制,而post请求则没有。且参数直接在地址栏不安全,所以相对来说post请求会安全一点。
从跨域上说,部分get是不会发生跨域的,如script标签的src发送get请求不会发生跨域,所以可以用jsonp来解决跨域问题,但是这需要前后端沟通。
十三、有哪些方式可以发送get请求?
其实这道题是问你ajax请求的方式有哪些?
1. 使用XMLHttpRequest对象
XMLHttpRequest(简称XHR)是JavaScript中的一个内置对象,用于在后台与服务器交换数据。以下是一个使用XMLHttpRequest发送GET请求的示例2:
function sendGetRequest(url, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
callback(xhr.responseText);
}
};
xhr.send();
}
2. 使用Fetch API
Fetch API是现代浏览器提供的一种更现代化的、基于Promise的HTTP请求方式。以下是一个使用Fetch API发送GET请求的示例2:
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
3.使用axios等第三方库
这个在平常项目当中用的最多了。
4. 使用Location对象
在某些情况下,你可以使用Location对象来发送GET请求。这种方式通常用于页面跳转。以下是一个使用Location对象发送GET请求的示例1:
Javascript
复制
function pageGo() {
var tp = ${pb.tp}; // 获取总页数
var page = document.getElementById('page').value; // 获取页码
if (Number(page) > 0 && Number(page) <= tp) {
var path = location.pathname + '?pc=' + page;
location.assign(path); // 提交URL
}
}
算法题
- 洗牌算法
这道题是这样的给你一个数组,要你写一个函数,使得数组的排列顺序随机。这一道题要用到洗牌算法,其实也不复杂,就是之前没有见到过,这下长见识了。
let arr = [1, 2, 3, 4, 5]
function shuffleAlgorithm(arr) {
for (let i = 0; i < arr.length; i++) {
let j = Math.floor(Math.random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
}
shuffleAlgorithm(arr)
console.log(arr);
分析一下,每个数组的位置都要发生变化,那就循环遍历每个位置,然后每次生成一个随机数作为另一个交换位置的下标。进行交换,这样就可以保证每次生成的数组都能随机。注意要取整哦,随机数生成的是0-1的小数。
- 用setTimeout模拟setinterval 当时写这一题的时候我就想到递归或者死循环,但是递归错了地方。
function MysetInterVal(fn, time) {
const tool = () => {
setTimeout(() => {
fn()
tool()
}, time)
}
tool()
}
function test(){
console.log('定时器触发');]
}
MysetInterVal(test,1000)
递归调用的时候,应该放在定时器里面再执行,我直接放在定时器外面了,可惜了,失之交臂。这一题其实就是考验对递归及函数功能的理解。
想更加详细了解的友友们,可以点击下方链接。 setTimeout模拟setInterval
总结
这一面,我清楚的认识到自己的不足。在对一些打包工具没有清晰的认识,因为没有自己去使用过,平常开发还达不到那个层面。再一个就是算法,有些人认为前端学算法有什么用,在平常开发的时候又用不到,可以用gpt查一下怎么写就够了。对,平常开发用不上,但这也是锻炼自己的逻辑思维能力和学习能力吧。到最后,我问了下面试官就是算法怎么去学,得到的答案是刷题。对,学习没有捷径。日拱一卒终到远方。不管怎么也要秉持一颗对编程的热爱,才能走的更加的长远。
其实吧,学习就是看透背后的逻辑,然后日复一日的进行总结归纳。万不可走捷径,贪图舒服。