apitable-面试体验

背景介绍,vika维格是一家专做互联网产品的企业,apitable是他们的开源在线协作办公工具的企业,该企业全员远程办公,招聘包括React前端、Java后端等职位,于是我想着去面试试试

起因

最近一直热衷于找各种远程工作,在直聘平台上看到了该公司有在招人,于是就前往应聘。

首轮对战

跟HR沟通后,对方也不管三七二十一,直接就一大段话复制粘贴扔过来,大致意思就是“我们的产品是github开源的,想要应聘就得自己去下载源代码并在开发环境下运行整个项目”,并且号称有经验的工程师一分钟就能搞定。
于是我一个应聘前端开发职位的人,必须按照说明下载了一堆全栈多语言开发环境,并且花了大量时间在解决说明里并无提及的各类问题,最终耗时一天终于搭建完成。
再回想前面那段话里不知道哪位大神夸下的海口“一分钟就能搞定”就想笑,按照这个项目要求,让我给他们提供一个基础环境,接着让他们开发部员工来搭建,网速保够的情况下,保证一小时都搞不定。
完成运行开发环境并按要求发成功截图后,HR又发了一个链接,说点进去填好告诉她。

奇怪的表单

这个表单信息填写,说是表单,其实更像是笔试,因为里面最重要的两题一个是直接让我按照别人提供的issue在他们项目里去实现一个自定义表格字段的功能并提交代码,另一个是让我也提出一个issue,最后还要报告填表所耗时间,选项只有“5分钟以内/30分钟/1小时以内/1小时以上”,说真的看到这种白嫖成果一样的笔试我是有点想直接放弃的,不过本着研究的心态还是看了看,不过没有完成,只是以半产品半开发的角度讲解了一下思路,并也将研究过程中发现的问题作为issue提交。
本来以为没有提供实现代码,应聘是到此为止,谁知道过了几天HR竟然说通过了笔试,准备进行下一轮正式视频面试。

正式面试

这次首轮面试对方派出了一个前端开发岗的员工作为面试官,按照我后续了解是总共会有三轮视频面试,第一轮就是这个相同职位的人,接着第二轮是产品负责人(我的理解就是产品经理),第三轮是技术负责人。
这样的面试顺序让我感觉如果第一轮是通过面试的话,基本后续就是畅通无阻?毕竟要说负责产品的时长、内容丰富程度可能我比现有负责人都要有经验得多,毕竟我原本的工作就是包含了一部分产品经理的职责。
而这第一轮,我想着是小喽啰必定手到擒来,结果当然是大受打击,竟然落选了。
本来一般我以为有经验的人都是看重理论结合实践的,特别是很长一段时间的面试以来,一般做自己产品的甲方都会关注算法、数据结构、计算机原理再到实际中的并发、渲染等实际问题。
谁知道这个面试官沉迷各种理论背诵,上来就是各种要不熟悉到都不记得原理怎么背诵,要不就是已经用不太上的理论,当然这可能是我个人的暴论,所以还是借此文章理顺一下,也给大家做个参考。
面试内容如下:

讲讲js的event loop

这个英文我差点不知道是啥东西,理解为nodejs的事件轮询了(实际面试官好像是想听我讲前端包含UI的事件循环),这个我虽然知道是如何运作,但也不知道有什么好讲,只能简单直白地说有个事件队列,每个回调或者异步都会被放入此队列中,当程序空闲时就会去读取这个队列,对已经完成的事件执行相应的处理方法。
这么介绍完,面试的人貌似不太满意,觉得我完全没掌握,于是直接写了一段代码,包含了延时事件、IO回调和主线程的打印,询问我这几个打印事件发生的顺序,代码大致如下:

fs.readFile("/path/to/file", () => console.log("readFile"))
setTimeout(() => console.log("setTimeout"))
console.log("main")

我的回答是readFile和setTimeout都会先被放入事件队列中,之后继续执行直到空闲,之后才去轮询事件队列,看看哪个事件完成了就执行它的回调处理,因此输出顺序是:

main
setTimeout
readFile

对此面试的人也没说什么,我姑且当它是过了吧,有兴趣的可以看看这个详解JavaScript中的Event Loop(事件循环)机制

讲讲Css的几种现代写法

  • Sass、Less,Css预编译器,最强大的地方当然是支持父子样式嵌套、变量、运算、调用自定义函数等,擅长Css的人如我的最爱,也是最常使用的一种。
  • Css-in-Js,如Styled等这种,记得是可以用定义对象和调用函数的方式在像jsx标签里定义样式,我了解过但没用过。
  • TailWindCSS,类似当年的Boostrap.css,只定义了各种样式名,直接组合不同样式名定义布局、组件样式、颜色、字体大小等,比较具有争议的一种方式,因为往往需要写入一大串样式名才能定义一个想要的标签或布局,为了重复利用不得不先用大量样式定义一个组件,再使用这个组件,而无法发挥样式表本身简洁抽象的对象与属性的定义方式。

其实还有一种是组件化,如ElementUI、Antdesign这种,直接使用组件而不需要太多考虑如何写Css,不过自由度比较低,好处当然就是大家写的外观绝对一致。

react生命周期

问生命周期时我整个人都懵了,不知道有啥好讲。
特别是React17之后全面采用hook与函数组件,所谓生命周期跟不存在一样,只剩下不断的执行整个函数组件。
当年我最喜爱的类组件结构分明,各种阶段事件也都作为方法一一存在,如constructor、componentDidMount、componentDidUpdate、render、getDerivedStateFromProps等,条理分明,而当时我也有认真去理解整个生命周期。
然而现在函数组件变成一坨函数,所谓周期根本感觉不到存在感,也就早早忘光了。
正当我在回想整个周期顺序是什么时,面试官看到我没讲话,于是换了个问题,让我讲讲react都有哪些hook函数。
于是我简单说了像是useState、useEffect、useDispatch、useMemo、useCallback和大概的作用,其中顺带介绍了useEffect第二参数和回调中的返回值对应生命周期的哪个阶段。
到此我感觉面试可能要黄了,毕竟这位太喜欢理论了,我感觉反过来让我问几个理论对方都不一定答得出来,所谓打工人何必为难打工人,就不能问点实际点的?

react自定义hook

接着面试官似乎想询问各种对现有的hook库的使用熟练程度,如useInterval,然而我从来没用过任何别人的hook库,只在需要时根据网上示例实现自己的useMousePositionuseScroll等,面试官本来兴致冲冲,听完我的说法也就沉默无语了。
我也有点汗颜,难道必须使用别人的库用得多才显得了不得?然而这些作为非必要的第三方库很可能每家公司使用的都不一样,能掌握react、vue这些基础框架,加上理解全局状态管理在我看来已经是对第三方库最有效的技能学习,更别说实际真正有用的是html、css、js、wasm,这些框架与库都是随着时间发展随时可能被抛弃的东西,再深入研究也没太大作用,除非你想要自己实现一个。

typescript实践题

接着突然来到了实践题环节(虽然前面的react hook库也算是实践,但因为我没用过于是被跳过),面试官给我甩了下面一段代码,一个已经实现功能的函数,但缺少类型约束,要求我完成约束:

//  缺少类型约束的函数
function getItem(args: any) {
    
    
    return Array.isArray(args) ? args[0] : args
}

const result = getItem([1, 2, 3])

看到该题目时我第一时间想到的是使用推断infer才能按照函数功能推断出第一个值的类型,但infer我比较少用,一般都是看着自己写的笔记一边完成的,现在是面试,我就不好意思说先看看笔记,于是只能换种写法使用泛型,代码如下:

//  注意使用了泛型与or的语法来实现
function getItem<T = any>(args: T[] | T): T {
    
    
    return Array.isArray(args) ? args[0] : args
}

const result = getItem([1, 2, 3])

以上代码将会自动根据输入的参数为T[]
,从而判断并限制输出的类型为T,面试官看到这个答案似乎有点意外,而我询问他是否希望我用infer实现时,他又强调这是开放性的题目,何种实现都可以,而我看他有点不甘的样子,我就顺势交流一波,反问他如果你用infer或者别的会怎么实现,实际代码我不记得,但大致记得是用infer推断数组项的类型:

// 使用infer判断是否为数组并返回数组项类型
function getItem<T = any>(args: T): T extends (infer U)[] ? U : T {
    
    
    return Array.isArray(args) ? args[0] : args
}

const result = getItem([1, 2, 3])

然而这个方式并没有真正解决一个问题,那就是函数功能是返回数组的第一项,而js中的数组是可以每项都是不同类型的,他和我的写法都只是推断了数组所有项的类型而非第一项的类型。
例如我调用函数时输入[1, "2", 3],期望的判断的类型应当是number,但我两的写法都只会推断出number | string
,这是不符合函数功能的类型判断。
而我一开始就想到了这个问题,也想到了infer可以推断数组第一项的特性,只是我忘记了怎么写。
在看到面试官的实现也是没有考虑这点后,我对他提出这个问题,而他似乎也不知道应当如何解决,只是简单说了用这种智能判断就足以,直到我提醒他infer语法也可以推断数组的第一项,他才想尝试去写出来,但很可惜他也不记得如何写,于是宣告放弃。
事后我也特意按照我笔记里的infer语法尝试去只推断出数组第一项的值,但很可惜这种方式是无用的,因为在形参阶段就会因为类型定义把数组所有项的类型合并,如[1, "2", true]
会变成类型number|string|boolean,从而无法再根据实参判断第一项究竟是什么类型,具体可以按照我以下的错误代码进行试验和改进:

第一种方式,使用infer推断第一项类型:

function getItem<T = any>(args: T): T extends [infer FirstItem, ...infer Others] ? FirstItem : T {
    
    
    return Array.isArray(args) ? args[0] : args
}

const result = getItem([1, "2", true])

第二种方式,直接通过索引指定第一项类型:

function getItem<T = any>(args: T): T extends unknown[] ? T[0] : T {
    
    
    return Array.isArray(args) ? args[0] : args
}

const result = getItem([1, "2", true])

以上两种方法推断的类型都是所有项的类型集合number|string|boolean,因为在定义形参类型时,args: T
已经把所有项的类型都合在一起,无法再分辨,到此无解,欢迎有能力的人留言这种情况如何解决。

代码review与测试

接着面试官又提供了一段代码,是一个函数实现,要求挑出里面的问题:

function getSId() {
    
    
    if (window["_option_data"]) {
    
    
        if (window["_option_data"].SID) {
    
    
            return window["_option_data"].SID
        } else {
    
    
            return 100;
        }
    } else if (window.SID) {
    
    
        return window.SID;
    } else {
    
    
        return 100;
    }
}

我的观点是:

  • 内部直接引用了全局变量window,作为纯函数应当根据输入决定输出,因此window最好作为参数传递而非直接引用
  • 对window一时采用字面量的语法调用,一会采用.属性值的调用,不够统一
  • 判断的方式有优化的可能性,现在太过繁杂
  • 对于返回的100没有明确的含义,应当定义为常量再引用,如const DEFAULT_ID = 100

现在想想,单纯这个函数名称本身我就不知道是什么意思,SId完全没说明是什么,不过当面面试时我下意识以为他们的习惯就如此,也就没说。

接着面试官要求根据这个函数实现会如何编写测试用例,我就简单写了两种:

  • getSId() 判断值是否我想要的
  • getSId.bind(_myWindow) 判断值是否和前面相同

这里我有点虚,主要虽然各种测试如单元测试、集成测试、组件测试、E2E测试我都做过,但实际经验也挺少,突然给i我这么一个看起来非常原始的函数实现,在没优化完成之前,我还真不知道有什么值得写的测试用例,于是草草了事。

说说Prisma遇到的问题

因为我简历有写到在Nodejs项目里用了Prisma,所以面试官就顺口问遇到过什么问题,然而我说的几个问题貌似面试官从来没面对过,他也就没就此问任何问题:

  1. 模型执行构建生成的步骤不能在Linux系统上执行,会报错,只能通过Docker打包或者Window生成后再放到Linux系统部署使用;
  2. 不支持SQL字段注释,而只能在模型注释,相当于如果有一些运维、领导层、第三方等无法接触源代码而是直接操作数据库的人员,他们将不知道字段含义;
  3. 对于不支持的字段类型如MySQL的TEXT(用于文章长文本)虽然可以强行使用,但会导致无法生成prisma/client对应的表操作函数,如果需要查询该表或者联表只能用最原始的sql语句。

讲讲最近面临的挑战

到这里其实我感觉面试可能过不了了,毕竟这么喜欢理论,就算我勉强及格,也有其他理论背诵充足与高学历的能碾压我,于是我只是敷衍地说了有些低代码平台和汇编问题,之前没接触过,很多不太了解导致的问题。
实际如果想吹逼,我肯定拿我最近学习的音视频直播还有编译器来讲,但那要耗费大量口水,我也就应付了事。

问面试官问题环节

面试完面试官惯例问我有什么问题可以提,我前面被折磨许久,于是连续问了快一小时的问题,大致是:

  • 面试总共有多少轮
  • 工作的安排和流程是如何
  • 如何界定任务所需时间
  • 具体工作内容是什么
  • 对设计或者规划有疑问应该找谁
  • 有好的需求构思如何提出
  • 如果想做全栈是否还要进行后端面试(这里对方的回复挺神奇,说是不用,前端面试进来的想做后端也可以上手)
  • 前后端的工作分配与如何合作
  • 平时的合作是只能一对一语音、文字群聊,还是有专门的在线协作软件像元宇宙一样实时交流

具体回答就不贴出来了,有兴趣的可以留言问我。

面试结果

面试时是周二,但直到周四都没有反馈,于是我只能主动询问HR,最后得到她,安慰的淘汰~
虽然我自认答得还可以,但还是被拒了,感觉现在开发工作很多都是鸡蛋挑骨头,很多明明是追求实践为主的,却喜欢各种理论背诵,真要喜欢理论我还是去做C/C++底层研发,何必做这种互联网应用。最明显就是前些年的算法岗,都是要求211/985硕士博士,再让你学一堆理论知识,实际上班摸鱼三小时,工作三分钟。

俺很强,俺也要报名!

如果大家也想着试试这种远程工作的话,可以尝试apitable等支持远程办公的先进企业:
apitable产品官网
Boss直聘企业主页(背后运营的企业)

猜你喜欢

转载自blog.csdn.net/u013102711/article/details/131154265