2024字节前端面经和经验分享,React方向

其实我面了两次字节,第一次是7月初,面的是博彦外包字节的前端岗位,当时因为给的工资不够理想就没去,因为手里有工资更高,地点更近的offer,虽然很喜欢去,但是还是拒绝了。

第二次是这周刚刚进行的,因为当时选的公司可能是因为项目紧急缺人,我要的高,也同意了,但是两个月后,工期不紧张了,估计新来的工资更低,和我商量之后我离职了。但是离职之后,就立马面试字节了,目前在等第三面结果,有点焦虑,焦虑之余,总结下这两次面试的经验,希望可以帮助大家。

时间有点久了,就不分几面几面了。

文章同步在公众号:萌萌哒草头将军,欢迎关注

第一次面试

1.三面都有的开场白

开场白,除了介绍自己的学校和工作情况之外,一定要将话题引到自己引以为豪的项目上,怎么引入这个话题?根据在实际的开发中解决了哪些切实的痛点。

这时候面试官一般问两个问题

  • 让你介绍这个项目的实现过程

  • 最后肯定还会问为啥不使用开源的项目

我主要介绍的是我开源的项目RaETable。实现思路就不多说了。

为啥不使用开源的项目?指出原因,一定要可以自圆其说,例如虽然市面上有很多拖拉拽形成可视化界面的项目了,但是这些工程生成的结果,无法在源码中开发一些其他非列表的页面;而且这个项目是21年之前就开始做的,当时这种开源项目不是很成熟等。

2. ts的泛型是做啥的

泛型是给参数的类型进行抽象,当这个参数的类型是动态的时,可以使用泛型

3. ts的装饰器了解吗

我没使用过,但是知道装饰器模式,一个类在不侵入另一个类的前提下,进行一些行为操作。比如注解的使用。

4. useState的原理

useState 在运行时,会被创建在Fiber类型节点中,hook属性是个链表,指向第一个useState,这个指向也是个hook类型,next属性指向下一个hook,依次类推,其中,Fiber还有个队列queue,存放每个hook对应Update内容,action属性对应dispatcher方法,next指向下一个Update.

上面的回答有点简陋。完整的回答,应该是:react会为每个函数组件创建Function类型的Fiber节点,这个节点有个hook属性,指向第一个hook,其数据结构为Hook类型的Fiber节点,该节点有下面几个属性存放初始值:baseState,存放后面改变的值:memoizedState,存放更改值的回调函数:queue队列,里面是Update类型的节点

5. 我看你博客和建立有提到设计模式,请问你了解哪些设计模式

单例模式、策略模式、装饰器模式、发布订阅模式、观察者模式、工厂模式

6. 接着让我手写一个单例模式
const simple = (function(){
    let instance = null;
    return function () {
        if (!instance) {
            instance = new Object()
        }
        return instance;
    }
})()
7. 实现一个深拷贝
function fun1(arr) {
  const set = new Set([...arr]);
  return [...set];
}

function fun2(arr) {
  let newArr = [];
  arr.forEach((item) => {
    if (!newArr.includes(item)) {
      newArr.push(item);
    }
  });
  return newArr;
}

function fun3(arr) {
  return arr.reduce((per, cuur) => {
    if (!per.includes(cuur)) {
      per.push(cuur);
    }
    return pre;
  }, []);
}

function fun4(old) {
  return JSON.parse(JSON.stringify(old));
}
//对象和数组分别怎么实现
function fun5(old) {
  return Object.assign({}, old);
  return [].concat(old);
}
// 写这个的时候,面试说可以兼容多种类型吗
function fun6(old) {
  if (typeof old !== "object") return old;
  let newObj = Array.isArray(old) ? [] : {};
  for (let key in old) {
    newObj[key] = old[key];
  }
  return newObj;
}

第二次面试

这次是字节的正式岗位

1.开场白

除了上面的模板,这次还额外问我为啥这么快离职了。

2.实现一个泛型函数签名
function <T>fun(a:T, b:T) => T
3.一个输入框,随时输入都会触发后端请求,怎么降低对后端服务器的请求压力,并且保证都是最新值

这种情况需要使用防抖函数,设置一个时间间隔,在这个时间间隔内,每次触发函数,就重新计时,当没有操作,过了时间间隔,才去用最新值触发请求。

4. 实现一个防抖函数

const db = (fn, time) => {
    let timer = null
    return function () => {
        if (timer) clearTimeout(timer)
        timer = setTimeout(fn, time)
    }
}

当时面试没有写参数,正常可以写个带参数的。

const db = (fn, time) => {
    let timer = null
    return function (...arg) => {
        if (timer) clearTimeout(timer)
        timer = setTimeout(() => fn(...arg), time)
    }
}

5,react 中如何优化组件

优化组件有两种情况,使用api和不使用优化api

说出前置条件

  • 首先,React是实践纯函数思想:函数的输入没有变,那么输出一定不会变!

  • 另外,React的输入有三个来源:state, props, contextt

说出更新组件的特点

  • React 在判断 props 是否发生改变,采用===

  • 组件的props不声明时,默认是个空对象

  • React还有个特点,就是在更新组件时,如果父节点数据发生变化重新渲染了,那么其子孙组件的 props会被替换为新的空对象

说出原因:在js中{} === {}永远为false,所以导致react的组件的更新具有传染性

使用api

如何避免:我一般会用memo包裹父组件,这是react会使用浅比较props

如果父组件下第一层的子组件中有声明props,那么我会给相应的属性传值是,使用useMemouseCallback,这样只有当依赖性实际发生改变时,才会返回新的值,较少不必要的渲染次数。

不使用api

另外,还可以通过组件的层级关系优化组件,例如父组件下的子组件,有没有强烈的父子关系,那么可以考虑设计成兄弟组件。

另外还有种方法,如果父组件和子组件确实是父子关系,那么可以将父组件和子组件动态的部分,合在一个组件中,作为不变的部分的包裹组件。

const Parent = () => {
    const [count, setCount] = useState(0);
    
    return <>
        {count}
        <Child count={count} upDateCount={setCount} />
    <>
}

const Child = ({count, upDateCount}) => {
    return <>
        <div>{count}</div>
        <Button onClick={() => upDateCount((count) => count++)}>+</Button>
        <Other />
    </>
}

const Other = () => <>我是不变的部分</>

设计成新的组件

const Warp = ({children}) => {
const [count, setCount] = useState(0);
    
    return <>
        {count}
        <div>{count}</div>
        <Button onClick={() => upDateCount((count) => count++)}>+</Button>
        {children}
    <>
}

const App = () => {
    return <Warp>
        <Other />
    </Warp>
}

6. useMemo和useCallback实际开发中的作用

除了刚才说过的作为优化api,useMemo还有个作用,缓存复杂的计算过程的结果,如果每次计算十分笨重,可以考虑这么做

如果我们的方法里采用了很多变量,有的时候会导致拿到的值不是预期的,可以考虑使用useCallback,这样可以帮助React获取调用这个方法时对应的值。(因为每个变量的值,都会被保存在hook类型的Fiber节点上)

面试官不理解,我举了个例子,useState的第二个参数是个函数,参数有两种情况,一个值,或者一个方法,区别就是是个值是,react无法感知内部的变化,而使用函数时,就是告诉Reat,你去拿这个变量Fiber节点对应的值,进行据具体的计算,并返回值,这样可以感知内部的变化。

7. 全面的介绍下我对前端框架的理解:React和Vue

不管是React还是Vue,前端框架的主要思想是将数据的变化映射到页面的变化。不同的框架,映射数据的逻辑不一样:React主要通过收集变量,放在链表中,然后循环这个链表,将数据的变化更新到页面中。而Vue则是通过发布订阅模式,收集依赖,监听数据变化,发生变化时,广播这个事件,让每处依赖随之更新。

(这里我展开的不是很好,说的很笼统,再给我一次机会,我一定展开说说)

8. react 组件的生命周期

我当时有点懵,因为我从没使用过类组件,生命周期的名字忘记的差不多了,凭记忆说了个几个常用的:constructor、render、componentDidMount、componentDidUpdate,然后说其中三个带will的钩子取消了,新增了两个静态方法:getDerivedStateFromProps、getSnapshotBeforeUpdate,getSnapshotBeforeUpdate的方法会返回一个快照,作为componentDidUpdate方法的第三个参数传入。

上面的回答有点乱,而且缺少了shouldComponentUpdate,正确的顺序是:

  • constructor

  • getDerivedStateFromProps

  • render

  • componentDidMount

  • shouldComponentUpdate

  • getSnapshotBeforeUpdat

  • render

  • componentDidUpdate

9. 我看你第一面写了一个防抖,那么说下防抖和节流(降频)的区别

防抖时一定时间内不会执行,节流的话一段时间内只执行一次。

10. vue2和vue3响应式原理的区别,怎么使用他们(我的简历有vue项目)

vue2使用Object.definePro vue3使用es6的Proxy对象。

let obj = {}
Object.keys(obj).forEach(key => {
  Object.defineProperty(obj, key, {
      get () {},
      set () {},
  })  
})
// proxy突然忘记怎么写了,当时有点尴尬,但hi面试官说没事
new Proxy(obj, {get() {}, set() {}})

11. 你实际开发中打包工具主要使用啥?介绍下它的流程。和vite有啥区别吗?

主要使用webpack,webpack的流程,初始化配置参数,找到入口文件,解析文件之间的依赖关系,包括一些特殊符号的处理,例如@符号可能会对应src的路径,需要替换,然后将文件传入对应的loader,将loading的对应结果传给对应的插件处理,然后根据输出路径,输出文件。

webpack的插件不一定是在loader后执行,因为有的插件是在webpack的生命周期开始时就执行的,不处理任何文件,只是提供一些非文件处理的服务。

webpack和vite都是先解析配置文件,然后在生命周期内,根据配置文件处理源文件的,

不同的是,webpack是将所有文件打包之后将结果返回浏览器,但是vite开发环境下下,是只打包当前模块代码,提高打包的速度,另外vite还会缓存打包的结果在.vite文件下。下次启动服务时,优先使用缓存文件。

12. 实现一个深拷贝

上面实现过了

13. 下面的代码的输出结果是啥

setTimeout(() => console.log("code"));
Promise.then(() => console.log("code2"));
console.log("code3");

14. 知道loash框架吗?实现下面的效果

let obj = {a: [{b: {c: 3}}]}
_.get(obj, "a[0].b.c") // => 3
_.get(obj, "a.0.b.c") // => 3
_.get(obj, "a.b.c", "default") // => defalut

思考了一分钟,我就介绍了思路:转换参数格式:如果是a[0].b.c,转为a.0.b.c,这样方便计算 实现如下:

function get(obj, path, defaultValue = undefined) {
    const pathArray = path.replace(/\[(\d+)\]/g, '.$1').split('.');
    return pathArray.reduce((acc, key) => {
        if (acc && acc.hasOwnProperty(key)) {
            return acc[key];
        }
        return undefined;
    }, obj) ?? defaultValue;
}

// 示例使用
let obj = {a: [{b: {c: 3}}]};
console.log(get(obj, "a[0].b.c")); // => 3
console.log(get(obj, "a.0.b.c")); // => 3
console.log(get(obj, "a.b.c", "default")); // => "default"

15. 闭包的原理

js中有个执行上下文的概念,这个概念主要指一个变量可以访问的作用域范围,当我们在函数中返回一个内部变量时,变量占用的内存被释放了,但是这个变量的执行上下文被保留了下来,形成的闭包。

16. 实现一个高阶组件,有哪些使用场景?和工厂模式的区别

高阶组件主要使用场景是基于一个组件进行一些不侵入组件内部的行为操作。本质是函数里返回一个组件,也可以理解为装饰器模式。

const hoc = (props) => {
    const {a, b, c} = props;
    console.log(c);
    return ({a, b}) => {
        return <></>
    }
}

期间,还扯了会注解相关的内容。举了个例子,Mobx状态管理库,有个observer方法,支持注解的写法,使类的属性变为响应式的。

和工程模式的区别:工厂模式主要使用场景是基于某个类创建大量的实例,而装饰器模式可能不会创建实例,只是扩展一些行为。

总结

面试大厂前一定多面试几家小公司方便查漏补缺,第一次面试我连续面试好几家小公司,面试字节外包和学而思外包,一点压力都没有,拿下了5家offer,只有第一家面试挂了。

第二次面试时隔两个月,有点生疏了,好几个问题有点懵逼。

另外能搞一些有意义的开源项目,肯定是可以加分的。一定要在简历上写上。没有开源项目的话,面试时要尽量靠拢开发时的一些优秀的点上。

最后,基础一定要牢!加油吧!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值