谈面试前端工程师


谈面试前端工程师

  • 打战需要周全的准备,而找一份年薪几十万的工作对于个人来说不亚于一场大战
  • 有一天,你若需要招人,则需要在极短的时间内从各方面考核对方是否合适
  • 通过面试可以推动自己主动了解行业新技术,尤其是长期呆在一家技术不那么好的公司
  • 面试的内容数不胜数(亦或千奇百怪),面试官和面试者的背景也不尽相同,或许我们只能尽力而为。将每次面试当成一次修炼,好好享受

以下是近期朋友去面试前端工程师所遇到的一些问题,整理如下:

vue

v-model 修饰符有哪些

答:.number、.trim、.lazy

Tip: 用法如下:






v-model 的原理

答:v-model 可以在表单或组件上创建双向绑定。v-model 的本质其实是一个语法糖,即名为 value 的 props 以及 input 事件。

  • 在组件上使用
   

等于

 
  • 在表单上使用


等于


Tip:更多介绍请看 v-model

v-if 与 v-for 一起使用

答:不推荐同时使用 v-if 和 v-for。若一定要一起使用,可以使用 template,就像这样:

  • 请简单介绍下 vuex

    答:在 vue 中我们可以使用 props 和 emit 解决父子组件之间的通信,而非父子之间的通信则可以使用 bus(中央事件总线)。而 Vuex 作为 vue 的一个插件,解决的问题与 bus 类似,更具体些,就是 vuex 能解决多个组件共享状态的需求。

    Vuex 的核心概念有 State、Getters、Mutations、Actions:

    • state,即 vuex 的数据
    • getters,可以认为是 store 的计算属性
    • mutations,更改 vuex 中 state(数据)的唯一方式
    • actions,类似 mutation,但不能直接修改 state

    Tip: 更多介绍请看

    vuex重刷还有吗,如何做持久化

    答:没有了。可以配合 sessionStorage 做持久化,亦或使用 npm 包(vuex-persistedstate)。

    Tip:

    // App.vue
    created(){
        //页面加载时读取 localStorage 里的状态信息
        if(localStorage.getItem("userMsg")){
            this.$store.replaceState(...)
        }
        //在页面刷新时保存到 localStorage
        window.addEventListener("beforeunload",()=>{
            localStorage.setItem(...)
        })
      }
    

    vue-router 默认是什么模式

    答:hash

    javascript

    是否了解 es11,?.??用过吗

    答:有所了解。例如 es11 中有 BigInt、可选链操作符( ?. )、空值合并操作符(??)。

    ?. 和 ?? 用法如下:

    const obj = {
      a: 1,
      b: {
        c: 2
      }
    };
    
    // ?. 操作符的功能类似于 . 链式操作符,不同之处在于,
    // 在引用为空 null 或者 undefined 的情况下不会引起错误,该表达式短路返回值是 undefined。
    console.log(obj.d?.c) // undefined
    
    console.log(obj.b?.c) // 2
    
    console.log(obj?.someNonExistentMethod?.()) // undefined
    
    // 当左侧的操作数为 null 或者 undefined 时,返回其右侧操作数,否则返回左侧操作数
    console.log(null ?? 'default string') // default string
    
    // || vs ??
    console.log(0 || 42) // 42
    console.log(0 ?? 42) // 0
    

    Tip:有关 ES2020、ES2021、ES2022 等特性可以查阅:kangax es2016plus、babel 插件列表。

    什么是事件循环

    答:js 是单线程的语言,同一时刻只能执行也给任务,只有第一个任务完成,才能接着做第二个任务,如果上一个任务是计算量很大,cup 很忙导致无法接着做下一个任务,这种情况没问题,但如果上一个任务是只是在等待(如 ajax 请求),cup 很闲而导致下一个任务也只能跟着一起等待,那么就不好了,所以需要事件循环这个机制来解决此问题。

    事件循环机制如下:

    1. 所有同步任务都在主线程上执行,形成一个执行栈
    2. 除了主线程,还有一个任务队列,只要异步任务有了结果就会往任务队列中放一个事件
    3. 当执行栈为空,主线程就会去读任务队列,放入执行栈中执行
    4. 如此循环

    Tip:关于事件循环更详细的介绍,有多种不同版本,笔者倾向于下面这种

    主线程:
    同步代码1->同步代码2->...->同步代码N
    
    事件队列:
    - 宏任务队列:事件1->事件2->...->事件N
    - 微任务队列: 事件1->事件2->...->事件N
    
    ①,执行栈中所有同步任务执行完毕
    ②,执行完微任务队列中所有事件对应回调
    ③,取出宏任务队列中一个事件,将对应回调加入执行栈
    
    依次循环①②③①②③...
    

    宏任务和微任务都有哪些

    答:

    • 微任务:Pormise.then、MutationObserver
    • 宏任务:setTimeout、setInterval、requestAnimationFrame

    给数组增加一个方法,只能存入唯一的值

    答:

    Array.prototype.uniquePush = function (...values) {
      values.forEach(v => {
        if (!this.includes(v)) {
          this.push(v)
        }
      })
    }
    
    let array = [1, 2, 3]
    array.uniquePush(4, 3)
    // array:  [ 1, 2, 3, 4 ]
    console.log('array: ', array)
    

    扁平化嵌套数组

    例如有这么一个数组:

    const arr = [1, 2, [3, 4, 5, [6, 7, 8], 9], 10, [11, 12]]
    

    将其扁平化(层级不限)成:

    [ 1,  2, 3, 4,  5, 6,  7, 8, 9, 10, 11, 12 ]
    

    可提供多种方式

    答:

    • 使用数组的 flat() 方法
    const flated1 = arr.flat(Infinity)
    console.log('flated1: ', flated1);
    
    • 使用递归
    function flat(arr, result = []) {
      arr.forEach(item => {
        let wrap = Array.isArray(item) ? flat(item) : [item]
        result.push(...wrap)
      })
      return result
    }
    
    const flated2 = flat(arr)
    console.log('flated2: ', flated2);
    
    • 使用 toString() 方法
    const flated3 = arr.toString().split(',').map(item => Number(item))
    console.log('flated3: ', flated3)
    

    Tip:Array.prototype.toString() 返回一个表示数组及其元素的字符串。并会调用每项的 toString() 方法:

    let obj = {
      toString() {
        return 'i am obj'
      }
    }
    
    let arr2 = ['a', 3, obj]
    // a,3,i am obj
    console.log(arr2.toString())
    

    请完成 getTree() 方法

    // 将数据转成树的结构
    function getTree(arr) {
     
    }
    
    // parent 为 -1 的是根节点。只有一个根节点
    let flatTree = [
      { name: 'b', id: 2, parent: 1 },
      { name: 'a', id: 1, parent: -1 },
      { name: 'c', id: 3, parent: 1 },
      { name: 'd', id: 4, parent: 3 },
      { name: 'e', id: 5, parent: 3 },
    ]
    
    let tree = getTree(flatTree)
    // {"name":"a","id":1,"parent":-1,"children":[{"name":"b","id":2,"parent":1},{"name":"c","id":3,"parent":1,"children":[{"name":"d","id":4,"parent":3},{"name":"e","id":5,"parent":3}]}]}
    console.log(JSON.stringify(tree))
    

    答:

    function getTree(arr) {
      arr.forEach(item => {
        if (item.parent === -1) {
          return
        }
        let pId = item.parent
        let parentNode = arr.find(item => item.id === pId)
        if (!parentNode.children) {
          parentNode.children = []
        }
        parentNode.children.push(item)
      })
    
      const root = arr.find(item => item.parent === -1)
      return root
    }
    

    刷新页面 sesstionStorage 会清空吗?

    答:不会

    刷新页面,将在控制台依次输出1、2、3...;若用新的 tab 访问,又会依次输出1、2、3...

    let msg = sessionStorage.getItem('a')
    if (!msg) {
        sessionStorage.setItem('a', '1')
    } else {
        sessionStorage.setItem('a', (+msg) + 1)
    }
    
    console.log(sessionStorage.getItem('a'));
    

    Tip: 更多介绍请看 sessionStorage

    new Date('abc') 会如何

    答:控制台输出 Invalid Date

    async 中的 await 后面如果报错,会发生什么

    答:会中断 await 后续代码的执行,async 方法会立即结束。

    Tip

    • 不明白题意,请看如下代码
    async function foo() {
      // obj 是一个不存在的变量
      await obj
      return 1
    }
    
    foo().then(v => {
      console.log(v)
    }).catch(v => {
      console.log(`catch, ${v}`)
    })
    
    // 输出:catch, ReferenceError: obj is not defined
    
    • 为什么会这样可以查看:

    class 的构造函数能否使用 async

    答:不能。

    请看示例:

    class Dog {
      async constructor() {
        this.name = 'apple'
      }
      async say() {
        console.log(this.name);
      }
    }
    
    new Dog().say()
    
    // Class constructor may not be an async method
    

    若用数组来实现栈、队列,需要用到数组的什么方法

    答:栈是先进后出,所以会用到 push 、pop;而队列是先进先出,故用到 push、shift

    如何解决 0.1 + 0.2 != 0.3

    答:此问题的出现是因为浮点数不能精确地用二进制表示所有小数,会出现一些意外的结果,比如:

    let a = 0.1
    let b = 0.2
    let c = 0.3
    
    // 0.30000000000000004
    console.log(a + b)
    

    有三种方法可以解决:

    • Number.EPSILON
    • 转为整数再比较。即将数字提升 10 的 N 次方
    • 使用 toFixed()

    请看示例:

    function equal(a, b) {
      return Math.abs(a - b) < Number.EPSILON
    }
    let isEqual = equal(a + b, c)
    // isEqual:  true
    console.log('isEqual: ', isEqual)
    
    let seed = Math.pow(10, 10)
    let isEqual2 = (a * seed + b * seed) === c * seed
    // isEqual2:  true
    console.log('isEqual2: ', isEqual2);
    
    let isEqual3 = parseFloat((a + b).toFixed(10)) === c
    // isEqual3:  true
    console.log('isEqual3: ', isEqual3)
    

    http

    什么是协商缓存、什么是强制缓存

    答:缓存的作用很好理解,通过复用以前的资源,可以提高网站的性能,提升用户体验。

    通常第一次获取资源后,会根据返回的信息(respone header)得知如何缓存,例如做强缓存、协商缓存或不做缓存。

    强缓存的特性是,下次直接从缓存中读取,不会发送到服务器,返回的状态码是 200(from cache)

    协商缓存的特性,也是直接从缓存中读取,不过得先到服务器那里询问一下缓存资源是否可用,返回的状态码是 304(not modified)

    与强缓存相关的 http 头有 cache-controlExpires。比如Cache-control: max-age=N的头,相应的缓存的寿命就是N,对于不含这个属性的请求,则会去查看是否包含 Expires 属性。

    与协商缓存相关的是 EtagLast-Modified。比如,用户再次访问给定的URL(设有ETag字段),若资源过期了,客户端就发送值为ETag的If-None-Match header字段。Last-Modified 由于精确度比 ETag 要低,所以这是一个备用机制

    Tip: 对于单页面,我们可以通过更新页面(例如 index.html)中引用资源的路径,让浏览器重新加载资源,更多介绍请看 MDN HTTP 缓存、静态资源的缓存。

    https 为什么安全

    答:https 即超文本传输安全协议。https 经由 http 进行通信,但利用 SSL/TLS(安全协议) 来加密数据包。之所以说 https 安全,主要是因为它有一套比较好的数据传输方案。

    比如 A 向 B 发送消息,A 会通过 B 提供的公钥对数据进行加密后再传输。并且 A 不是直接从 B 那里获得公钥,而是从认证机构取得。

    也就能保证:

    • 数据是以密文的形式进行传输,且只能被服务端配对的的私钥进行解密
    • A 取得的公钥来自认证机构,保证了公钥确实是 B 的,而非伪造的

    Tip:相关术语介绍:

    • 加密算法和解密算法统称为密码算法
    • 密钥是使用密码算法中输入的一段参数。同一个明文在相同的密码算法和不同的密钥下会产生不同的密文。根据密钥的使用方法,可分为对称加密和公钥加密
    • 对称加密,加密和解密使用相同的密钥
    • 公钥加密,分为加密密钥(或称公钥)和解密密钥(私钥),其中任何人都可以获取公钥,而私钥只能自己使用
    • 传输层安全性协议(英语:Transport Layer Security,缩写:TLS)及其前身安全套接层(英语:Secure Sockets Layer,缩写:SSL)是一种安全协议,目的是为互联网通信提供安全及数据完整性保障

    :http 的默认端口是 80,而 https 的默认端口是 443。

    描述下浏览器输入 url 回车后的整个过程

    答:
    大概流程:

    1. 首先拿到服务器 ip(即域名解析)
    2. 接着和服务器建立连接,传递数据(即相互通信)
    3. 服务器处理请求
    4. 浏览器接收响应

    更具体一些:

    域名解析
    拿到服务器 ip 需要用到 域名解析(将好记的域名解析成 ip)。

    解析流程大致如下图所示:

    1. 客户端:我要访问 www.163.com,请告诉我 ip
    2. 本地 dns 服务器:缓存里找不到,去 dns 根服务器询问
    3. dns 根服务器:这个域名由 .com 负责
    4. .com 域服务器:163.com 域名应该知道
    5. 163.com 域服务器:ip 是 1.1.1.xx
    6. 本地 dns 服务器取得 www.163.com 对应的 ip,写入缓存,返回 ip 给客户端

    Tip

    • 真实的解析过程情况会更多,比如会搜索系统中 hosts 文件。笔者很久以前用过一个叫 SwitchHosts(能管理、切换多个 hosts) 的工具,开发中用于切换不同的环境,比如开发环境、测试环境等等。
    • dns(即域名系统),用于管理有意义的域名和ip的对应关系

    相互通信
    主要是涉及 TCP/IP协议(一个协议族,能够在多个不同网络间实现信息传输,也是 Internet 最基本的协议)。

    大致流程如下:

    1. 浏览器发起 http 请求
    2. 经由应用层
    3. 然后到传输层,其中 tcp 会使用三次握手建立连接,后面则会使用四次握手中断连接
    4. 在往下就是网络层,比如 ip 寻址
    5. 最后是网络接口层,这里有传输数据的物理媒介
    6. 服务端接收则会反过来,依次是网络接口层,网络层,传输层,应用层

    Tip:不同种类的应用程序会根据自己的需要来使用应用层的不同协议,比如浏览器在这里会使用 http 协议。

    浏览器接收响应
    大致流程如下:

    • 浏览器接收到资源后,会根据响应内容做不同的处理
    • 比如响应码是 301 则永久重定向,如果是 304 则去读缓存
    • 比如资源使用 gzip 压缩了,就得解压
    • 比如资源类型是视频(Content-Type:video/mp4),浏览器则会播放
    • 比如是否对资源做缓存

    Tip:有关构建 dom 树、预加载扫描器、构建 cssom树、回流、重绘等可以看 这里

    零碎

    • 如果输入的不是合法的 url,而是关键字,那么浏览器就会去搜索该关键字
    • 如果之前已经访问过,那么会先走强缓存,如果过期就走协商缓存
    • 可能还会涉及 sts,例如通过 http 而非 https 来访问淘宝、百度、京东的首页,则会发生一个 307 的重定向,并转到 https 上来

    webpack

    简单说一下 tree-shaking (树摇)

    答:用于描述移除 JavaScript 上下文中的未引用代码

    Tip: 更多细节请看 tree-shaking

    说一下你知道的 loader

    答:由于 webpack 只能识别 javascript,其他资源都需要相应的 loader 来做翻译。

    与css相关的有:css-loader、style-loader、less-loader、sass-loader、postcss-loader

    配合图片的有:url-loader、file-loader

    与 svg 相关的有:svgo-loader

    与 js 有关的有:babel-loader、ts-loader

    与 vue 相关的有:vue-loader、vue-style-loader

    Tip:以下是相应 loader 的简易介绍:

    • css-loader,将 css 文件翻译成 webpack 能识别
    • style-loader,将 css 注入 dom。
    • less-loader,用于将 less 转为 css。类似的有 sass-loader、stylus-loader
    • postcss-loader,webpack 它用来使用 postcss
    • url-loader,将图片转为 base64
    • file-loader,配合 url-loader 可以生成图片
    • label-loader,用于在 webpack 中使用 babel
    • ts-loader,与 typescript 相关
    • svgo-loader,与 svg 相关,用于 svg 优化
    • vue-loader,它允许你以一种名为单文件组件 (SFCs)的格式撰写 Vue 组件
    • vue-style-loader,是一个基于 style-loader 的 fork,作为依赖项包含在 vue-loader 中,无需下载 1. vue-style-loader 即可使用。

    请说一说 loader 和 plugin

    答:
    webpack 没有特殊配置只能识别 javascript,而前端还有css、图片等其他资源。

    loader 用于对模块的源代码进行转换,可以理解成”翻译官“,比如我们需要使用 css,那么可以用 css-loader,如果需要使用图片,我们可以使用 url-loader 和 file-loader,如果需要将箭头函数翻译成普通函数,可以使用 babel-loader。loader 本质上是导出为函数的 JavaScript 模块。

    而插件的目的是用于解决 loader 无法是现实其他事,plugin 强调一个事件监听能力,能在 webpack 内部监听一些事件,并且能改变一些文件打包后输出的结果。比如我们可以使用 html-webpack-plugin 来帮我们创建 html 并自动引用打包后的资源。plugin 本质上是一个具有 apply 方法的类。

    Tip:有关 loader 和 plugin 的本质介绍可以查看 。

    请说一下 vue-loader

    答:vue-loader 是一个 webpack 的 loader,它允许我们能以单文件组件的格式编写 vue 组件。同时它也提供了一些其他特性,比如:提供热重载、提供 scoped css、允许为 vue 组件的每个部分使用其他的 webapck loader(如 style 部分使用 sass,template 部分使用 pug)。总之,webpack 配合 vue-loader 能帮助我们编写 vue 应用。

    Tip: 更多介绍请看

    其他

    能否说说 lodash.js 的深拷贝

    答:没看过 lodash.js,但我用过 jQuery 的 extend 方法,如果要深拷贝,则传 true 给第一个参数

    深度作用选择器

    修改 element-ui 样式,比如将表格组件的表头改为红色,通常是不起作用,就像下面这样:

    
      
    
    

    我们可以怎么做?

    答:可以使用 深度作用选择器。即三个大于号(>>>),就像这样:

    
    

    子域名跨域 Document.domain

    不修改后端服务器,只在前端设置,如何让 a.baidu.com 和 b.baidu.com 能相互访问

    答:可以使用 Document.domain

    平时你是如何封装 axios

    答:会简单封装下 axios。比如创建一个 request.js 的文件,里面返回一个 axios 实例,再做一下请求拦截和响应拦截。请求拦截器可以统一给所有请求传递一些信息给服务器,比如 token,在响应拦截中,比如返回 code 不是2000,则抛出错误,比如是 3000 就说明需要重新登录等等。

    如何将二进制数据显示成图片

    答:或许可以利用 URL、Blob。就像这样:

    var blob = new Blob(...);
    var url = URL.createObjectURL(blob)
    imageElement.setAttribute('src', url)
    

    一般通过什么途径来学习

    答:比较杂,比如:腾讯 Web 前端、百度前端、淘宝前端、凹凸实验室、奇舞团,或者 github(看开源项目)、npm、babel、掘金、简书、博客园、纸质书、线上视频(如 慕课网、bilibili、极客学院)、技术相关的官网(如 node、express、webpack)、mdn、维基百科等等

    简单谈一下跨域

    答:跨域方式有很多,但现在主流的有两种,一种是开发环境和生成环境都使用 cors(跨域资源共享),一种是开发环境用 proxy,而生产环境用 nginx。

    推荐的是 cors,它能决定浏览器是否阻止前端 JavaScript 代码获取跨域请求的响应。前端无需变化,工作量在后端。而后端的开发环境和生成环境是一套代码,所以也很方便使用。如果后端不愿意,前端就用 proxy + nginx。

    Tip:更多细节可以查看 这里

    请说一下 bfc(块级格式化上下文)

    答:bfc 有个特性,如果一个元素具有 bfc,那么内部子元素在怎么折腾,都不会影响外部元素。

    所以 bfc 元素不会有 margin 重叠。bfc 元素也能用来清除浮动的影响,子元素浮动会导致父元素高度塌陷,则会影响后面布局,这有悖于前面的特性。

    创建 bfc 有多种方式,比如 overflow:auto/hiddendisplay:table-cell

    是否用过 chrome 中 Performance

    答:用过。通常在无痕模式下进行来分析性能,比如可以通过勾选内存其来分析内存使用情况,或使用调用树来找出花费时间较长的函数等。

    是否了解过微服务、微前端

    答:是的。微前端第一提出好似在 2016年底,将微服务这个被应用于服务端的技术扩展到前端领域。微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。比如 蚂蚁金融开源一套完整的微前端解决方案,即 qiankun(对于用户而言只是一个类似 jQuery 的库)。

    是否自己写过 npm 包

    答:写过。

    Tip:如何发布一个 npm 包请看 这里

    结束

    在罗列一点剩余的题目:

    • 介绍一下 vue,就当我是 vue 小白
    • 是否了解 csrf,能否简单谈一下
    • 如果做单页面应用的首页优化
    • 是否了解 vue 中的 $listeners、$attrs
    • 请介绍一下 http1 和 http2 的关系和区别
    • 比如让你制定项目规范,你会从什么方面着手
    • 请说说你知道的 http 头部(headers)
    • 请说说你知道的 http 状态码
    • 是否使用过 corss-env

    把心放平,轻装上阵,祝君好运。