双非本科的大厂面经总结,不是很卷!(新鲜出炉)

程序员成长指北

共 6532字,需浏览 14分钟

 · 2021-12-28

大厂技术  高级前端  Node进阶

点击上方 程序员成长指北,关注公众号

回复1,加入高级Node交流群

作者:Uni (感谢小伙伴投稿)

原文:https://fwf-studio.feishu.cn/docs/doccnqyJfOyD3Q5JPG2K0PfAPnf#

个人背景介绍

嗨咯大家好,我是 Uni。本人就读于某双非一本大学计算机系,大一的时候在疲于提升绩点后,发现自己根本不知道计算机有哪些领域,能够干啥。于是在互联网上广泛搜索计算机有哪些领域、需要学什么、能干什么后,确定了自己喜欢的领域:前端。在我看来前端就是美学与逻辑的完美结合,也是那个时候坚定了自己的座右铭:无论是技艺、构图、还是大胆的想法,达到极致,即为艺术。于是在大一下,我开始了我的前端自学之路。

后来大二的时候跟志同道合的朋友们一起组建了一个技术组织,一起写项目、学技术,也是一段快乐时光了。

在今年的3月份(2021年),考虑到家里和学业的缘故,在实习和字节跳动训练营之间选择了参加字节跳动的前端训练营,并取得了不错的名次。原本是要准备秋招的,但是暑假因为算法比赛和其他缘故又搁置了。

因为一直很喜欢网易云音乐这个产品,从大三的时候就一直 follow 网易云音乐大前端团队在掘金上的文章,并一直在关注着网易云音乐的岗位。然后很幸运地发现网易云音乐现在正在招实习生于是便投了。网易的效率很高,两三天后就约面试了,所以最近有且仅面了网易。

因为最近半年基本因为一些原因一直在打算法比赛和学机器学习相关的东西,一直没怎么碰过 Web,所以在面试之初还是有些紧张的。

技术一面

面试官人很nice,一开始怕我紧张就一直让我介绍自己的项目。一面主要是在深挖项目。

介绍下在字节跳动训练营的这个项目

回答:这个项目是一个在线 markdown 编辑器,用的是 React 及其相关生态做的前端,Koa2 做的服务端,采用的MongoDB数据库。有邮箱验证、JWT鉴权、文件上传并返回链接、将 markdown 语法编写的文本转换成 HTML、采用 websocket 做的在线错误日志等功能。

为什么要做这个基于 websocket 的在线错误日志?主要是怎么实现的?

回答:做这个功能的原因主要是当时为了练技术,并没有从整个产品的角度去考虑这一块功能,仅仅是为了实现而实现。在实现上,首先我后端的所有响应类型都是基于 SuccessModelErrorModel 这两个类产生的,这样能够保证我的响应格式的统一(都是 msgdatacode

然后通过 Node 中 fs 模块的 appendFile 方法将错误信息传入 errorLog.txt 这个文件,如果没有这个文件是会自动生成的。

然后在管理员打开错误日志的前端页面的时候会建立 websocket 连接,并将 errorLog.txt 文件中的记录当作历史日志传给前端并倒序渲染出来。这个时候同时调用 fs.watch 方法对 errorLog.txt 文件的变化进行监听,如果有错误日志写入文件中,那么文件就变化了,就会通过 websocket 将新增的错误日志记录主动广播给前端,以此达到管理员在日志界面时可以看到实时的错误信息的效果。

PS:做这个功能的目的是为做而做,并没有考虑那么多,也没有过这种场景的经验,所以做的很不规范。只是为了尝试、锻炼一下。

面试官:我看到你简历上有一个在线聊天室的项目,用到了 socket.io 来做实时通讯这一块,而你训练营的项目用的是 ws 这个 npm 库,能说说为什么用 socket.io 吗以及 socket.io 和 ws 之间的对比。

回答:用 socket.io 主要原因还是为了尝试新的东西,其实这两个我都没有钻的很深,只是为了需求去实现。

我所了解到的是,相比于 ws,socket.io 在客户端有良好的支持,但是 ws 没有,在客户端写的时候还需要自己去封装。其次就是 socket.io 是有回退方案的,在不支持 websocket 的时候会回退到 HTTP 长轮训的方案。(这一块答的不怎么好,因为确实对这两个库使用的不多,理解的不深)

能说说你项目中图片上传那一块是怎么实现的吗?

回答:在项目的一开始,我是采用 base64 的方式实现的图片上传,但是这样的话每次请求都会传一个超长的字符串,这样会占据更多的空间资源。所以后来我换成了文件流的形式上传。前端通过事件对象和 FormData 的配合,将数据传给后端。

然后后端我引用了 formidable-upload-koafs-extra 这两个库,将传过来的文件格式进行解析并移动到一个暴露在外的可访问目录下,最后再将文件路径存于数据库中并返回路径给前端,前端每次获取图片只需要请求暴露在外的路径即可,就相当于是做了一个简陋的图床。

(反思:面试官问完我这个问题后,我虽然说出了自己实现的思路以及为什么用这个方案的原因,但是却没有实打实地研究过这两个方案到底适合什么场景,也没有仔细思考过到底是不是很项目,只是为了用技术而用。通过前面几个问题发现了自己思维上的大短板,就是很少思考项目本身需要什么技术,用什么技术合适。)

如果有海量请求来了,你在项目中是如何处理这些高并发请求的呢?

回答:因为我没有实际遇到过这种场景所以我也没有具体了解过相关的解决方案。但是 I/O 密集型是 Node的强项,我后端所有的 I/O 处理都是采用异步的方式。然后前端也会对一些操作做防抖节流,来防止一些无效或者重复的请求。

你刚刚说到了防抖节流,能讲讲他们之间的区别吗?

回答:防抖在单位时间内触发的事件会被重置,防止误触多次事件。节流就是单位时间内只触发一次。(回答完我摸了摸键盘准备等着面试官让我手写防抖节流,但是他没有继续往下问了)

你项目都是 React 是吧?(是的,我 hook 写的比较多)那你介绍一下你常用的 hook 吧

说了几个常用的 hook,然后重点讲了一下 useEffectuseLayoutEffect 的区别,通过他们渲染时机的区别讲了讲项目中遇到过的页面闪烁的问题并怎么解决的。

那你来手写实现一下这道题吧

class Event {
    // 触发事件
    trigger(eventName) {}
    // 注册事件
    on(eventName, callback) {}
    // 销毁事件
    off(eventName) {}
}

// case
const event = new Event()
event.on("eventA", (a) => {
    console.log(a)
})
event.on("eventA", (a, b) => {
    console.log(a)
    console.log(b)
})
event.trigger("eventA"12)   // print: 1 1 2

我的实现:

class Event {
    constructor() {
        this.map = new Map()
    }

    // 注册事件
    on(eventName, callback) {
        !this.map.has(eventName) && this.map.set(eventName, [])
        const arr = this.map.get(eventName)
        arr.push(callback)
        this.map.set(eventName, arr)
    }
    // 触发事件
    trigger(eventName, ...args) {
        const arr = this.map.get(eventName)
        arr.forEach(item => {
            item(...args)
        })
    }
    // 销毁事件
    off(eventName) {
        this.map.delete(eventName)
    }
}

追问:如果这个 off 方法加一个 callback 参数,我想要每次注销的事件是这一组同名事件中的具体的某一个呢?

回答:如果这个 callback 是外部作用域有引用的,然后传入参数的话,那直接 forEach 判断一下是否相等然后直接对数组使用 splice方法删除即可,因为都是同一个引用嘛。如果是这种场景的话我这里就可能用 WeakMap 会比 Map好些,这样可以防止内存泄漏的情况发生

技术二面

面试官人也很nice,我运气真的太好了,遇到的都是彬彬有礼、很是温和的面试官。首先还是上来跟面试官介绍了一下项目,这里就简单跳过。

你对跨域的方案有了解吗?你的项目里是怎么实现跨域的?

我了解到的跨域方案有 jsonpCORSpostMessgae 以及 Websocket。在我的项目中用的是 CORS 跨域的方案。

你刚刚说到CORS 跨域,哪请问 options 是在什么情况下触发的呢?

回答:不会(这个真没了解到) 下来后立马百度了解了相关知识:浏览器会对于非简单请求会触发一次预检的请求,对应的 HTTP Request Method 为 OPTIONS。这个请求对服务器是安全的,也就是说不会对服务器的资源做任何改变,仅仅用于确认 header 响应。具体的欢迎参考这篇文章进行了解: https://zhuanlan.zhihu.com/p/70032617

让我手写 JS 的继承方式

先问:你平时都看什么技术书

  • 答:红宝书和《你不知道的JS》

再问:那你能讲讲这些书里面让你印象深刻的知识点吗

  • 我说了说继承、原型链、JS执行机制等

继续问:那你说说有什么继承方式吧(其实我很想让面试官问我JS整个执行机制和词法作用域具体机制来着,整个当时深钻了很长时间)

回答(让我手写 JS 的继承方式,我写了三种):

// 1.借助原型链
function Parent({
    this.name = "Uni"
}

function Child({}

Child.prototype = new Parent()
Child.prototype.constructor = Child

// 2.借助 call 方法
function Parent({
    this.name = "Uni"
}

function Child({
    Parent.call(this)
    this.age = 21
}

const child = new Child()

// 3.寄生组合式继承
function clone (parent, child{
    child.prototype = Object.create(parent.prototype)
    child.prototype.constructor = child
}

function Parent({
    this.name = "Uni"
}  

function Child({
    Parent.call(this)
    this.age = 21
}

clone(Parent, Child)

面试的时候其实我是边写边跟面试官说每种写法的优缺点和为什么用这种写法,所以自然而然地按照这个顺序写下来了,但是由于有些紧张加上边写边说,所以有些磕磕绊绊,但是总的还是回答完了这个问题。

如果有一个模态框,想要点击模态框以外的区域让模态框消失,应该怎么做?

回答:我从事件冒泡和事件捕获两种方式进行了回答。冒泡的方式很好地答了出来,但是面试官一直在不断追问我一些情形,然后我脑子卡壳了捕获就没有答的很好。因为是面经具体答案就不阐述了,这个问题手动写写试一下就知道答案了。

CSS优先级顺序能说说吗?

回答:当时因为还在纠结前面捕获的事情,脑子一团混乱,就迷迷糊糊答得很差。面完后重新看了下这块的内容,具体可以参考这两篇文章:

1、https://zhuanlan.zhihu.com/p/41604775
2、https://zhuanlan.zhihu.com/p/23047507

有读过什么框架和库的源码吗(我其实研究过一段时间的 React 和 JQuery,但是因为最近半年一直在打算法和研究机器学习方面的内容,前端很多东西都忘了,就求稳说了一个 JQuery),请讲讲JQuery 源码方面的内容。

  • 回答:我讲了讲 JQuery 的大致机制和外部的自执行匿名函数。

追问:JQuery 是如何做到链式调用的

  • 回答:返回 this(如下是示例,面试的时候并没有写代码只是口头讲解)
const MyJQ = function(){}
MyJQ.prototype = {
    css:function(){
       console.log("设置css样式");
        return this;
    },
   show:function(){
        console.log("将元素显示");
       return this;
    },
   hide:function(){
        console.log("将元素隐藏");
   }
};
const myjq = new MyJQ();
myjq.css().css().show().hide();

写一道算法题(有效括号,LeetCode 20)

就用一个辅助栈就好了,感兴趣的朋友可以去刷一下

反思与总结

其实说实话,面经中写面试题很难说起到一个参考或者帮助作用,因为面试题是不固定的、面试官是不确定的、面试难度和应聘者实力也是不一定匹配的。这就像考试一样,有些人拿一百分是因为卷子满分就一百分。

但是面试和考试不一样的地方在于,面试是你与面试官交流的媒介,是要体现个人综合实力的地方,也是一个学习的渠道。所以我认为面经最重要的地方其实在于面试者下来的反思和思考。

我的反思

通过这次面试,其实我最大的感触是对业务上思考太少,我很少会去从产品功能的角度去思考我的技术。这会导致我的技术方案或者做法缺少业务价值。比如在做在线 markdown 编辑器的时候我的关注点只在于我怎么做出来这个语法转译的功能,但却没有思考如果从用户角度想要定制我的一些 markdown 语法转译后的样式我该怎么去做,所以我的整个功能可扩展性就非常地低。因为缺少业务上的思考,所以我开发的时候会更难想到一些更隐性的场景,比如性能优化、功能的可扩展性和完备性。这是我需要再多刻意练习的。

关于面试的建议

首先,我觉得应该抱着学习的心态多面试,不论是找实习的同学还是找校招的同学。面试就是一个从面试官那取经、发现不足的地方。所以一定一定不要紧张,要放开手脚来。在面试的过程中可以多跟面试官交流,可以根据某道题聊聊你的思考,也可以在反问环节向面试官请教自身的不足,也可以带着面试官一起剖析反思自己。我面试的时候就是抱着这样的心态去跟面试官交流,最后也都跟面试官们聊开了,也得到了许多宝贵的建议。真就是面到就是赚到!最后,不得不说整个流程下来,我的体验是非常好的,HR推进流程的效率非常高,面试官也都很好沟通愿意引导我给我一些建议。

Node 社群


我组建了一个氛围特别好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你对Node.js学习感兴趣的话(后续有计划也可以),我们可以一起进行Node.js相关的交流、学习、共建。下方加 考拉 好友回复「Node」即可。


   “分享、点赞在看” 支持一波👍

浏览 29
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报