likes
comments
collection
share

面试:百度一面,吓尿了

作者站长头像
站长
· 阅读数 2

假如您也和我一样,在准备春招。欢迎加我微信shunwuyu,这里有几十位一心去大厂的友友可以相互鼓励,分享信息,模拟面试,共读源码,齐刷算法,手撕面经。来吧,友友们!

前言

在百度的学长,帮我内推了下简历。高兴还没有两秒,就接到通知面试的电话,瞬间压力山大。小公司也没怎么面,八股文背的也不利索,看到镜头里又秃又强的面试官,有如看恐怖片,吓尿了!!!

百度一面

1. 宇宙铁律,介绍下自己

2. var a = []; a.push()、a.pop() 请说明在js中的意义

  • 使用的是字面量初始化创建了一个空数组,这是最简洁、直观且推荐的写法。这种方法直接在内存中创建并初始化数组对象,执行效率高。

    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

3. promise有几种状态,可以重复改变吗?

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')和对应的valuereason属性。

Promise.all() 更关注所有 Promise 是否都成功完成,它适用于需要所有任务成功完成才能继续下一步场景。而 Promise.allSettled() 则允许你观察一组异步操作的所有结果,无论成功与否,这对于获取并处理所有任务的最终状态非常有用。

到此,在三种状态的语法回答外,还将并发任务及sellted等偏业余的需求表演给面试官,看表情他还是挺满意的... 如果面试官不打断,我准备继续聊异步、红绿灯、手写promise... 面试就要变被动为主动,将自己擅长的表演出来。

4. 输出结果,为什么?

const obj3 = {a: 1}; 
const obj4 = {b: 2}; 
console.log(obj3 == obj4); // false 
console.log(obj3 === obj4); // false

结果:

falsefalse

原因:

  • == 值相等, ===严格相等,即值和类型都相等
  • 类型转化,虽然obj3obj4 值不一样,但面试官要听的是我们对于JS类型转换的理解。
在转换不同的数据类型时,相等和不相等操作符遵循下列基本规则:

如果由一个操作数是布尔值,则在比较相等性之前先将其转换为数值---false转换为0,而true转换为1
如果一个操作数是字符串,另一个操作数是数值,在比较相等性之前先将字符串转换为数值;
如果一个操作数是对象,另一个操作数不是,则调用对象的valueOf()方法,用得到的基本类型值按照前面的规则进行比较;
这两个操作符在进行比较时则要遵循下列规则。

nullundefined是相等的。
要比较相等性之前,不能将nullundefined转换成其他任何值。
如果有一个操作数是NaN,则相等操作符返回false,而不相等操作符则返回true。重要提示:即使两个操作数都是NaN,相等操作符也返回false;因为按照规则,NaN不等于NaN
如果两个操作数都是对象,则比较他们是不是同一个对象。如果两个操作数都是指向同一个对象,则相等放回true,否则返回false

所以这里不会转换两个对象,而是比较他们是不是指向同一个地址,是否是同一个对象。

  • ===严格相等,这里比较的也是引用地址。

所以,在比较两个对象时,并不会发生类型转换以试图使它们相等。相等性判断直接基于对象的内存地址。

5. 聊聊你对语义化的理解

会长提醒过我,面试前要了解下公司的产品和技术栈什么的。百度是搜索公司,前端和搜索关联的肯定是HTML5的语义化了,SSR后面的题目会讲到。当然针对百度, 我也准备了一些AI相关的内容,后面果然被问到了。

6. 斐波那契

哟,面试官要使出闪电五连鞭了,出算法题了。斐波那契或爬楼梯,始于递归,终于动态规划。还好我有准备,引着面试官往我的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基础面试官还比较满意。但是后面还有十几位候选人要面,貌美如花的女朋友约了晚上一起吃饭,算法是杀招。不会就挂电话不浪费时间,表现好的话就在小本本上记下明天通知二面。于是,面试官来了道动态规划难点的题。

7. leetcode 原题编辑距离

给你两个单词 `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数组右下角
}

8. 如何用AI工具 提升开发效率

  • 之前申请了Github的Copilot,学生党免费嘛。对代码提速和源码学习都有挺大帮助的。也试过下通义千问的vscode 插件,挺好的。
  • 使用各种chat bot(Chatgpt等),从前端到后端、AI学习、数据库等,提问式学习及解决问题,拥抱AI Native。
  • 刻意练习一些prompt 的技巧, 生成前端页面、SQL等 比如在做后台管理系统的时候, tailwind的一些页面,基本都是chat 完成。
  • 学习transfromer、openai 等AIGC类技能,将一部分的编程任务交给Agent来完成,发挥大模型的能力。最近在学习LangChain, 对AI很感兴趣。

面试官好像突然来了兴趣,哐哐问了我一堆扩散模型、pandas,越听越像鸟语,真不会啊。这时想起来,会长的分享,面试里有不会的很正常,可以适当的请教面试官。果不其然,在我很有礼貌的说出请教后,面试官给我讲了一通,我瞬间表示学到很多,希望跟着他多学习,感觉面试官有种突然想收我的感觉。

之前有朋友说,会一点的东西不要写到简历上。但也觉得,AI如此火的今年春招,建议把自己会的都加上。面试被追问不可怕, 新的知识表现出学习兴奋和激情,再适当的拍拍面试官的马屁,可以让面试官有更深的印象。

9. 讲一讲HTTP请求三次握手

不出意外,面试官继续追问 为什么是三次握手,不是两次或者四次

三次握手是确定客户端和服务端接收和发送能力都正常(HTTP)的最优次数

  • 第一次:客户端发送能力正常
  • 第二次:服务端接收能力正常,服务端发送能力正常(接收和发送可以合并)
  • 第三次:客户端接收能力正常

10. 说一说跨域

新手在实习的时候,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();
});

11. vite 为什么比webpack快?

面试来到了四十几分钟,面试官开始问工程化相关的问题了。之前会长在给我做模拟面试的时候,有问到工程化的问题。当时一脸懵逼,会长给我讲了一遍,还交给我在这种问题上延申表达,展示自己在工程化深度的一些方法。舒服,完美表演二十分钟,一小时左右的面试应该会完美收官。

越想越开心, 赶快收拾下内心的荡漾,组织语言,回答面试官。

我们先不要急于回答问题本身,会长建议我多想想面试官通过面试题想考察我们哪些点。一场前端面试无非包含前端基础(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。

bundlebundless,原因是浏览器里的JavaScript没有很好的方式去引入其他文件Webpack(node环境运行) 只能通过require(commonJS) 将一堆js 按依赖关系组织起来,打包后运行。但是现在主流浏览器都支持ES6的module功能(import/export)。

    <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有激情
  • 算法准备好动态规划由简到难,常考题一定要刷刷,状态转移方程可以计下来。
  • 不要怕回答不上来,请教面试官,快速学习。

参考资料

假如您也和我一样,在准备春招。欢迎加我微信shunwuyu,这里有几十位一心去大厂的友友可以相互鼓励,分享信息,模拟面试,共读源码,齐刷算法,手撕面经。来吧,友友们!