谈面试前端工程师
谈面试前端工程师
- 打战需要周全的准备,而找一份年薪几十万的工作对于个人来说不亚于一场大战
- 有一天,你若需要招人,则需要在极短的时间内从各方面考核对方是否合适
- 通过面试可以推动自己主动了解行业新技术,尤其是长期呆在一家技术不那么好的公司
- 面试的内容数不胜数(亦或千奇百怪),面试官和面试者的背景也不尽相同,或许我们只能尽力而为。将每次面试当成一次修炼,好好享受
以下是近期朋友去面试前端工程师所遇到的一些问题,整理如下:
vue
v-model 修饰符有哪些
答:.number、.trim、.lazy
Tip: 用法如下:
<input v-model.lazy="msg"> <input v-model.number="age" type="number"> <input v-model.trim="msg">
v-model 的原理
答:v-model 可以在表单或组件上创建双向绑定。v-model 的本质其实是一个语法糖,即名为 value 的 props 以及 input 事件。
- 在组件上使用
<custom-input v-model="count"></custom-input> 等于 <custom-input :value="count" @input='changCount'></custom-input>
- 在表单上使用
<input v-model="message" placeholder="edit me"> 等于 <input type="text" :value='message' @input='handleInput' placeholder="edit me">
Tip:更多介绍请看 v-model
v-if 与 v-for 一起使用
答:不推荐同时使用 v-if 和 v-for。若一定要一起使用,可以使用 template,就像这样:
<li v-for="todo in todos" > <template v-if="!todo.isComplete"> {{ todo }} </template> </li>
请简单介绍下 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基础
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 很闲而导致下一个任务也只能跟着一起等待,那么就不好了,所以需要事件循环这个机制来解决此问题。
事件循环机制如下:
- 所有同步任务都在主线程上执行,形成一个
执行栈
- 除了主线程,还有一个
任务队列
,只要异步任务有了结果就会往任务队列中放一个事件 - 当执行栈为空,
主线程
就会去读任务队列,放入执行栈中执行 - 如此循环
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
- 为什么会这样可以查看:async
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-control和
Expires。比如
Cache-control: max-age=N的头,相应的缓存的寿命就是N,对于不含这个属性的请求,则会去查看是否包含
Expires属性。
与协商缓存相关的是
Etag和
Last-Modified。比如,用户再次访问给定的URL(设有ETag字段),若资源过期了,客户端就发送值为ETag的
If-None-Matchheader字段。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 回车后的整个过程
答: 大概流程:
- 首先拿到服务器 ip(即域名解析)
- 接着和服务器建立连接,传递数据(即相互通信)
- 服务器处理请求
- 浏览器接收响应
更具体一些:
域名解析 拿到服务器 ip 需要用到 域名解析(将好记的域名解析成 ip)。
解析流程大致如下图所示:
- 客户端:我要访问
www.163.com
,请告诉我 ip 本地 dns 服务器
:缓存里找不到,去 dns 根服务器询问dns 根服务器
:这个域名由 .com 负责.com 域服务器
:163.com 域名应该知道163.com 域服务器
:ip 是1.1.1.xx
- 本地 dns 服务器取得
www.163.com
对应的 ip,写入缓存,返回 ip 给客户端
Tip:
- 真实的解析过程情况会更多,比如会搜索系统中 hosts 文件。笔者很久以前用过一个叫 SwitchHosts(能管理、切换多个 hosts) 的工具,开发中用于切换不同的环境,比如开发环境、测试环境等等。
- dns(即域名系统),用于管理有意义的域名和ip的对应关系
相互通信 主要是涉及 TCP/IP协议(一个协议族,能够在多个不同网络间实现信息传输,也是 Internet 最基本的协议)。
大致流程如下:
- 浏览器发起 http 请求
- 经由应用层
- 然后到传输层,其中 tcp 会使用三次握手建立连接,后面则会使用四次握手中断连接
- 在往下就是网络层,比如 ip 寻址
- 最后是网络接口层,这里有传输数据的物理媒介
- 服务端接收则会反过来,依次是网络接口层,网络层,传输层,应用层
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-loaderpostcss-loader
,webpack 它用来使用 postcssurl-loader
,将图片转为 base64file-loader
,配合 url-loader 可以生成图片label-loader
,用于在 webpack 中使用 babelts-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: 更多介绍请看 vue-loader
其他
能否说说 lodash.js 的深拷贝
答:没看过 lodash.js,但我用过 jQuery 的 extend 方法,如果要深拷贝,则传 true 给第一个参数
深度作用选择器
修改 element-ui 样式,比如将表格组件的表头改为红色,通常是不起作用,就像下面这样:
<template> <el-table></el-table> </template> <style scoped> .el-table thead { color: red; } </style>
我们可以怎么做?
答:可以使用 深度作用选择器。即三个大于号(>>>),就像这样:
<style scoped> >>> .el-table thead { color: red; } </style>
子域名跨域 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/hidden、
display:table-cell
是否用过 chrome 中 Performance
答:用过。通常在无痕模式下进行来分析性能,比如可以通过勾选
内存其来分析内存使用情况,或使用
调用树来找出花费时间较长的函数等。
是否了解过微服务、微前端
答:是的。微前端第一提出好似在 2016年底,将微服务这个被应用于服务端的技术扩展到前端领域。微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。比如 蚂蚁金融开源一套完整的微前端解决方案,即 qiankun(对于用户而言只是一个类似 jQuery 的库)。
是否自己写过 npm 包
答:写过。
Tip:如何发布一个 npm 包请看 这里
结束
在罗列一点剩余的题目:
- 介绍一下 vue,就当我是 vue 小白
- 是否了解 csrf,能否简单谈一下
- 如果做单页面应用的首页优化
- 是否了解 vue 中的 $listeners、$attrs
- 请介绍一下 http1 和 http2 的关系和区别
- 比如让你制定项目规范,你会从什么方面着手
- 请说说你知道的 http 头部(headers)
- 请说说你知道的 http 状态码
- 是否使用过 corss-env
把心放平,轻装上阵,祝君好运。
- 阿里校招前端工程师实习生面试心得及体会
- 恒盛新锐android前端开发工程师面试经验
- 面试高级前端工程师必问之流-stream
- web前端开发工程师面试技巧自我总结
- 前端开发工程师应该如何准备一场技术面试?
- 完整的前端工程师面试问题列表
- 完整的前端工程师面试问题列表
- 初级前端工程师面试注意事项
- 面试总结三---2015百度校园招聘长沙站前端工程师笔试面试经历
- 前端工程师面试总结
- 金山2018春季前端开发工程师招新面试
- 完整的前端工程师面试问题列表
- 前端工程师的面试套路
- 前端工程师面试指南:破解4个关键问题
- 前端工程师面试问题列表
- 面试前端工程师
- 前端工程师面试内容
- 面试前端工程师:Github很重要
- 2015年7月VIP内推前端工程师面试经历
- 一个前端工程师的面试经历