前言
今天给大家分享一个字节跳动系公司——石墨文档的面经吧!废话不多说,先看题目!
题目
一面
- 1、['10', '10', '10', '10', '10'].map(parseInt) 的输出值是什么?
- 2、你们现在的技术栈是什么?为什么要使用ts?
- 3、setTimeout的执行过程(事件循环,同步、异步)?
- 4、对Promise的理解,与async、await的区别,async、await是怎么实现的?
- 5、解释 requestAnimationFrame/requestIdleCallback,分别有什么用?
- 6、react性能优化?
- 7、说说对flex的理解?
- 8、回流、重绘是什么?如何减少回流和重绘?
- 10、怎么寻找react页面卡顿的原因?
11、编程题:实现一个对象的 flatten 方法,如下:
const obj = { a: { b: 1, c: 2, d: { e: 5 } }, b: [1, 3, {a: 2, b: 3}], c: 3 }
flatten(obj){} 结果返回如下:
// { // 'a.b': 1, // 'a.c': 2, // 'a.d.e': 5, // 'b[0]': 1, // 'b[1]': 3, // 'b[2].a': 2, // 'b[2].b': 3 // c: 3 // }
二面
- 1、说说对web worker的理解
- 2、service worker和强缓存相比,有哪些优势?
- 3、说说对堆栈溢出的理解
- 4、position中的sticky是什么,还有哪些其他的?
- 5、ts中,any和unknown分别是什么意思?泛型怎么使用?
- 6、bind有什么用?连续多个bind,最后this指向是什么?
- 7、webpack的plugin怎么实现?
- 8、编程题:
现已知一个字符串是由正整数和加减乘除四个运算符(+ - /)组成。 例如存在字符串 const str = '11+2-34+5/24+10/5',现在需要将高优先级运算,用小括号包裹起来,例如结果为 '11+2-(34)+(5/2*4)+(10/5)'。注意可能会出现连续的乘除运算,需要包裹到一起。 请用 javascript 实现这一过程
三面
1、手写体:使用TypeScript 实现一个 get 函数来获取它的属性值
const data = { name: 'tom', age: 18, address: 'xxx' }
- 2、ts中的 any 、 unknown 的区别
- 3、有用过ts中的 keyof 吗?
- 4、for in/for of的区别?
- 5、Promise值穿透?
解答
一面
1、['10', '10', '10', '10', '10'].map(parseInt) 的输出值是什么?
可转化为:
['10', '10', '10', '10', '10'].map((num, index) => parseInt(num, index))
// [10, NaN, 2, 3, 4]
'10' 0 -> 10: 进制为0,则默认10进制
'10' 1 -> NaN: 1进制不存在
'10' 2 -> 2: 2 * 1 + 2 * 0
'10' 3 -> 3: 3 * 1 + 3 * 0
'10' 4 -> 4: 4 * 1 + 4 * 0
2、你们现在的技术栈是什么?为什么要使用ts?
typescript
是 JavaScript
的超集,它本质其实是是在 JavaScript
上添加了 可选静态类型
和 基于类的面向对象编程
typescript 的特点
- 可以在编译期间发现并纠正错误
- 提高可维护性
- 提高协同开发的效率
- 支持强类型、接口、泛型、模块
3、setTimeout的执行过程(事件循环,同步、异步)?
事件循环
- 1、执行同步代码
- 2、
1
中生成的微任务
先执行 - 3、
1
中生产的宏任务
再执行
同步
简单来说就是:排队。代码有前后顺序,必须按照顺序去执行
异步
异步任务可以不阻塞后面的代码执行,而是可以同时进行,并且执行完后会有一个异步的回调。
想起一个故事可以很好的解释 同步
和 异步
- 同步:你打电话去书店借书,老板接电话时让你等着,他去找书,你只能守着电话干等着
- 异步:你打电话去书店借书,老板接电话后说等他找到书再打回给你,然后挂电话了,这段找书的时间你可以自由活动
4、对Promise的理解,与async、await的区别,async、await是怎么实现的?
Promise的理解
顾名思义, Promise
就是 承诺
的意思,表现在了Promise的状态一旦改变则不会再变了,如果状态为 fulfilled
则执行 then
,如果状态为 rejected
则执行 catch
,Promise 也支持 链式调用
。我觉得Promise最大的用处就是 解决了回调地狱,提高了代码的可读性
。常用的方法有 resolve、reject、then、catch、race、all、allSettled、any、finally
async await async/await
的作用是 用同步的方式执行异步的操作
,它的实现原理,我个人理解就是利用了 Promise
的不断嵌套,再加上 generator函数
的步骤控制,实现了按顺序执行异步操作的效果
补充:async函数返回的是一个Promise
5、解释 requestAnimationFrame/requestIdleCallback,分别有什么用?
requestAnimationFrame:
- 一般间隔是
16ms
,因为大部分电脑都是 每秒60帧 ,所以1000 / 60 ≈ 16ms
- 会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中完成,且时间间隔紧紧跟随浏览器的刷新频率
- 如果有隐藏或不可见的元素,将不会进行重绘或回流,减少了cpu、gpu的内存使用量
- 如需取消则使用
cancelAnimationFrame
- 一般间隔是
- requestIdleCallback:我的理解就是找浏览器空闲时间去执行传入的回调,具体也没在项目中使用过
6、react性能优化?
7、说说对flex的理解?
弹性布局
,设置了 display: flex
的盒子为 弹性盒子
,子元素会自动变成 弹性项目
,盒子有一根主轴,默认是水平,并且有一个交叉轴(跟主轴垂直)。
弹性盒子的样式:
- flex-direction:定义主轴方向
- flex-wrap:是否允许换行
- flex-flow:flex-direction 和 flex-wrap的简写
- justify-content:主轴方向上的对齐方式
- align-items:交叉轴方向的对齐方式
- align-content:多根轴线的对齐方式
弹性项目的样式:
- order:定义项目的排列顺序,数值越小排列越靠前,默认0
- flex-grow:定义项目的放大比例,默认为
0
- flex-shrink:定义项目的缩小比例,默认为1
- flex-basis:定义了在分配多余空间之前,项目占据的主轴空间,默认auto
- flex:flex-grow、flex-shrink、flex-basis的简写
align-self:允许单个项目设置不同的交叉轴对齐方式
8、回流、重绘是什么?如何减少回流和重绘?
重绘回流
- 回流:尺寸、布局改变时,引起页面重新构建
- 重绘:元素外观、风格改变时,不影响布局,则为重绘
- 区别:回流一定引起重绘,重绘不一定引起回流
- 浏览器帮忙:浏览器维护一个队列,把所有引起回流、重绘的操作放入这个队列,等队列到了一定数量或者到了一定的时间间隔,浏览器就会清空队列,进行批量处理。
避免重绘、回流
- 1、批量修改DOM或者样式
- 2、复杂动画使用绝对定位让它脱离文档流,不然会印日分元素或后续元素的频繁回流
3、GPU加速:transform、opacity、filters、will-change等样式
9、判断一个对象是数组的方法?
- Object.prototype.toString.call(xxx)
- Array.isArray(xxx)
xxx instaceOf Array
10、怎么寻找react页面卡顿的原因?
11、编程题:实现一个对象的 flatten 方法,如下:
const obj = { a: { b: 1, c: 2, d: { e: 5 } }, b: [1, 3, {a: 2, b: 3}], c: 3 }
flatten(obj){} 结果返回如下:
// { // 'a.b': 1, // 'a.c': 2, // 'a.d.e': 5, // 'b[0]': 1, // 'b[1]': 3, // 'b[2].a': 2, // 'b[2].b': 3 // c: 3 // }
解题
const isObject = (target) => { return typeof target === 'object' && target !== null } const flatten = (obj) => { if (!isObject) return const res = {} const dfs = (cur, prefix) => { if (isObject(cur)) { if (Array.isArray(cur)) { cur.forEach((item, index) => dfs(item, `${prefix}[${index}]`)) } else { for(let key in cur) { dfs(cur[key], `${prefix}${prefix ? '.' : ''}${key}`) } } } else { res[prefix] = cur } } dfs(obj, '') return res }
二面
1、说说对web worker的理解
- 1、开启一个子线程,并在此子线程进行一些大数据处理或者耗时的操作
- 2、使用
postMessage
和onmessage
,实现主线程和子线程之间的通信 - 3、使用
onerror
监听子线程挂了没 - 4、
web worker
并没有改变JavaScript单线程的事实
2、service worker和强缓存相比,有哪些优势?
service缓存没用过。。
3、说说对堆栈溢出的理解?
常见的情况发生在 大数量递归
或 死循环
时,就会造成 栈溢出
,因为每次执行代码都需要分配一定空间的内存,以上两种情况都会使执行空间超出最大限度,从而报错
4、position中的sticky是什么,还有哪些其他的?
- static:默认
- relative:相对定位,相对于自身定位
- absolute:绝对定位,相对于非static的第一个祖宗元素定位
- fixed:相对于浏览器窗口进行定位
- inherit:规定应该从父元素继承 position 属性的值
- sticky:吸顶定位
5、ts中,any和unknown分别是什么意思?泛型怎么使用?
- any:变量如果是 any 类型,绕过所有类型检查,直接可使用
- unknown:变量如果是 unknow 类型,需要判断完是什么类型之后才能使用
6、bind有什么用?连续多个bind,最后this指向是什么?
bind
的作用是改变函数执行的指向,且不会立即执行,而是返回一个新的函数,可以自主调用这个函数的执行(此函数不可当做构造函数)
连续多个bind之后this指向始终指向第一个
7、webpack的plugin怎么实现?
一个plugin就是一个类,类里有一个 apply方法
,每次打包时都会调用这个apply,而这个apply方法接受一个参数对象,其中有一个 plugin
方法,此方法中有许多 钩子函数
,且可以决定静态文件的生成,修改等等
8、编程题:
现已知一个字符串是由正整数和加减乘除四个运算符(+ - /)组成。 例如存在字符串 const str = '11+2-34+5/24+10/5',现在需要将高优先级运算,用小括号包裹起来,例如结果为 '11+2-(34)+(5/2*4)+(10/5)'。注意可能会出现连续的乘除运算,需要包裹到一起。 请用 javascript 实现这一过程
解答
我比较菜,用的方法也是临时想出来的,没有优化,大家将就着看吧:
const checkType = (str) => {
if (['*', '/'].includes(str)) return 'high'
if (['+', '-'].includes(str)) return 'low'
return 'number'
}
const addBrackets = (formula) => {
const strs = formula.split('')
let i = 0, j = 1, high = false, res = []
while(j < strs.length) {
const jType = checkType(strs[j])
if (jType === 'low' && !high) {
i = ++j
j++
}else if (jType === 'low' && high) {
res.push(j++)
i = j++
high = false
}else if (jType === 'high') {
j++
!high && res.push(i)
high = true
}else {
j++
}
}
if (high) res.push(strs.length)
let add = 0
for(let i = 0; i < res.length; i++) {
const index = res[i]
strs.splice(index + add, 0, add % 2 ? ')' : '(')
add++
}
return strs.join('')
}
三面
1、手写体:使用TypeScript 实现一个 get 函数来获取它的属性值
const data = { name: 'tom', age: 18, address: 'xxx' }
解答:
const get = (obj: T, key: K): T[K] => {
return obj[key]
}
2、ts中的 any 、 unknown 的区别?
- any:变量如果是 any 类型,绕过所有类型检查,直接可使用
unknown:变量如果是 unknow 类型,需要判断完是什么类型之后才能使用
3、有用过ts中的 keyof 吗?
将一个interface的所有key,汇聚成一个联合类型,可以用来对传入key的限制,比如:
interface Target { name: string, age: number } const fn = (obj: Target, key: keyof Target) => {} const obj: Target = { name: 'sunshine', age: 18 } fn(obj, name) // 成功 fn(obj, age) // 成功 fn(obj, height) // 报错
4、for in/for of的区别?
- for in:遍历对象的key或者数组的索引
for of:遍历可迭代对象的值,如数组、Set
5、Promise值穿透
then或catch没有传入函数的话,会发生值穿透,原理是Promise内部检测如果传入的是非函数,则会拿上一次的结果包装成一个返回Promise的函数,达到穿透效果
例如:
Promise.resolve('foo')
.then(Promise.resolve('bar'))
.then(function(result){
console.log(result) // foo
})
但是如果传入的是函数的话:
Promise.resolve('foo')
.then(() => Promise.resolve('bar'))
.then(function(result){
console.log(result) // bar
})
结语
由于本人React太菜,所以不敢答题有关React的题目
我是林三心,一个热心的前端菜鸟程序员。如果你上进,喜欢前端,想学习前端,那咱们可以交朋友,一起摸鱼哈哈,摸鱼群,加我请备注【思否】