字节跳动前端实习完整四面题解

本人最近投了字节的前端实习,面试是三轮技术面+一轮hr面,已成功拿到offer,分享以下面试过程中的问题和解答。

一面(50 min)

面试我的是一个二十出头的小哥哥,第一次面试我还是比较紧张的,还好面试官待人和蔼可亲,针对不会的问题并没有刁难我,反而给我不少提示(给点个赞)

首先是自我介绍,问了下我学习前端的途径和方法啥的,算是暖了个开场吧,然后是具体的问题。

1. ES6新特性

我首先说的const和let,学长就直接让我细说以下let和const

  1. let、const声明的变量只在其所在的块代码内有效,隐式截取了作用域。
  2. 暂时性死区,变量声明提升但在不会被赋值为undefined,不能在声明之前使用。
  3. 不允许重复声明。

然后给了两个例子让说一下:

<code>

const

a =

1

const

a =

2

console.

log

(a)/<code>

这个会直接报错SyntaxError: Identifier 'a' has already been declared,ES6中的let和const是不允许变量重复声明的。

<code>

const

b = [] b.push(

1

) console.

log

(b)/<code>

输出结果是[1],这个是关于基本类型和引用类型的区别,b指向的只是这个数组的内存地址。

2. ES6之前的模块引入方式和区别

ES6之前模块引入主要是CommonJSAMD两种。

然后学长让说明一下CommonJs和ES6模块引入的区别:

  1. 首先,CommonJS导出值是浅拷贝,一旦输出某个值,模块内部的变化就影响不到这个值。而ES6导出是采用实时绑定的方式,是将其内存地址导出,导入是动态地加载模块取值,并且变量总是绑定其所在的模块,不能重新赋值。
  2. ES6模块化导入是异步导入,CommonJS导入是同步导入。这跟ES6模块通常用于web端,而CommonJS用于服务器端有关。
  3. CommonJS导入支持动态导入require(`${path}/xx.js`),ES6模块化导入不支持,目前已有草案。
  4. ES6模块化会编译成require/exports来执行的。

3. 一道数组遍历

实现[1,2,3] => [2,4,6],这个太简单了:

<code>

function

fn

(

arr

)

{

return

arr.map(

item

=>

item *

2

) }/<code>

4. 简单算法题

对输入的字符串,去除其中的字符'b'以及连续出现的'a'和'c',例如:

<code>  

'aacbd

' ->

'ad

'

'aabcd

' ->

'ad

'

'aaabbccc

' -> ''/<code>

这个连续的ac需要注意以下,最开始想的是使用栈也是可以的比较麻烦,学长提醒了下正则,直接使用正则:

<code>

function

fn

(

str

)

{

let

res = str.replace(

/b+/g

,

''

);

while

(res.match(

/(ac)+/

)) { res = res.replace(

/ac/

,

''

) }

return

res; }/<code>

5. CSS9宫格

问题:创建出CSS9宫格,3*3宫格,每个宫格的长宽未知,要求达到自适应并且精确分配

这个精确分配,就注定使用width: 1/3是达不到要求的,需要使用flex布局:

<code> 

<

div

class

=

"container"

>

<

div

class

=

"wrapper"

>

<

div

class

=

"item"

>

div

>

<

div

class

=

"item"

>

div

>

<

div

class

=

"item"

>

div

>

div

>

<

div

class

=

"wrapper"

>

<

div

class

=

"item"

>

div

>

<

div

class

=

"item"

>

div

>

<

div

class

=

"item"

>

div

>

div

>

<

div

class

=

"wrapper"

>

<

div

class

=

"item"

>

div

>

<

div

class

=

"item"

>

div

>

<

div

class

=

"item"

>

div

>

div

>

div

>

.container { display: flex; flex-direction: column; flex-wrap: nowrap; } .wrapper { display: flex; flex: 1; flex-direction: row; flex-wrap: nowrap; } .item { flex: 1; }/<code>

6. 简单说下React Virtual Dom (1)diff算法 (2)映射到真实DOM\

React虚拟DOM和diff算法,几乎问到React都会问这两个吧。。。

React将DOM抽象为虚拟DOM(VDOM), 然后通过新旧虚拟DOM 这两个对象的差异(Diff算法),最终只把变化的部分重新渲染,提高渲染效率。

VDOM

使用JS模拟DOM树的结构,DOM树的变化通过JS进行对比。

当我们需要创建或更新元素时,React首先会让这个VitrualDom对象进行创建和更改,然后再将VitrualDom对象渲染成真实DOM;

虚拟DOM的优势:

  1. 函数式编程。UI取决于数据。
  2. 打开了跨平台的大门。提供了统一的JS层对象,根据不同平台制定不同的渲染方式,带来了跨平台渲染的能力。

Diff算法

创建一个React元素树之后,在更新的时候将创建一个新的React元素树,React使用Diff算法对元素树进行比对,只更新发生了改变的部分,避免多余的性能消耗。

主要是两个要点:

  1. 两个不同类型的元素会产生不同的树,直接替换。
  2. 对于同一层级的一组子节点,它们可以通过唯一 key 进行区分。
  3. 逐层递归比较。

7. NodeJS中的任务队列

说下输出顺序:

<code>process.nextTick(

function

(

)

{

console

.log(

'a'

) }) process.nextTick(

function

(

)

{

console

.log(

'b'

) }) setImmediate(

function

(

)

{

console

.log(

'c'

) process.nextTick(

function

(

)

{

console

.log(

'd'

) }) }) setImmediate(

function

(

)

{

console

.log(

'e'

) })

console

.log(

'f'

)/<code>

输出:f -> a -> b -> c -> d -> e 这个值得注意的是setImmediate只能占用一次,这块setImmediate当时就回答错了。

8. ES6字符串

这道题的清醒没有遇到过,也不会,现在也忘了这道题。。。

9. Promise实现网络超时判断

使用Promise实现网络请求超时判断,超过三秒视为超时。 借助的是Promise.race这个方法:

<code>

const

uploadFile =

(

url, params

) =>

{

return

Promise

.race([ uploadFilePromise(url, params), uploadFileTimeout(

3000

) ]) }

function

uploadFilePromise

(

url, params

)

{

return

new

Promise

(

(

resolve, reject

) =>

{ axios.post(url, params, {

headers

: {

'Content-Type'

:

'multipart/form-data'

},

withCredentials

:

true

}).then(

res

=>

{

if

(res.status===

200

&& res.data.code===

0

) { resolve(res.data.result) }

else

{ reject(res.data) } }) }) }

function

uploadFileTimeout

(

time

)

{

return

new

Promise

(

(

resolve, reject

) =>

{ setTimeout(

()

=>

{ reject({

timeoutMsg

:

'上传超时'

}) }, time) }) }/<code>

二面(50min)

一面过后让我稍等十分钟,二面就开始了。

1. 进程和线程

简要说下操作系统的进程和线程

  1. 线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位。
  2. 一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线。
  3. 进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间。
  4. 调度和切换:线程上下文切换比进程上下文切换要快得多。

2. 简要说下操作系统中的锁(死锁)

通过锁机制,能够保证在多核多线程环境中,在某一个时间点上,只能有一个进程进入临界区代码,从而保证临界区中操作数据的一致性。

死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,两羊过独木桥。

死锁的解除与预防,资源的合理分配。。。

这一块答的不是很好。

3. 说一下链表的类型

单向链表、双向链表、循环链表

4. 栈和队列的区别?

栈是后进先出的数据结构,常用的比如调用栈。

队列是先进先出的数据结构,常用的就是NodeJS和浏览器中的任务队列了。

5. 算法题

遍历一个二叉树所有节点,返回它们的和

<code>

function

numSum

(

root

)

{

if

(!root)

return

0

;

return

root.val + numSum(root.left) + numSum(root.right); }/<code>

6. 算法题

给出一个数组,求出连续元素组成子串和的最大值。

<code>

输入: [-2,1,-3,4,-1,2,1,-5,4],

输出: 6

/<code>

双层循环的话时间复杂度是: O(n * n), 使用分治的话时间复杂度可以压缩到O(n * logn),问了下学长说直接用笨方法写一下,给出代码:

<code>

function

numSum

(

arr

)

{

if

(!arr.length)

return

null

;

if

(arr.length

2

)

return

arr[

0

];

let

sumMax = arr[

0

];

let

sumTemp;

for

(

let

i =

0

; i < arr.length; i++) { sumTemp = arr[i];

if

(sumTemp > sumMax) sumMax = sumTemp;

for

(

let

j = i +

1

; j < arr.length; j++) { sumTemp += arr[j];

if

(sumTemp > sumMax) sumMax = sumTemp; } }

return

sumMax; }/<code>

7. 介绍下你的项目,微信小程序的原理简要说一下?

略。。。

三面(1h)

三面的难度就比之前的高了不少

1. 说下ES6新特性

列举下ES6新特性并简要说明一波

  • let、const
  • 解构赋值
  • 字符串、正则、数值、函数、数组、对象的扩展
  • symbol
  • Set、Map
  • Promise
  • Generator
  • async
  • class
  • 修饰器
  • module

2. Generator实现一个自执行

就是实现以下著名的co模块:

<code>

function

run

(

gen

)

{

return

new

Promise

(

function

(

resolve, reject

)

{

if

(

typeof

gen ==

'function'

) gen = gen();

if

(!gen ||

typeof

gen.next !==

'function'

)

return

resolve(gen) onFulfilled();

function

onFulfilled

(

res

)

{

var

ret;

try

{ ret = gen.next(res); }

catch

(e) {

return

reject(e); } next(ret); }

function

onRejected

(

err

)

{

var

ret;

try

{ ret = gen.throw(err); }

catch

(e) {

return

reject(e); } next(ret); }

function

next

(

ret

)

{

if

(ret.done)

return

resolve(ret.value);

var

value = toPromise(ret.value);

if

(value && isPromise(value))

return

value.then(onFulfilled, onRejected);

return

onRejected(

new

TypeError

(

'You may only yield a function, promise '

+

'but the following object was passed: "'

+

String

(ret.value) +

'"'

)); } }) }

function

isPromise

(

obj

)

{

return

'function'

==

typeof

obj.then; }

function

toPromise

(

obj

)

{

if

(isPromise(obj))

return

obj;

if

(

'function'

==

typeof

obj)

return

thunkToPromise(obj);

return

obj; }

function

thunkToPromise

(

fn

)

{

return

new

Promise

(

function

(

resolve, reject

)

{ fn(

function

(

err, res

)

{

if

(err)

return

reject(err); resolve(res); }); }); }

module

.exports = run;/<code>

3. 说下微信小程序的生命周期

这个我没温习到,没有回答完整哈~ 页面生命周期:

  • onLoad // 页面创建时执行
  • onShow // 页面出现在前台执行
  • onReady // 页面首次渲染完毕时执行
  • onHide // 页面从前台变为后台时执行
  • onUnload // 页面销毁时执行
  • onPullDownRefresh // 触发下拉刷新时执行
  • onReachBottom // 页面触底时执行
  • onPageScroll // 页面滚动时执行
  • onResize // 页面尺寸变化时执行
  • onTabItemTap // tab点击时执行

组件声明周期(在lifetimes字段内进行声明):

  • created // 在组件实例刚刚被创建时执行
  • attached // 在组件实例进入页面节点树时执行
  • ready // 在组件在视图层布局完成后执行
  • moved // 在组件实例被移动到节点树的另一个位置时执行
  • detached // 在组件实例被从页面节点移除时执行
  • error // 每当组件方法抛出错误时执行

4. 说下你会的React技术栈

Redux、React-Router、Antd。。。

5. React-Router实现格简单路由

<code>import { BroserRouter, Route, Switch, Redirect } from 'react-router-dom'

<

BroserRouter

>

<

Switch

>

<

Route

path

=

'/login'

exact

component

=

{Login}

/>

<

Route

path

=

'/'

render

=

{()

=>

(

<

Switch

>

<

Route

paht

=

'/home'

component

=

{Home}

/>

<

Redirect

to

=

'/home'

/>

Switch

>

)} />

Switch

>

BroserRouter

>

/<code>

6. 说下Redux原理

Redux就是一个数据状态管理,简要说下Redux数据流,store、action、reducer,搭配React的使用。

可以有空看下源码,内容不是很多。

7. 说下你的项目

略。。。

8. 说下Koa洋葱模型

Koa的洋葱模型:一个请求从外到里一层一层地经过中间件,响应时从里到外一层一层地经过中间件。

观看Koa源码,你会发现:实现洋葱模型依赖于koa-compose,分析一波koa-compose的源码:

<code> 

module

.exports = compose

function

compose

(

middleware

)

{

if

(!

Array

.isArray(middleware))

throw

new

TypeError

(

'Middleware stack must be an array!'

);

for

(

const

fn

of

middleware) {

if

(

typeof

fn !==

'function'

)

throw

new

TypeError

(

'Middleware must be composed of functions!'

); }

return

function

(

context, next

)

{

let

index =

-1

;

return

dispatch(

0

);

function

dispatch

(

i

)

{

if

(i <= index)

return

Promise

.reject(

new

Error

(

'next() called multiple times'

)); index = i;

let

fn = middleware[i]

if

(i === middleware.length) fn = next

if

(!fn)

return

Promise

.resolve();

try

{

return

Promise

.resolve(fn(context, dispatch.bind(

null

, i +

1

))); }

catch

(err) {

return

Promise

.reject(err) } } } }/<code>

9. 实现一个Koa中间件

中间件通常为以下形式:

<code>

async

(ctx, next) => {...} 复制代码/<code>

一般来说中间件有自己的配置,所以我们总结出来一种通用的中间件写法,我们通过传入配置的方式可以返回根据配置定制的中间件:

<code>

module

.exports =

function

(

options

)

{

return

async

(ctx, next) => { } }/<code>

使用方式:

<code>

app

.use

(middleware(options)) /<code>

上一个面试题提到的koa-compose,可以将多个中间件合并,也可以将有关联的中间件合成一个大的中间件:

<code>

app

.use

(

compose

(

[middleware1, middleware2, ...]

))/<code>

OK~!现在我们自定义一个中间件! 问题背景:网站经常被某些ip攻击,影响业务正常运行,于是做了一个中间件进行ip的过滤,对于ip黑名单上的ip一律拒绝进一步处理请求。

<code> 

module

.exports =

function

(

ip_blacklist

)

{

return

async

(ctx, next) => {

if

(

Array

.isArray(ip_blacklist) && ip_blacklist.length) {

let

ip = ctx.request.headers[

'x-real-ip'

] ||

''

if

(ip && ip_blacklist.indexOf(ip) !==

-1

) {

await

next() }

else

{

return

res.end(

'ip restricted'

) } }

else

{

await

next() } } }/<code>

10. 参与比赛的项目中,你自我感觉发挥作用比较重要的一个项目?

略。。。

四面(30min)

四面是hr面,聊了一下选择前端的原因和对于前端发展的看法,平时的学习途径和学习方法,大学校园生活、参加的一些比赛和活动,对于考研和就业的一些看法。随后也是顺利拿到了字节的实习offer。

反思

字节的面试官还是比较善解人意的,对于没有思路的问题会引导着你去思考而不是以难倒人为乐趣,算法题嘛整体也是比较简单的,多关注于实际实现和知识基础。

面试的节奏也是比较快的,三轮技术面基本都是连着面中间隔个15min。也可以看到字节对于基础知识是比较看重的,知识基础还是需要多看,还有一些框架的源码和原理看了的话也是加分很多的。

三面的效果并不是很理想,但最后还是成功拿到了实习offer,还是比较幸运的。也祝大家都能拿到理想的offer啦~

字节跳动前端实习完整四面题解

参考文献:

实现Generator自执行


学习Koa - 让我们写一个中间件


分享到:


相關文章: