阅读 650

前端菜鸟的面试经历(持续更新,直到上岗)

记录一下我的前端面试过程吧

前言:楼主今年24岁,在上一家公司工作了接近两年,base成都。目标:10K+,面试题是我能记住且大多为没答上来的,面试完后作总结,答案为参考(欢迎指正)

1.第一场面试,一家做网络安全的公司(9-12k)

  • 时间:2021年6月15日上午
  • 结果:未通过
  • 面试问题:
  1. 讲一下页面的重绘(repaint)和回流(reflow)
答案整理:
1.浏览器的渲染机制:1.首先浏览器会将HTML解析为DOM,将CSS解析为CSSOM,然后把DOM和CSSOM结合生成render tree(渲染树)
                 2.有了渲染树后,知道了每一个节点的样式,然后浏览器就会计算节点的位置,把节点绘制到界面上。
                 
2.回流:当render tree的一些元素的位置或大小发生变化,导致浏览器重新渲染部分或全部文档的过程就叫做回流。
导致回流的操作:1.页面首次渲染2.浏览器窗口发生变化3.节点内容变化4.添加或删除节点。。。

3.重绘:当页面中元素样式的改变不会影响它在文档流中的位置时,浏览器会将新样式赋予给元素,这个过程就叫做重绘。
导致重绘的操作:1.background 2. visibility(可见性设置,除了display:none)---->简单记忆为:颜色、背景、边框、轮廓等改变会引起重绘

4.总结:重绘不一定引起回流,回流一定会引起重绘

5.性能方面:回流比重绘的影响大。

6.避免性能影响:
H5、CSS方面: 1.避免使用table布局  2.避免不必要的DOM层级 
             3.使用transform来做形变和位移,不会引起回流
Javascript方面:1. 避免使用js频繁操作DOM节点和CSS属性 2.对于大量DOM的操作,使用代码文档片段,documentFragment(这样就可以只插入一次了)如下:
HTML:<ul id="list"></ul>

const list = document.querySelector('#list');
const fruits = ['Apple', 'Orange', 'Banana', 'Melon'];
const fragment = document.createDocumentFragment();
fruits.forEach(fruit => {
  const li = document.createElement('li');
  li.innerHTML = fruit;
  fragment.appendChild(li);
});
list.appendChild(fragment);
复制代码
  1. CSS3动画了解多少
  2. 不考虑网络带宽的情况,如何使页面加载变快?(web性能优化)
    面试前,忽略了这一点,这题答的很差,面试官疯狂暗示也没答上来。
    答案整理:
        首先,有一个老生常谈的问题:从用户输入URL到页面显示出来经历了哪些过程?
    这一题换句话说就是在这些过程里面进行优化。那么,下面将进行分析:
    1.用户输入URL
    2.浏览器解析URL解析出主机名和端口号
    3.浏览器将主机名转化为ip地址(浏览器首先会查找自身的DNS缓存列表,如果没有的话,会向默认的DNS服务器发送查询请求,然后缓存下来)
    3.通过3次握手与服务器建立TCP连接
    4.浏览器向服务器发送HTTP请求报文
    5.服务器处理浏览器的发送的请求,并返回响应报文
    6.请求响应处理完成后,通过4次挥手断开TCP连接
    7.浏览器解析返回的文档,并将处理结果显示在页面上
    
    优化方案:
    
    1.减少HTTP请求的数量,方案:组合文件,比如合并js,css文件;优化图片,使用CSS sprite将多张ico 图片合并在一起。
    2.使用内容分发网络CDN,原理:也就是在不同的地理位置部署多台服务器,用户访问到服务器的距离就更近了。
    3.CSS放在顶部,JS放在底部。原理:JS加载和执行会阻塞渲染线程。假如放在head标签里,我们可以给script标签加上 defer属性.
    (
    PS:顺便回忆一下js资源异步加载的两种方案:defer 和 async
    defer:告诉浏览器立即下载js脚本(下载过程不阻塞渲染线程),下载完成后也要等到页面解析完毕后才执行。
    async:告诉浏览器立即下载js脚本(下载过程不阻塞渲染线程),等到下载完毕后立即执行,就会阻塞渲染线程了。--所以加了async的js脚本中不应该出现操作DOM的代码。
    )
    4.js文件动态加载,按需引入
    
    5.减少DNS的查找,方案:使用DNS预解析,如<link rel="dns-prefetch" href="www.baidu.com">,
    !慎用,用不好可能造成多次DNS查找
    6.从静态资源的角度:a.压缩文件js、css,图片(一般小于8kb图片,base64处理)
                       b.tree-shaking删除没用到的代码
    7.利用浏览器缓存
    a.使用外部js文件和css文件,浏览器会进行缓存,避免了多次请求
    b.通过HTTP的META设置expires和cache-control,只会缓存网页,不会缓存其中的资源和请求,如下:
    <meta http-equiv="Cache-Control" content="max-age=7200"/>  //7200秒
    <meta http-equiv="expires" content="Mon 01 Aug 2019 00:00:00 GMT"/>  //设置到期时间
    
    8.避免出现空src或者空href,会导致发送一次请求,可能返回404
复制代码
  1. 对网络安全了解多少(前端方向)
答案整理:我当时就只答了HTTP/HTTPS。。。应该还有很多,看到的朋友可以补充一下。
    这个问题似乎问的不多。从网上截两张图吧:
    简单理解:XSS:跨站脚本攻击---->通过特定方式,诱导受害者在客户端执行恶意代码。(盗用账户密码等信息)
             CSRF:跨站请求伪造---->盗用受害者信息,发送伪造请求。
复制代码

XSS.PNG CSRF.PNG 5. React Hooks用过吗?了解多少?然后问了UseEffect怎么卸载组件,怎么控制更新,第二个参数为空数组时会怎样?

  1. 暂时性死区是怎么回事?
    答案整理:
    在我们的js代码执行之前,浏览器会进行预解析。会做以下两件事:
    1.变量名提升:只提升声明,不提升赋值
    2.函数提升:整个函数提升
    
    我们通常说var有变量提升,letconst没有,但是实际上是有的。如下代码可以验证:
    let a =10;
    {
        console.log(a) //可以验证一下,如果没有声明提升,根据作用域链,这里应该输出10。但是却报错了
        let a =5;
    }
    总结:letconst的初始化过程分为:声明和赋值,在声明到赋值间的距离就叫做暂时性死区。
    
复制代码
  1. HTTP/HTTPS
  • 自我讨伐:由于是第一次面试,很多问题没答出来。。。略显尴尬,还得加油啊

2.第二场面试,一家金融基金软件的公司(6-10k)

  • 时间:2021年6月15日下午
  • 结果:通过,薪资7200-7500,口头告知平均每月有2000元左右的奖金(可信度存疑)。---最终决定不去,因为工资感觉不高,而且时常有2-3月的出差,公司小,且感觉学不到什么技术
  • 面试问题:
  1. 笔试:题目很简单,和面试官交谈,她和我说基本上能得90多分。
  2. 面试:谈了谈项目,都是问些基础,感觉不怎么深入。从面试官到总经理,一共面了三个人,过程除了谈技术还有画饼,很想留一下我的样子。
  • 自我讨伐:面试问的问题基本都会,都让我产生了错觉,“才面第二家就拿offer了???”,所以面试回来就躺了一晚上没复习。。。懒起来了,实在不应该。

3.第三场面试,一家初创公司,做智能硬件(规模50人不到,薪资10-18K)

  • 时间:2021年6月19日下午
  • 结果:未知,等待通知
  • 面试:只有面试,无笔试。两个面试官,一个对我上一份工作的内容很感兴趣,所以全程基本上在手机上看,应该是个技术总负责人。另一个应该是前端负责人,他负责问我问题。
  1. React路由的使用,如何在页面跳转的时候传参?
    传参方式主要有3种:
    1.Params参数
    路由链接(携带参数):<Link to={`/demo/test/${id}/${name}`}>详情</Link>
    注册路由(声明接收):<Route path="/demo/test/:id/:name" component={Test}/>
    接收参数:let {id,name}=this.props.match.params
    
    2.search传参
    路由链接(携带参数):<Link to={`/demo/test?id=${id}&name=${name}`}>详情</Link>
    注册路由 (正常注册即可):<Route path="/demo/test" component={Test}/>
    接收参数: let {search} = this.props.location.search
    注:接收到的参数形式为:?id=xxx&name=xxx--->可以引入queryString进行解析
    
    3.state参数
    路由链接:<Link to={{pathName='/demo/test',state:{id:xxx,name:xxx}}}>详情</Link>
    注册路由 (正常注册即可):<Route path="/demo/test" component={Test}/>
    接收参数:let {id,name} = this.props.location.state
    注:刷新也可以保留住参数
    
    其他:编码方式
    this.props.history.push({ pathname: "/about", state: { id:xxx } });
    
   PS:this.props.history.push()与this.props.history.replace()区别?
   
复制代码
  1. React 框架里面有过哪些优化经历?PureComponent?www.bilibili.com/video/BV1wy…
    我提到了两点:
    1.按需加载组件
    2.PureComponent
        使用普通component的2个问题:
        1.只要执行setState(),即使没有改变状态数据,组件也会重新render()
        2.只要当前组件render(),就会自动重新render子组件==》效率低
        效率高的做法:当组件的state或props数据发生变化时才render()
        原因:component中的shouldComponentUpdate()总是返回true
        解决办法:
        方法一:从写shouldComponentupdate()
        比较新旧state或props数据,如果有变化才返回true,如果没有就返回false
        方法二:使用PureComponent
        PureComponent重写了shouldCompoentUpdate(),只有state或props数据有变化才返回true
        注意:只是进行state和props数据的浅比较,如果只是数据对象内部数据变了,返回false。不要直接修改state数据,而是要产生新数据。
    3.Hooks中的useMemo/useCallBack???
复制代码
  1. 有用过Redux吗? 讲一下Reducer,讲一下纯函数
1.Reducer:

2.纯函数:一类特殊的函数,只要是同样的输入(实参),必定得到同样的输出(返回)。
需要准守下面这些约束:
    a.不得改写参数数据
    b.不会产生任何副作用,例如不能发送网络请求,不能调用输入输出设备等。
    c.不能调用new Date(),Math.random()等不纯的方法。
复制代码
  1. 对js原生熟悉吗?讲一下Promise?除了then和catch,还有什么办法能获取Promise的返回值?
  2. 懒加载?你在项目里面是怎么用的
    我在项目里面使用lazy加载主要是用来按需加载组件。
    步骤:
    1.import {lazy} from react
    2.const Test = lazy(()=>{import('./Test')})
    3.import {Suspense} from react
    4.用Suspense把我们注册的路由包裹起来,如下
    <Suspense fallback={<h1>加载过程中的过度组件,我们可以自定义</h1>}>  //fallback指定的组件不能通过懒加载引入,它必须是正常引入进来的。
        <Route to='/Test' component={Test} />
    </Suspense>
复制代码
  1. Hook了解吗?你了解哪些?讲一下怎么在函数组件中实现生命周期钩子?在useEffect里面怎么卸载组件?
  2. 怎么在cra(create-react-app)中实现,css代码的模块化? moudle???
  3. 你在项目中客户验证是通过什么?使用什么发网络请求?axios拦截器是怎么回事?
  4. 用什么UI框架?了解antd吗?
  5. 了解VUE吗?
  6. 了解RN吗?
  7. 你是如何在cra中配置webpack的?
  8. 块级作用域?讲一下?
    ES5中有两种作用域,全局作用域和函数作用域。ES6新增了块级作用域。
    块级作用域:
    1.在ES6中,用{}来界定一个块级作用域。
    2.使用letconst声明的变量有块的概念,var声明的变量则没有块级作域的概念。
    3.在块的内部声明letconst变量,不能够跨块访问
复制代码
  1. setState后立马想拿到state,如何处理?
    //使用setState({状态对象},callback)的第二个参数,传入一个函数,在函数里面可以拿到最新的state。如下
    setState({count:1},()=>{
    console.log(this.state.count)})
复制代码
  • 自我讨伐:可以看出,小公司更注重来个人立马就能用,问了很多很多React方面的问题。有些对于React的基础知识我由于记忆的不太深刻,导致没有答上来。接下来应该把这部分再复习一下。

4.第四场面试,一家医疗科技相关的公司(8-15k)

  • 时间:2021年6月21日下午
  • 结果:未知,等待通知
  • 面试:只有面试,无笔试。面试官拿着我的简历看了一下,一直想问我VUE的问题,但是我对VUE一无所知。我心里想,我简历上一个VUE的字眼都没有出现,既然招VUE你干嘛叫我来...聊了一会儿后,面试官说他们也有React相关的需求。但是他好像对React一点也不熟悉...所以全程就问了我一些js,css基础
  1. H5里面有哪些新属性? www.cnblogs.com/vicky1018/p…
  2. ES6有哪些新属性?
  3. 你怎么理解虚拟DOM?
  4. 怎么理解MVVM框架?与MVC有什么区别? www.jianshu.com/p/0ae3c0d83…
    1.MVVM(Model–View–Viewmodel)是一种软件架构模式。
    2.简单的讲,MVVM是MVC的改进版。
          先来看看MVC:
          在MVC中M就是单纯的从网络获取回来的数据模型,V指的我们的视图界面,而C就是我们的ViewController。
    ViewController负责View和Model之间调度。随着业务越来越复杂,视图交互越复杂,导致Controller越来越臃肿。
          再来看MVVM:
         所以为了解决这个问题,MVVM就闪亮登场了。他把View和Contrller都放在了View层
     (相当于把Controller一部分逻辑抽离了出来),
     Model层依然是服务端返回的数据模型。而ViewModel充当了一个UI适配器的角色,
     也就是说View中每个UI元素都应该在ViewModel找到与之对应的属性。除此之外,
     从Controller抽离出来的与UI有关的逻辑都放在了ViewModel中,这样就减轻了Controller的负担。
     下面是MVVM示意图:
复制代码

image.png

View层:视图展示。包含UIView以及UIViewController,View层是可以持有ViewModel的。
ViewModel层:视图适配器。暴露属性与View元素显示内容或者元素状态一一对应。一般情况下ViewModel暴露的属性建议是readOnly的,至于为什么,我们在实战中会去解释。还有一点,ViewModel层是可以持有Model的。
Model层:数据模型与持久化抽象模型。数据模型很好理解,就是从服务器拉回来的JSON数据。而持久化抽象模
型暂时放在Model层,是因为MVVM诞生之初就没有对这块进行很细致的描述。按照经验,我们通常把数据库、文
件操作封装成Model,并对外提供操作接口。(有些公司把数据存取操作单拎出来一层,称之为DataAdapter
层,所以在业内会有很多MVVM的变种,但其本质上都是MVVM)。
Binder:MVVM的灵魂。可惜在MVVM这几个英文单词中并没有它的一席之地,它的最主要作用是在View和
ViewModel之间做了双向数据绑定。如果MVVM没有Binder,那么它与MVC的差异不是很大。
复制代码
  1. 输入一个URL到页面显示,会经历哪些过程?
  2. js里面怎么实现继承?
    1.在ES6中:class继承的方式,使用extends关键字。在构造函数中使用super()调用父类构造函数。
    2.ES5中:
    a.构造函数继承
        在CHILD方法中调用 PARENT.call(this),相当于给CHILD的实例设置了很多私有的属性或者方法。
        特点:只能继承父类的私有属性或者方法,和其原型链没有关系。
    b.原型链继承
        让父类中的属性和方法在子类实例的原型上。
        CHILD.prototype = new PARENT();
        CHILD.prototype.constructor = CHILD;
        特点:
        1.继承靠的是把父类的原型放在子类实例的原型链上,想调取这些方法,是基于__proto__原型链查找机制完成的。
        2.!!!子类可以重写父类上的方法(会导致父类其他实例也受影响)
    c.寄生组合继承:CALL继承 + 类似于原型链继承
       1.CHILD中 PARENT.call(this);
       2.CHILD.prototype = Object.create(Parent.prototype);//创建一个空对象,将其的__proto__指向父类的原型
       3.PARENT.prototype.constructor = CHILD;
       特点:父类私有和公有的分别是子类实例的私有和公有属性方法(推荐)
复制代码
  1. px,rem,em,vw/vh,百分比异同?
    我平时没有怎么用vw/vh,这里写一下吧
    vw、vh都是视口单位(Viewport units),在桌面端指的是浏览器的可视区域。移动端指的就是Viewport中的Layout Viewport(布局视口)
    1.vw:1vw等于视口宽度的1%
    2.vh:1vh等于视口高度的1%
    3.vmin:等于vw和vh中较小的那个
    4.vmax:等于vw和vh中较大的那个
    
    rem/em
    这两个都是相对长度单位。
    rem:相对于根布局, 由html 元素的字体大小决定的
    em:相对于自身元素的font-size,由于继承因素,font-size可以继承父元素的
复制代码
  1. 用div模拟textArea?

    方案:给div加上属性contenteditable = true;

5.第五场面试,一家教育工具类APP的公司(8-12k)

  • 时间:2021年6月24日下午
  • 结果:未知,等待通知
  • 面试:

1.null和undefined区别

    阮一峰:
    null表示"没有对象",即该处不应该有值。
    undefined表示"缺少值",就是此处应该有一个值,但是还没有定义。
复制代码

2.简述new关键字的过程

blog.csdn.net/Kreme/artic…

    1.创建一个空对象object,let obj = new Object()---创建对象新对象,就是指在栈内新建了一个
    obj,这个obj实际上是指的堆中对应的一个地址。
    2.设置原型链---这里所说原型链,就是设置新建对象obj的隐式原型即_proto_属性指向构造函数Person
    的显示原型prototype对象,即obj.proto = Person.prototype
    3.改变构造函数Person的this绑定到新对象obj,并且利用call()或者是apply()来执行构造函数Person
    let result = Person.call(obj)
    4.将第三步中初始化完成后的对象地址,保存到新对象中,同时要判断构造函数Person的返回值类型,为
    什么要判断值类型呢?因为如果构造函数中返回this或者是基本数据类型(number数值,string字符串,
    Boolean布尔,nullundefined)的值时,这个时候则返回新的实例对象,如果构造函数返回的值是引
    用类型的,则返回的值是引用类型

复制代码

6.第六场面试,一家电商公司(12-18k)

  • 时间:2021年6月25日下午
  • 结果:通过一面,等待二面
  • 面试:

一面

1.闭包概念?怎么形成一个闭包?闭包造成的内存泄漏怎么处理?

    概念:一个函数和它周围状态的引用捆绑在一起的组合。它允许一个函数可以从内部访问到它外部函数作用域。
    
    形成:
    闭包形成的3个条件:
    a.函数嵌套
    b.内部函数引用外部函数的局部变量
    c.在两个函数外部,有变量引用内部函数
    
    优点:可以延长外部函数局部变量的存储时间
    缺点:容易造成内存的泄漏
    
    处理内存泄漏:
    在合适的地方,把我们闭包返回的函数给设置为 null。那么自然内存就被释放了。
    
    PS:哪些行为可能造成内存泄漏?
        a.意外的全局变量,比如在函数中定义变量时忘记加var。那么久相当于在window上新加了一个变量
        b.setTimeout,setInterval忘记clear
        c.闭包中的变量一直被引用
复制代码

2.了解哪些设计模式?单例模式?工厂模式?说一下

3.如何判断两个对象完全相同?

4.说一下深拷贝? 5.说一下,react中会出现无需的render过程,你是怎么优化的。

6.说一下react生命周期?

7.说一下,怎么加快页面加载速度?(性能优化,前面有说过)

二面

时间:2021/6/29 下午

结果:通过

部门经理面试,问了下简历上的项目,30分钟左右,结束后直接叫HR面。然后回去等通知,大概下午5点告知将发offer。

7.第七场面试,一家做中间件的公司(附带自家开发商城?没大听明白)(10-15k)

  • 时间:2021年6月28日上午
  • 结果:应该是通过一面了,口头告知会打电话后续交流一下
  • 笔试:垂直居中,盒子模型,程序执行题,两个算法题
  • 面试: 1.组件间传值? 有用过context吗? 2.除了split(),你还有其他方法能拆分字符串吗? 3.箭头函数与普通函数有哪些异同?
    
复制代码

4.ES6中map,set?

复制代码

其他问题记不住了,没有问的很深入。面试官认识我上个公司的一个同事,聊了两句,就没有多问了。然后就叫他的leader过来,照着简历问了下项目。然后,就告知,他要去开会,等他有空会给我打电话进行后续的交流。

  • 自我感受:面试官告知,成都这边是去年刚新成立的分部,总部在北京,然后公司规模不大,技术部门应该就几十人。成都目前10人。等着看看后续结果

8.第八场面试,一家给中石化/中石油做软件的公司(7-12K)

  • 时间:2021年6月30日上午

  • 结果:通过,隔天打电话给我,薪资9K,公积金比较低,双边加上一共400.

  • 笔试:CSS两个题。一个算法题,还有一个程序解读题。

  • 面试:一个小姐姐面试官,大概比我大3岁的样子,感觉上也不是技术大牛。

    1.事件冒泡?了解吗?讲一下

    2.了解哪些DOM事件?

    3.问了ComponentDidMount.(她说的时候还说错了单词,我给她纠正了一下。)

    4.问了一下,我简历上的项目问题。

-自我感受:由于是传统行业,我并不想去,且公司给我一种老板油腻中年人的感觉,一看就是老油条。

9.第九场面试,一家做在线教育的公司(10-18k)

  • 时间:2021年6月30日下午
  • 结果:未通过
  • 面试:两轮面试,总体难度,个人感觉是这些面试里面最难的。
    第一个面试官

1.把他的Mac给我写代码(由于我之前没用过mac,导致不知道怎么切换大小写,尴尬。。。)

    第一个算法题:实现数组扁平化
    //我实现了两种:1.flat() 2.toString()
    
    第二个算法题:实现数组去重
    //我实现了两种:1.利用set 2.存入一个对象中,如有重复的就不存
    //面试官让我用递归再实现一次:没写完整。
复制代码

2.进程有几种状态?(操作系统)

3.Webpack的HRM是怎么实现的?

    没答上来。他告诉我是:首先监听我们的代码文件,若有改变,重新编译代码,然后利用WebSocket主动
    将编译好的内容推送给我们浏览器。。。(大概是这样)
复制代码

4.什么是Hash表?

第二个面试官

1.instanceOf 和 typeof 有什么区别?(很深入)

    这个问题不是我们常规的回答。
    还问了他们之间有哪些耦合的地方,就很深入。值得下来深入了解
复制代码

2.假设我们在输入框中输入一个内容,请求已被发出,但它还接收到服务器的响应(假设10s,结果返回)。我们在这个时间里,立马再发出第二次的请求,它的结果立马就返回了,并显示在我们的内容里面。但是10后,第一次发出的请求,结果返回了,然后覆盖了第二次请求的结果。请提出方案来解决这个问题?

    听到这个问题----懵逼树上懵逼果,懵逼树下你和我...
    然后我想了一下20秒:
    可以用防抖,在第一次请求发出后,设置一个setTimeOut(),如果在时间段内,在发请求,就cancel()掉。
    然后面试官说,可以,但这个方案有问题,无法正确设置间隔时间。然后疯狂暗示我继续想,但我也想不起来了。
    
    他看我想不起来就:说可以给每一个请求设置一个编号,比如1,2,3...然后每次发请求就带给服务器。
    服务器返回的时候回传给客户端。然后进行比较,如果1<2,就不要1.  然后他说这种方案还有问题,让我再想。。。我继续懵逼,然后他说了第三种方案,我没心思听了,也就没记住
复制代码

3.这次,给了我一个thinkpad,让我手写的深拷贝?

    由于我之前看过,不到3分钟就给写出来了。
复制代码

面试结束:他去找第一个面试官合计,估计就不要我了,233...

-自我讨伐:没有解决实际问题的方案,平时缺乏的很

10.第十场面试,一家做医疗药品网站后台的公司(8-15K)

  • 时间:2021年6月31上午
  • 结果:通过
  • 面试:

1.PureComponent? 什么是浅比较?

2.webpack打包流程是怎么样的?

3.闭包?闭包可以解决那些问题?

-自我感受:第二天告诉我面试通过,11K底薪加上季度项目奖。我拒绝了,因为公司开发人员只有10来个,也是一个传统行业。

11.第十一场面试,一家做家装APP的公司。(15-25k)

  • 时间:2021年7月1日下午
  • 结果:通过一面(很多问题没答上,面试官也给了我二面机会???)
  • 电话面试:真的讨厌电话面试,听不清楚问题,有几个问题,我重复让面试官说了两次,我都不好意思了(像耳朵有问题一样)。

1.归并算法?

2.快速排序?

-自我感受:难度较大,我以为过不了一面,隔天打电话过来告诉我一面过了,准备现场二面。但是我不打算去了,已经接offer。

结果:

    拿到了第6场面试的offer,具体薪资就不说了,上面有个范围,也达到了我的期望。76日上班
复制代码

面试总结与感受

    最近太累了,后面补上。2021/7/323:35
复制代码
文章分类
前端
文章标签