19
头图

前言

今天给大家分享一个字节跳动系公司——石墨文档的面经吧!废话不多说,先看题目!

题目

一面

  • 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、回流、重绘是什么?如何减少回流和重绘?
  • 9、怎么寻找react页面卡顿的原因?
  • 10、编程题:实现一个对象的 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 = <T extends object, K extends keyof T>(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的题目

我是林三心,一个热心的前端菜鸟程序员。如果你上进,喜欢前端,想学习前端,那咱们可以交朋友,一起摸鱼哈哈,摸鱼群,加我请备注【思否】

image.png


Sunshine_Lin
2.1k 声望7.1k 粉丝