哈喽,大家好,我是Fine。
今天继续为大家分享一下大厂的前端实习面试题。面试不算太难,比较侧重基础,包括了一些简单的基础题,算法,计算机网络,工程化。对于在校生/应届生来说打好前端基础很重要。
在百度的学长,帮我内推了下简历。高兴还没有两秒,就接到通知面试的电话,瞬间压力山大。小公司也没怎么面,八股文背的也不利索,看到镜头里又秃又强的面试官,有如看恐怖片,吓尿了!!!
还好,之前看到过敖丙大佬面试自我介绍5句话公式 - 掘金 (juejin.cn)一文,里面有个公式:我是谁+从哪里来+我做过什么+有什么成绩+为什么能胜任。
面试官您好,我叫XX,XX大学XX专业大三在读,想来百度前端岗位实习,要是是AIGC项目就更好了。低年级的时候,跟着学长一起在B站学习、打些比赛,拿过蓝桥杯省二,奖学金也拿了一些。个人比较喜欢前端,也能用node写后端。chatgpt火了后,又对AIGC非常感兴趣,所以学习了python、prompt enginneering、吴恩达AI课程等,平时写代码会用一些AI Copilot工具提升编程效率。
仔细读过《你不知道的JavaScript》,并在掘金上写过JS基础和底层相关系列文章,热爱技术分享,掘金优秀作者。平时主要使用vue,熟悉全家桶开发,数据流按理及后台管理系统开发。阅读过vue、axios、ElementPlus的源码,未来会持续学习。
未来最大的机会是AI的机会, 非常期待在AIGC产品或项目写下自己的代码。同时非常看好百度代表中国队参与AI技术革命,为用户开发划时代的产品与体验。未来几年,我将为此而奋斗, 谢谢。
使用的是字面量初始化创建了一个空数组,这是最简洁、直观且推荐的写法。这种方法直接在内存中创建并初始化数组对象,执行效率高。
var a = new Array();
使用了Array构造函数来创建一个空数组。尽管功能上等同于前者,但性能上有差异。
如果传递整数参数,比如new Array(3), 指定长度
比如('a', 'b', 'c') 作为元素初始化。还有 var arr = new Array(5).fill(0);
也经常用到。
通过原型链查找,Array 原型上的push等方法
面试官又追问, var a = new A(),a 和 A 的关系,A 和 Function 的关系
a 是 A 的一个实例对象,a 的
__proto__
指向 A 的prototype
,A 的__proto__
指向 Function 的prototype
。
Promise对象有三种状态
Pending(未决)初始状态
Fulfilled(已履行/成功):操作成功完成时的状态
Rejected(已拒绝/失败):操作因错误或异常未能完成时的状态
Promise状态变化的特性是:
Promise状态的转变是不可逆且只能发生一次。也就是说,一个Promise不能从
Fulfilled
状态变回Pending
状态,也不能从Rejected
状态变为Pending
或者Fulfilled
状态。一旦Promise从Pending
状态变为Fulfilled
(resolved)或Rejected
(rejected),它就永远不会再改变。因此,Promise的状态不能重复改变。
感觉回答的还可以,记得去字节的会长提醒面试时,切忌一个字一个字的蹦面试官。面试不只是回答问题,而是要展示自己
。抓住熟悉的知识或技能点,就来个滔滔不绝,面试官不喊停就一直说
。于是想到下面这个API:
Promise.resolve()与Promise.reject() 用于创建已确定状态的Promise对象,方便快速返回成功的或失败的结果
面试官一听这个就感兴趣了, 继续提问Promise还提供了哪些静态方法,平时怎么用的?开心, 面试官进包围圈了。
Promise.all(iterable)
参数是promise对象数组。只有当所有Promise都变为fulfilled时,返回的Promise才会变为fulfilled,并且结果是一个包含所有Promise结果的数组;只要有一个Promise变为rejected,则整体Promise也会立即变为rejected,返回第一个rejected Promise的理由。
Promise.race(iterable)
在传入的 Promise 数组中任何一个 Promise 解决(resolve)或拒绝(reject)时,会立即以那个率先改变状态的 Promise 的结果为准来解决或拒绝。
这里强调下细节,其它的promise实例仍然会继续运行,只不过其状态和结果不会归于最终的结果。
Promise.race
关注的是速度最快的 Promise 的结果,而 Promise.all
关注的是所有 Promise 是否都成功完成。
Promise.allSettled(iterable)
和Promise.all()
相似,它等待所有Promise都达到settled状态(即无论是fulfilled还是rejected)。一旦所有Promise都决断了,返回的Promise会变成fulfilled,并且结果是一个数组,包含了每个输入Promise的结果描述对象,这些对象具有status
('fulfilled'或'rejected')和对应的value
或reason
属性。
Promise.all()
更关注所有 Promise 是否都成功完成,它适用于需要所有任务成功完成才能继续下一步场景。而 Promise.allSettled()
则允许你观察一组异步操作的所有结果,无论成功与否,这对于获取并处理所有任务的最终状态非常有用。
到此,在三种状态的语法回答外,还将并发任务及sellted等偏业余的需求表演给面试官,看表情他还是挺满意的... 如果面试官不打断,我准备继续聊异步、红绿灯、手写promise... 面试就要变被动为主动,将自己擅长的表演出来。
const obj3 = {a: 1};
const obj4 = {b: 2};
console.log(obj3 == obj4); // false
console.log(obj3 === obj4); // false
false,false
==
值相等, ===
严格相等,即值和类型都相等
类型转化,虽然obj3
和obj4
值不一样,但面试官要听的是我们对于JS类型转换的理解。
在转换不同的数据类型时,相等和不相等操作符遵循下列基本规则:
如果由一个操作数是布尔值,则在比较相等性之前先将其转换为数值---false转换为0,而true转换为1;
如果一个操作数是字符串,另一个操作数是数值,在比较相等性之前先将字符串转换为数值;
如果一个操作数是对象,另一个操作数不是,则调用对象的valueOf()方法,用得到的基本类型值按照前面的规则进行比较;
这两个操作符在进行比较时则要遵循下列规则。
null和undefined是相等的。
要比较相等性之前,不能将null和undefined转换成其他任何值。
如果有一个操作数是NaN,则相等操作符返回false,而不相等操作符则返回true。重要提示:即使两个操作数都是NaN,相等操作符也返回false;因为按照规则,NaN不等于NaN。
如果两个操作数都是对象,则比较他们是不是同一个对象。如果两个操作数都是指向同一个对象,则相等放回true,否则返回false。
所以这里不会转换两个对象,而是比较他们是不是指向同一个地址,是否是同一个对象。
===
严格相等,这里比较的也是引用地址。
所以,在比较两个对象时,并不会发生类型转换以试图使它们相等。相等性判断直接基于对象的内存地址。
会长提醒过我,面试前要了解下公司的产品和技术栈什么的。百度是搜索公司,前端和搜索关联的肯定是HTML5
的语义化了,SSR
后面的题目会讲到。当然针对百度, 我也准备了一些AI相关的内容,后面果然被问到了。
html5 新增了语义化标签 header、nav、section、aside、footer、aside、article一边讲标签, 一边把脑子里的这张图(掘金详情页)讲给面试官。这样说比干巴巴讲单词,好太多。
div + css 能解决布局问题,但是可读性不好,代码不好维护
SEO 搜索引擎优化
更好地支持各种终端,例如无障碍阅读和有声小说等
哟,面试官要使出闪电五连鞭
了,出算法题了。斐波那契或爬楼梯,始于递归,终于动态规划。还好我有准备,引着面试官往我的dp
包围圈里走,大家往下看。
使用递归,快速解决战斗
function fib(n) {
if (n == 0 || n === 1) return 1;
return fib(n - 1) + fib(n - 2);
};
有什么可以优化的地方
时间复杂度是O(n^2),而且递归(自顶向下)的过程中有很多重复计算,我们可以缓存,当然这里可以一步到位,用动态规划(自下向上,状态转移方程),将时间复杂度降到O(n),代码又有了。
function fibonacci(n) {
if (n <= 1) return n;
let fib = [0, 1]; // 保存斐波那契数列的结果
for (let i = 2; i <= n; i++) {
fib[i] = fib[i - 1] + fib[i - 2]; // 计算第i个斐波那契数
}
return fib[n];
}
面试时间大概来到了25分钟左右,前面的JS基础面试官还比较满意。但是后面还有十几位候选人要面,貌美如花的女朋友约了晚上一起吃饭,算法是杀招。不会就挂电话不浪费时间,表现好的话就在小本本上记下明天通知二面。于是,面试官来了道动态规划难点的题。
给你两个单词 `word1` 和 `word2`, 请返回将 `word1` 转换成 `word2` 所使用的最少操作数。
你可以对一个单词进行如下三种操作:
- 插入一个字符
- 删除一个字符
- 替换一个字符
首先,这道动态规划题有些复杂,大家需要刷一些基础的动态规划题再来搞这道。像百度等这种级别的大厂,算法题我称为
三板斧
。一般是1-2道简单或中等题,再是1-2道有难度的动态规划题。只要我们准备好动态规划由简到难的各种常考题型,扛住面试官的前三板斧子,基本没有问题。
举例
word1 horse
word2 ros
1. word1 变
horse -> rorse h->r 替换
rorse -> rose 删除最2个r
rose-> ros 删除最后e
操作次数是3次 由word1 变成word2
再换个思考 由word2变成word1也是可以的
2. word2 变
ros -> rose 添加e
rose->rorse 添加r
rorse-> horse r 替换为h
其实就是上面的逆向操作
3. 都变
horse -> rorse h-> r word1 变
rorse -> rose 删除r word1 变
ros -> rose word加e word2 变
word1 和word2 都操作了, 一共操作了3次
分析demo,方便等下我们考虑最优子结构的各种情况
天啊, 太复杂了,替换、删除、添加三种操作、两个单词混合修改。别怕,最值问题求解,就用动态规划。看上去复杂的dp
问题,使用动规五部曲
就能化腐朽为神奇。
定义dp数组 比较两个字符串,那么我们要定义一个二维的dp[i][j],两个字符串的最少操作次数(无论哪个操作字符,哪种操作,都可以用二维矩阵涵盖)
以i-1结尾的word1 ,以j-1结尾的word2
dp[i][j]就是让两个字符相同的最少操作次数,根据动态规划的局部最优亦是全局最优,最后的dp[m][n]就是我们要的结果
递推公式
我们要比较两个字符串,就要比较每个元素,那么要比较哪些元素呢?
word1[i-1]、word2[j-1] 递推,自顶向下思考
// 如果两个字符相同
if (word1[i-1] == word2[j-1]) {
// 不需要添加、删除、替换元素
// 最少操作次数可以是不改变的,取上一次的最少操作数,因为dp局部最优也是全局最优的
dp[i][j] = dp[i-1][j-1]
} else {
// 上操作
// dp[i][j] = dp[i - 1][j - 1] + 1, // 替换操作
// dp[i][j] = dp[i][j - 1] + 1, // 插入操作
//dp[i - 1][j] + 1)// 删除操作
dp[i][j] = Math.min(
dp[i - 1][j - 1] + 1, // 替换操作
Math.min(dp[i][j - 1] + 1, // 插入操作
dp[i - 1][j] + 1) // 删除操作
);
// 总之, 根据动态规划的思想,只要有一步操作就可以到达
}
初始化
怎么来考虑初始化问题呢?上图dp[i][j]会由左上角的dp[i-1]j-1、左边dp[i-1][j]、上边dp[i][j-1]三个方向迭代而来。所以在初始化的时候,我们需要把第一行dp[]和第一列都初始化,这样就可以递推出相应的值。
for (let i = 0; i <= m; i++) {
dp[i][0] = i;
}
for (let j = 0; j <= n; j++) {
dp[0][j] = j;
}
迭代
//自底向上, 迭代
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
.....
}
}
返回结果
最后的dp[m][n] 也就是二维矩阵的右下角。
通过动态规划五步走,代码如下:
/**
* @param {string} word1
* @param {string} word2
* @return {number}
*/
function minDistance(word1, word2) {
const m = word1.length;
const n = word2.length;
// 初始化一个(m+1) x (n+1)的矩阵,第一行和第一列分别表示空串到word1前i个字符、空串到word2前j个字符的距离
const dp = new Array(m + 1).fill(null).map(() => new Array(n + 1).fill(0));
// 初始化边界条件:空字符串转换成任意长度的字符串至少需要该字符串的长度次操作
for (let i = 0; i <= m; i++) {
dp[i][0] = i;
}
for (let j = 0; j <= n; j++) {
dp[0][j] = j;
}
// 动态规划填充矩阵
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (word1[i - 1] === word2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1]; // 如果两个字符相等,则不需要消耗操作次数
} else {
dp[i][j] = Math.min(
dp[i - 1][j - 1] + 1, // 替换操作
Math.min(dp[i][j - 1] + 1, // 插入操作
dp[i - 1][j] + 1) // 删除操作
);
}
}
}
return dp[m][n]; // 最终答案位于dp数组右下角
}
之前申请了Github的Copilot
,学生党免费嘛。对代码提速和源码学习都有挺大帮助的。也试过下通义千问的vscode 插件,挺好的。
使用各种chat bot(Chatgpt等),从前端到后端、AI学习、数据库等,提问式学习及解决问题,拥抱AI Native。
刻意练习一些prompt 的技巧, 生成前端页面、SQL等 比如在做后台管理系统的时候, tailwind的一些页面,基本都是chat 完成。
学习transfromer、openai 等AIGC类技能,将一部分的编程任务交给Agent来完成,发挥大模型的能力。最近在学习LangChain
, 对AI很感兴趣。
面试官好像突然来了兴趣,哐哐问了我一堆扩散模型、pandas,越听越像鸟语,真不会啊。这时想起来,会长的分享,面试里有不会的很正常,可以适当的请教面试官。果不其然,在我很有礼貌的说出请教后,面试官给我讲了一通,我瞬间表示学到很多,希望跟着他多学习,感觉面试官有种突然想收我的感觉。
之前有朋友说,会一点的东西不要写到简历上。但也觉得,AI如此火的今年春招,建议把自己会的都加上。面试被追问不可怕, 新的知识表现出学习兴奋和激情,再适当的拍拍面试官的马屁,可以让面试官有更深的印象。
推荐大家看下 关于三次握手与四次挥手面试官想考我们什么?--- 不看后悔系列 - 掘金 (juejin.cn),我就按这个来回答的。
不出意外,面试官继续追问 为什么是三次握手,不是两次或者四次
三次握手是确定客户端和服务端接收和发送能力都正常(HTTP)的最优次数
第一次:客户端发送能力正常
第二次:服务端接收能力正常,服务端发送能力正常(接收和发送可以合并)
第三次:客户端接收能力正常
新手在实习的时候,git 操作和 跨域(前后端联调)是两个动手的要点。
首先,跨域问题源于浏览器的同源策略,该策略限制了从一个源加载的网页脚本如何与另一个源交互。
jsonp (JSON with Padding)
利用 <script>
标签不受同源策略限制,通过动态创建 src
属性指向服务端提供的接口,并带上回调函数名,服务端返回调用这个回调函数并携带数据的JS代码。
WebSocket API
WebSocket协议本身支持跨域,通过握手过程中服务器发送正确的Access-Control-Allow-Origin
来实现跨域通信。
POSTMessage API
主要用于窗口间通信,比如iframe和父页面间的跨域通信,通过window.postMessage()
方法发送消息,同时监听message
事件接收消息。
Image Beacon
利用图片请求可以跨域的特点,通过创建Image对象并向其src属性设置跨域URL进行GET方式的数据传输。不适用于POST或复杂操作。
CORS(Cross-Origin Resource Sharing)
跟其它的方案不一样的地方,它单独在服务器设置。
服务器端设置允许跨域请求头(如Access-Control-Allow-Origin
),浏览器在发起跨域请求时会检查响应头是否允许本次请求。
//具体代码
app.use((req, res, next) => {
// 设置允许任何来源进行跨域访问(生产环境请替换为具体的源)
res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*');
// 允许浏览器预检请求(OPTIONS)通过
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
// 允许携带认证信息(如cookies)
res.setHeader('Access-Control-Allow-Credentials', true);
// 允许自定义请求头
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With');
// 对于预检请求,直接返回成功状态码
if (req.method === 'OPTIONS') {
return res.sendStatus(204);
}
next();
});
面试来到了四十几分钟,面试官开始问工程化相关的问题了。之前会长在给我做模拟面试的时候,有问到工程化的问题。当时一脸懵逼,会长给我讲了一遍,还交给我在这种问题上延申表达,展示自己在工程化深度的一些方法。舒服,完美表演二十分钟,一小时左右的面试应该会完美收官。
越想越开心, 赶快收拾下内心的荡漾,组织语言,回答面试官。
我们先不要急于回答问题本身,会长建议我多想想面试官通过面试题想考察我们哪些点。一场前端面试无非包含前端基础(js/css/html5/es6)、算法(排序、动态规划)、项目(vue / node)、计算机基础(计网/数据库/数据结构/操作系统)、工程化(webpack/vite)等。所以像工程化这种,在拿到题目后,尽量把这块的知识,以题目为中心全盘托出给面试题。一来展示了自己对这块知识点的理解广度和深度,二来不经意延长了优质面试时长,面试官会更有兴趣随时打断我们,“参与”进来。殊不知,面试官一参与进来,就又进入到我的包围圈,他会问什么问题,怎么问,就是我们预先准备好的。所以, 建议大家多做做模拟面试,自己当面试官,会有利于这一技巧的把握。
工程化、历史及现状
在开发前端项目的时候,工程化工具是标配。比如项目的初始化,让我们快速开始业务开发;模块化的支持,方便组织和复用代码;各种资源的处理和加载,如css、图片、字体等,并将其压缩或优化后放入最后的代码包;各种loader和plugin,按需定制编译流程 (stylus/ts/jsx)、压缩(MiniCssExtractPlugin);热更新等
Webpack和Vite等构建工具旨在解决前端开发中的复杂性和规模问题,通过自动化处理、模块化管理、性能优化等手段极大地提高了开发效率和应用性能,现代前端开发实践中不可或缺的部分,即前端工程化。
之前,webpack是主流,但相对复杂,有点慢。vite 非常快,更简单,有种取代webpack地位之势。
bundle 与 bundless
webpack:一切皆可打包,是目前使用率最高的工程化框架,帮助我们打理代码调试到打包的全过程,但是也有一些缺点:
webpack在项目调试之前,要把所有文件的依赖关系收集完,打包后才能运行,这是它慢的主要原因,随着项目规模(强调,新手有这个视野很nice)的激增,慢的一坨屎一样(数分钟)。于是,针对webpack的bundle思路,社区推出了bundless思路框架:Vite。
从bundle
到bundless
,原因是浏览器里的JavaScript没有很好的方式去引入其他文件
。Webpack
(node环境运行) 只能通过require
(commonJS) 将一堆js 按依赖关系组织起来,打包后运行。但是现在主流浏览器都支持ES6的module功能(import/export)。
xml
复制代码
<script type="module" src="/src/main.js"></script>
只要在script标签上添加type="module"标记, main.js 就可以直接使用import 语法(动态导入)去引入js 文件,所以可以不用webpack(node)打包功能,直接使用浏览器的module功能就可以组织我们的代码。
Vite 能够做到这么快的原因,还有一部分是因为使用了 esbuild 去解析 JavaScript 文件。esbuild 是一个用 Go 语言实现的 JavaScript 打包器,支持 JavaScript 和 TypeScript 语法,现在前端工程化领域的工具也越来越多地使用 Go 和 Rust 等更高效的语言书写,这也是性能优化的一个方向。
到这里,我们就向面试官讲清楚了 bundle 和bundless。这才是vite 更快的关键。
优缺点
上面有太多东西可以说了, 后面打算开一个专题单聊,这里就不过多展开了。
接下来我们再怎么扩展工程化这块的表达,主动把握优质面试时长,让面试官感到惊艳呢?这一方面是面试技巧,一方面也是检测面试准备充分程度。
我通过之前的面经收集了以下问题,大家可以按这个列表扩展:
webpack.config.js 中 entry, output, module.rules, plugins 等关键配置项的作用及其具体用法
代码分割、Tree Shaking 和懒加载等技术
Vite 开发时,如何处理 CSS 预处理器
手写Vite
热更新机制的理解
百度的面试还是比较专业的,能学到很多东西。对于刚准备春招的我,真是有点吓尿了。不管结果怎么样, 继续前行吧,要去亲戚家拜年了,先写到这,我会不断更新,写出万字长文的,以祭奠我被百度伤害的初面...
找学长内推,会比自己投好很多
吸星大法,问下学长面试经验,社区里多刷题
模拟面试,找些伙伴,假设自己是面试官, 你会问伙伴哪些问题,这样来准备
自我介绍最好写出来,多朗诵
js基础要准备好promise系列、JS语法系列、ES6系列,面试不是被考试,而是当面展示自己。滔滔不绝,相关联知识不停说。让面试官觉得我们基础扎实,对JS有激情
算法准备好动态规划由简到难,常考题一定要刷刷,状态转移方程可以计下来。
不要怕回答不上来,请教面试官,快速学习。
原文地址: https://juejin.cn/post/7335337310547017768
侵删
最后
还没有使用过我们刷题网站(https://fe.ecool.fun/)或者前端面试题宝典的同学,如果近期准备或者正在找工作,千万不要错过,题库主打无广告和更新快哦~。
我们团队的前端辅导也做了将近2年了,陆陆续续辅导了几百位同学,分享一下最近几个结束辅导的回访。