由于公司年前的一些战略调整,和自己预感公司路线的担忧,自己也不得不提前准备一些后路,这期间陆续面试了头条,西瓜视频这边,支付宝公益这边和猿辅导这块。这次和三年前的职业变更,leetcode 刷了一些,但是其实也没刷多少,至少面头条的时候,也只是刷了少数 easy / medium 和那些后端相比少了很多。¶
统计信息:字数 107034 阅读215分钟
然后就是特别推荐这个技术图,去准备复习的知识点:
这期间,还有就是对自己特别模糊记忆不清楚的一定记笔记,这期间自己 Evernote 也不断誊抄“答案” 也有 32篇左右,主要还是需要不断加强记忆,尤其面试前。
简单说下三家的各自经历吧。
字节跳动-视频架构¶
这个大概是十一月面的,这个时候离职意愿还没有那么强烈,而是帮助前同事去完成一个内推名额。打心底,我觉得头条加班多,以及江湖一些小恩怨,我不太偏向去。不过面试还是认认真真的准备,听闻面试很难,加上自己本身是偏 Engineering (Production > Science)多点那种, 所以感觉跪的概率还是比较大。
大概一面是交叉面,是在头条的海淀总部那边,是安全的前端小哥,感觉年龄很年轻(我前同事说她去头条最大感觉就是小鲜肉太多了)。一面纯粹是基础面试题,涉及 JS 的非常多,大概题目我后面记录了下:
- JS 的基本数据类型?
- 引用类型是什么?
- ES6 Symbol 是什么,用在什么地方?
- 最满意的项目,学到了什么?
- 题目1
- 题目2
- 实现函数防抖或者截流的任意一个?
- 使用 ES5 实现原型链继承?
- 算法题目
- 统计一个字符串出现次数最大的字符,并且获取出现频率 比如 "accdddccaaaaa" 答案 "a” 6
总的来说,一面还是比较快,40-50分钟就完成了,全部都是偏 JS 的题目,大概面完后八点半去了另外一个地方,开车去的,是另外一个办公地点。
二面是未来同部门同事(也很年轻,给我们这些老人一些生路把)面试的,问了也是巨多的问题,涉及到了项目和视频这块,大概聊了一个多快一个半小时了。也要求手写代码,和思路总结这些。
- var vs const ? 什么是变量提升?
- 平时学习的途径有哪些?会看什么样的书籍?
- Object.defineProperty() 里面,可以设置哪些属性,分别代表什么意思? 实际场景有哪些用到了
- 手写 实现 JS 单例模式和订阅模式(EventMitter)
- 手写,使用 Promise 实现任务队列发送请求,实现最大请求数目限制任务?
- 如何实现无缝切换分辨率?
- HLS 播放,dts 出错,有什么解决方案没有?
- 如何解决画面音频不同步的问题?
- H.264 里面 I 帧,B 帧 P 帧,代表什么? IDR 帧代表什么?
- H.265 的解决方案有哪些
- 算法 找到连续某个字符串中不重复的最大子字符, 类似 “acdeadea” => “acde” leetCode 上的题目
- 如何克服跨域问题,常见的跨域手段有哪些?
- HTTPS 的工作原理,证书机制有什么好处?
- 如何推进前端和后端需要配合的项目?
- WebVR 底层原理是,播放器架构是怎样的?
二面问的问题也非常多,涉及到前端基础和项目设计的问题,然后感觉那天精神状态特别好,很多问题思路都比较不错,然后感觉对面的小哥也非常好奇我得项目经历,彼此项目交集还是比较多,他也觉得有些思路之前项目没有用到过。差不多从8点40多面到10点二十左右,然后就叫前同事下班走了。
三面在隔了两天通知了,面试的是这边前端总负责人,问了一些关于简历和意愿的一些问题:
- 个人职业发展的经历?说出感觉印象最深的项目?
自己答的是一个 Canvas 粒子动画和现在整体播放的优化这块。
然后面试官又根据这些项目问了一些实现细节,关于代码设计和一些特性原理这块。
总之三面就是一些项目经历的总结,类似 PPT 展示这样子,要说服下面的观众,觉得你做的事情还是很有意义的。
差不多三十分钟左右,又叫坐车去头条总部那边,应该就是 HR 面试了。当然 HR 就是一些常规的问题,然后和个人一些发展预期。其中我个人认为失败感最强的时间段,个人恰好说到了现在,感觉身边都是牛人,为什么项目最后推进一直那么顺利,和创业的不可预知因素的无奈等等。
头条的总体流程还是很快的,隔了周末, offer 开始沟通,整体 Package 感觉头条确实给力,但是自己还是觉得自己创业这块还有希望,加上不太想加班哈哈哈,只能婉拒了。
支付宝 - 公益¶
后面前部门关系比较好的 TL 把简历推给了支付宝这边,北京三环总部那边,支付宝公益也就是种树,春节红包这边,这块项目吸引力确实足够。
一面是周六上午,人很 Nice 说周六上午过来面试也行,他也刚好过来加班。一面主要是问了下近况,和项目这些,然后就是一轮笔试题目,一面应该是未来一起共事的同事。
- Sticky Footer 布局,内容增加,但是 Footer 始终在页面底部
- 给页面所有 Class 名称为
item
的元素绑定click
事件; - 代码阅读
- 实现一个简单的事件订阅模式,实现
on()
和emit()
方法; - 实现一个正则表达式,匹配 https://gwalipay.alicdn.com/style-main.css?( 匹配所有包含 .css 的文件,可能带查询参数)
差不多,写完之后,又闲聊了这边支付宝的整体情况,后面就走了。
过了差不多几天,周三下午收到面试通过,准备二面,面试是远程电话,上海团队的交叉面试。二面技术基础的问的比较少,还是主要问项目,和项目的产品参与度?
- 说说过去一年做的比较重要的项目?
- 性能优化的具体实践,如何解决页面卡和慢的问题?
- 优化的数据对比是怎样的?上线后数据是怎样的?(这里面试官察觉出自己产品嗅觉不是很敏感)
- VR 图片是怎么优化的?线上转码系统是怎样工作的?
- 播放稳定性的埋点是怎样的?
- 线下产品的重要指标有哪些,技术会关系到哪些?
当然差不多二面聊了45分钟,由于在地铁站面试的,感觉交流不是那么顺利,不过好在后面还是通知了三面四面的安排。也加了二面的微信,觉得未来如果来上海可以交流哈哈哈。
过了差不多一周左右时间,因为当时人去云南了,一直等回帝都,才能面试。三面四面是一起面试的,也是电话面试,三面结束没多久,就面试了四面。
他们也是问了一些项目上的问题,其中四面面试官比较牛?开篇就是像我这种经验的人,先给20分钟吹牛,然后他再问20分钟问题?
然后就 balabala 说了20多分钟,然后提了关于 PWA 缓存利用,和 Prefetch 包括 Local DNS 和阿里云 CDN 热源的问题,有一些是关于架构层面的内容,涉及到整个集群的服务利用等,总之问的非常多关于链路的内容,然后又问了 H.265 解决思路和优化怎么做的,以及同步音画等问题。感觉四面最后一面还是暴露了一些架构上的确实,和所谓知识广度问题。
过了三天,通知 HR 面试,见到了 HR 和 前端组负责人,沟通了职业经历,和不稳定性。HR 也大致聊了个人意愿和发展预期,差不多很快两个人也就聊了40多分钟就结束了,然后在国贸那边吃了汉堡王,感觉阿里这面试流程真的算是比较长(虽然很早就知道),由于那个时候双十二和红包活动,过了十天左右,HR 才通知 Offer 申请下来,进行双向沟通,这里就是涉及特别重要的个人发展问题。我个人如果偏 Web 多媒体的话,可能那边短期是不会有个方向的,但是后期肯定会有,还有就是薪资上,不会去和头条 Match ,看个人抉择。我觉得这边 HR 还是很 Nice 说足了自己的一些不足,包括所谓“宽度“ 和 业务思维这些问题,强调这个时间点是不是最好的时候回归阿里这样的。当然最后,自己还是断了这个 Offer。一个是自己觉得业务兴趣问题,也包括整个薪资打底的问题。
猿辅导¶
其实猿辅导也是年后自己公司进行团队调整和疫情薪资调整,包括更严重的 Streaming 流量控制的原因,自己接触了这边。面试流程很快,由于在家办公,猿辅导是在牛客网上面试,一二轮都是交叉面试(是因为面试人员紧张),问的问题也都还是非常基础:
- 函数防抖/截流
- Event Loop 代码题目
- 算法,二分查找
- Promise All
- Object.Proxy
- ES6 的一些新特性
- 箭头函数
- 继承
其中算法问了关于树的,左视树,输出左边看到的节点。
总的来说每一轮都控制在 45 分钟左右,随后就是部门 TL 的三面,聊了项目上的一些,也涉及了团队方面的一些问题,包括规范,执行等。由于项目问题已经被问了太多次,自己是真的回答的滚瓜乱熟了。大概隔了两天,HR 面试确认个人发展预期,然后和前端负责人双向沟通,大概也就是现任 TL ,给的建议,建议我从技术广度去发展,不要局限某个领域的深度扩展,这边也给予人员管理的期望,希望可以共建专业的前端团队等。
总的来说这三家公司各有特点,从个人角度看,也是暴露了自己整个前几年过于专注的问题,整个思维的广度有所欠缺,和前TL聊的时候,他给的建议,就是要学会讲故事的能力,说白了技术基础,是个人稍微多努力点,多记忆点,都好通过,到更高纬度去思考和项目分析,这种是实战和总结的。感觉的确离那个目标还是有一定距离,总之大家在平时学习积累中,总结和思考一定要有,还有就是在职业发展后期,一定要强调个人的业务联动能力,学会去把项目的整体流程把控起来,说白了技术基础决定你拥有现在的饭碗,个人综合能力(技术能力哦,产品能力,逻辑思维能力,沟通能力)决定你在市场的整体定位。
Daily-Interview-Question¶
第 160 题:输出以下代码运行结果,为什么?如果希望每隔 1s 输出一个结果,应该如何改造?注意不可改动 square 方法
const list = [1, 2, 3]
const square = num => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(num * num)
}, 1000)
})
}
function test() {
list.forEach(async x=> {
const res = await square(x)
console.log(res)
})
}
test()
解析:第 160 题
最近汇总¶
第 159 题:实现 Promise.retry
,成功后 resolve
结果,失败后重试,尝试超过一定次数才真正的 reject
解析:第 159 题
第 158 题:如何模拟实现 Array.prototype.splice
解析:第 158 题
第 157 题:浏览器缓存 ETag 里的值是怎么生成的
解析:第 157 题
第 156 题:求最终 left、right 的宽度
<div class="container">
<div class="left"></div>
<div class="right"></div>
</div>
<style>
* {
padding: 0;
margin: 0;
}
.container {
width: 600px;
height: 300px;
display: flex;
}
.left {
flex: 1 2 300px;
background: red;
}
.right {
flex: 2 1 200px;
background: blue;
}
</style>
注:此题和 155 题 left、right 样式有些不同
解析:第 156 题
第 155 题:求最终 left、right 的宽度
<div class="container">
<div class="left"></div>
<div class="right"></div>
</div>
<style>
* {
padding: 0;
margin: 0;
}
.container {
width: 600px;
height: 300px;
display: flex;
}
.left {
flex: 1 2 500px;
background: red;
}
.right {
flex: 2 1 400px;
background: blue;
}
</style>
解析:第 155 题
第 154 题:弹性盒子中 flex: 0 1 auto 表示什么意思
解析:第 154 题
第 153 题:实现一个批量请求函数 multiRequest(urls, maxNum)
要求如下:
- 要求最大并发数 maxNum
- 每当有一个请求返回,就留下一个空位,可以增加新的请求
- 所有请求完成后,结果按照 urls 里面的顺序依次打出
解析:第 153 题
2019-12-31
第 152 题:实现一个 normalize 函数,能将输入的特定的字符串转化为特定的结构化数据
解析:第 152 题
2019-11-25
第 151 题:用最简洁代码实现 indexOf 方法
解析:第 151 题
2019-11-21
第 150 题:二分查找如何定位左边界和右边界
不使用JS数组API,查找有序数列最先出现的位置和最后出现的位置
解析:第 150 题
2019-11-12
第 149 题:babel 怎么把字符串解析成 AST,是怎么进行词法/语法分析的?
解析:第 149 题
2019-11-01
第 148 题: webpack 中 loader 和 plugin 的区别是什么(平安)
解析:第 148 题
2019-10-31
第 147 题:v-if、v-show、v-html 的原理是什么,它是如何封装的?
解析:第 147 题
2019-10-29
第 146 题:Vue 中的 computed 和 watch 的区别在哪里(虾皮)
解析:第 146 题
2019-10-24
第 145 题:前端项目如何找出性能瓶颈(阿里)
解析:第 145 题
2019-10-22
第 144 题:手写二进制转 Base64(阿里)
解析:第 144 题
2019-10-21
第 143 题:将 '10000000000' 形式的字符串,以每 3 位进行分隔展示 '10.000.000.000'
解析:第 143 题
2019-10-17
第 142 题:(算法题)求多个数组之间的交集(阿里)
解析:第 142 题
2019-10-15
第 141 题:Vue 中的 computed 是如何实现的(腾讯、平安)
解析:第 141 题
2019-10-14
第 140 题:为什么 HTTP1.1 不能实现多路复用(腾讯)
解析:第 140 题
2019-09-17
第 139 题:谈一谈 nextTick 的原理
解析:第 139 题
2019-09-11
第 138 题:反转链表,每 k 个节点反转一次,不足 k 就保持原有顺序(哔哩哔哩)
解析:第 138 题
2019-09-04
第 137 题:如何在 H5 和小程序项目中计算白屏时间和首屏时间,说说你的思路
解析:第 137 题
面试分享:两年工作经验成功面试阿里P6总结¶
前言¶
本文主要给大家带来一些我面试的经历和经验,希望对正在求职的同学有所帮助。我先大致说下面试之前的个人情况:2017年7月正式入职海康威视数字技术股份有限公司,使用Vue.js技术栈。
我写的篇幅可能有点长,如果只想看成功的面试请直接从阿里企业智能事业部(一面)开始,大家见谅哈。
这里推荐阅读之前写的文章(前面两篇实用型,后面三篇对面试应该会有帮助):
- Vue CLI 3结合Lerna进行UI框架设计
- Cz工具集使用介绍 - 规范Git提交说明
- 你真的理解$nextTick么
- 基于Vue实现一个简易MVVM
- 2019 前端之路(干货满满)(墙裂推荐)
关于阿里¶
Hi,大家好,我们是阿里巴巴新成立的BU,目前还有大量的Web前端职位空缺,机会难得,希望正在找工作的同学们可以来试试:
- 目前Web前端急缺P6和P7(阿里的很多BU都只招P7了)
- 新的BU你进来即是元老😂😂😂
- 前端技术体系大部分需要一起重新开拓,可以学习到更多的新内容
- 主要负责PC端、客户端、钉钉E应用以及支付宝小程序的开发(我本人完全不会小程序,不用担心😂😂😂)
- 技术栈是React(如果你是Vue技术栈完全不用担心,因为我也是😂😂😂)
- 其他BU面试可能有五轮,我们这边只有4轮面试
真的机会难得哦,如果想更多了解我们BU以及找我内推的同事加我钉钉或者微信(纯粹找我了解或者沟通技术也行,啊哈哈):18768107826
简历¶
我的简历只是简单的用MD做了一份,大致包含了以下几个部分:
- 基本资料
- 专业技能
- 工作经历
- 实习经历(可选)
- 项目经历
小提示:在**基本资料**里一定要填写正确的邮箱地址,我在前期面试的时候都没有打开邮箱查看面试情况,导致一些面试的时间点和面试结果都不清楚(一直以为会发短信通知)。
如果去现场面试,一定要记得带上笔和简历。一方面你给面试官的简历必定是最新的(在不断面试的过程中你必定会修改简历),另一方面这也会给面试官一种非常舒心的感觉。
对于简历这里提一点,在写自己的**专业技能**和**项目经历**时尽量不要给自己挖坑,这里展示一下我的专业技能(我会的不多):
- 熟悉嵌入式C、JavaScript、Node.js
- 熟悉Vue.js框架
切忌写一大堆让人感觉花里胡哨的技能,尤其是一些很浅显的技能(基本技能除外)。如果你有一些别人很难替代的技能,那这些技能就是亮点了,我这里就没什么亮点技能。有些技能你会但是不熟练,你可以适当的在你的**项目经历**中体现出来。对于**项目经历**尽量挑自己觉得非常有技术含量的项目进行说明(宁缺毋滥),对于自己参加过但不是特别熟悉的项目尽量不要填写,防止给自己挖坑。
小提示:这里附上的我的面试简历供大家参考。感谢jsliang的文章2019 面试系列 - 简历,大家制作简历时也可以参考这篇文章。
在投递简历时大家千万不要被招聘信息中的要求吓到,记得有一次投递简历时我对招聘者说自身不太符合要求,招聘者当时说要求都是唬人的,觉得有兴趣就投,有些招聘要求可能正是你未来学习或者深入的领域。
面试¶
简历制作完后我大概投了四家公司:有赞、滴滴、51信用卡和阿里。其中有赞挂在二面,滴滴挂在一面,51信用卡挂在一面,阿里两个部门挂在一面,一个部门面试成功。很多面试者的经历可能都是像我这样,在一次次的面试失败中不断的总结进步,最终拿到理想的Offer。
小提示:建议大家在投递简历时可以先投递一些试水的小公司,先检验一下自己是不是可以胜任这些公司的面试。同时在每一次面试完后记得把面试官提问的问题记录下来,对于没有答上来的问题还是要好好搞懂或者实践一下,因为很有可能下一家的面试官会问同样的问题。
在面试的过程中,这里我给出几点意见:
- 心态放平稳,假设第一题你答不上来很正常,面试官不会因为第一题你不会就PASS你
- 不会的题目一定不要瞎猜,往往面试官给你挖的坑就是希望你往错的方向猜,一定要答不知道
- 不要说太多跟当前面试题无关的内容,问你什么问题尽量就答什么问题,除非面试官指定你发散一下思维
- 如果没有听懂面试题可以试着询问面试官,您要问的是关于xxx的问题么
- 对于某些问题一定要自己先提前精炼一下(例如作用域链、继承以及原型链等问题)
- 如果面试官问的某项技术自己在某些场景使用过或看到别的场景有使用,可结合这些场景进行讲解(让面试官知道你不仅仅理解它,你还会很好的使用它)
- 如果是Vue技术栈希望可以深入源码或者至少理解一些别人的源码分析
- 如果面试阿里那么面试之前一定要好好准备这样一个问题:你觉得你最擅长什么
- 面试一定要真诚,切勿投机取巧
- 面试态度一定要谦虚
接下来我会按照面试顺序给出面试题以及自己理解的一些答案:
- 大部分答案都是借鉴别人的博客
- 有些答案不一定合理
- 有些答案写的很零散
- 有些答案会举一反三
- 有些题目太基础或者重复了就没有写答案
- 有些题目太宏观或者不知道怎么回答合理,希望大家可以在评论中补充答案供更多的人受益
有赞(一面)¶
说说CSS选择器以及这些选择器的优先级¶
!important
- 内联样式(1000)
- ID选择器(0100)
- 类选择器/属性选择器/伪类选择器(0010)
- 元素选择器/关系选择器/伪元素选择器(0001)
- 通配符选择器(0000)
你知道什么是BFC么¶
小提示:这个问题重点是BFC是什么,BFC触发的条件有哪些,BFC可以干什么。这里我试着讲解了一下Boostrap的清除浮动(display:table创建匿名table-cell间接触发BFC),如果有看到别的场景使用或者自身有使用的场景可以尝试讲解一下使用技巧。这样可以让面试官觉得你不仅仅知道他问的东西是什么,你还能很好的使用它。
什么是BFC¶
BFC 全称为块级格式化上下文 (Block Formatting Context) 。BFC是 W3C CSS 2.1 规范中的一个概念,它决定了元素如何对其内容进行定位以及与其他元素的关系和相互作用,当涉及到可视化布局的时候,Block Formatting Context提供了一个环境,HTML元素在这个环境中按照一定规则进行布局。一个环境中的元素不会影响到其它环境中的布局。比如浮动元素会形成BFC,浮动元素内部子元素的主要受该浮动元素影响,两个浮动元素之间是互不影响的。这里有点类似一个BFC就是一个独立的行政单位的意思。可以说BFC就是一个作用范围,把它理解成是一个独立的容器,并且这个容器里box的布局与这个容器外的box毫不相干。
触发BFC的条件¶
- 根元素或其它包含它的元素
- 浮动元素 (元素的
float
不是none
) - 绝对定位元素 (元素具有
position
为absolute
或fixed
) - 内联块 (元素具有
display: inline-block
) - 表格单元格 (元素具有
display: table-cell
,HTML表格单元格默认属性) - 表格标题 (元素具有
display: table-caption
, HTML表格标题默认属性) - 具有
overflow
且值不是visible
的块元素 - 弹性盒(
flex
或inline-flex
) display: flow-root
column-span: all
BFC的约束规则¶
- 内部的盒会在垂直方向一个接一个排列(可以看作BFC中有一个的常规流)
- 处于同一个BFC中的元素相互影响,可能会发生外边距重叠
- 每个元素的margin box的左边,与容器块border box的左边相接触(对于从左往右的格式化,否则相反),即使存在浮动也是如此
- BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素,反之亦然
- 计算BFC的高度时,考虑BFC所包含的所有元素,连浮动元素也参与计算
- 浮动盒区域不叠加到BFC上
BFC可以解决的问题¶
- 垂直外边距重叠问题
- 去除浮动
- 自适用两列布局(
float
+overflow
)
了解盒模型么¶
包括**内容区域**、内边距区域、边框区域**和**外边距区域。
box-sizing: content-box
(W3C盒子模型):元素的宽高大小表现为**内容**的大小。 box-sizing: border-box
(IE盒子模型):元素的宽高表现为**内容 + 内边距 + 边框**的大小。背景会延伸到边框的外沿。
IE5.x和IE6在怪异模式中使用非标准的盒子模型,这些浏览器的width
属性不是**内容**的宽度,而是**内容**、**内边距**和**边框**的宽度的总和。
如何实现左侧宽度固定,右侧宽度自适应的布局¶
小提示:这个问题面试官会要求说出几种解决方法。
DOM结构
<div class="box">
<div class="box-left"></div>
<div class="box-right"></div>
</div>
利用float + margin
实现¶
.box {
height: 200px;
}
.box > div {
height: 100%;
}
.box-left {
width: 200px;
float: left;
background-color: blue;
}
.box-right {
margin-left: 200px;
background-color: red;
}
利用calc
计算宽度¶
.box {
height: 200px;
}
.box > div {
height: 100%;
}
.box-left {
width: 200px;
float: left;
background-color: blue;
}
.box-right {
width: calc(100% - 200px);
float: right;
background-color: red;
}
利用float + overflow
实现¶
.box {
height: 200px;
}
.box > div {
height: 100%;
}
.box-left {
width: 200px;
float: left;
background-color: blue;
}
.box-right {
overflow: hidden;
background-color: red;
}
利用flex
实现¶
这里不是最佳答案,应该是使用flex-basis
实现更合理
.box {
height: 200px;
display: flex;
}
.box > div {
height: 100%;
}
.box-left {
width: 200px;
background-color: blue;
}
.box-right {
flex: 1; // 设置flex-grow属性为1,默认为0
overflow: hidden;
background-color: red;
}
了解跨域吗,一般什么情况下会导致跨域¶
小提示: 如果平常自身有使用场景可结合使用场景进行讲解,比如我在这里使用过的场景是CORS和Nginx反向代理。
跨域行为¶
- 同源策略限制、安全性考虑
- 协议、IP和端口不一致都是跨域行为
JSONP¶
小提示:如果你提到JSONP,面试官肯定会问你整个详细的实现过程,所以一定要搞懂JSONP的实现原理,如果不是很理解可以自己起一个Express服务实践一下。
Web前端事先定义一个用于获取跨域响应数据的回调函数,并通过没有同源策略限制的script标签发起一个请求(将回调函数的名称放到这个请求的query参数里),然后服务端返回这个回调函数的执行,并将需要响应的数据放到回调函数的参数里,前端的script标签请求到这个执行的回调函数后会立马执行,于是就拿到了执行的响应数据。
缺点: JSONP只能发起GET请求
如何实现一个JSONP¶
这里给出几个链接:
segmentfault.com/a/119000001… zhangguixu.github.io/2016/12/02/…www.cnblogs.com/iovec/p/531…
JSONP安全性问题¶
CSRF攻击¶
前端构造一个恶意页面,请求JSONP接口,收集服务端的敏感信息。如果JSONP接口还涉及一些敏感操作或信息(比如登录、删除等操作),那就更不安全了。
解决方法:验证JSONP的调用来源(Referer),服务端判断Referer是否是白名单,或者部署随机Token来防御。
XSS漏洞¶
不严谨的 content-type导致的 XSS 漏洞,想象一下 JSONP 就是你请求 http://youdomain.com?callback=douniwan
, 然后返回 douniwan({ data })
,那假如请求 http://youdomain.com?callback=alert(1)
不就返回 alert(1)({ data })
了吗,如果没有严格定义好 Content-Type( Content-Type: application/json ),再加上没有过滤 callback
参数,直接当 html 解析了,就是一个赤裸裸的 XSS 了。
解决方法:严格定义 Content-Type: application/json,然后严格过滤 callback
后的参数并且限制长度(进行字符转义,例如<换成<,>换成>)等,这样返回的脚本内容会变成文本格式,脚本将不会执行。
服务器被黑,返回一串恶意执行的代码¶
可以将执行的代码转发到服务端进行校验JSONP内容校验,再返回校验结果。
CORS(跨域资款共享)¶
小提示:如果你回答跨域解决方案CORS,那么面试官一定会问你实现CORS的响应头信息Access-Control-Allow-Origin。
什么是CORS¶
CORS(跨域资源共享 Cross-origin resource sharing)允许浏览器向跨域服务器发出XMLHttpRequest请求,从而克服跨域问题,它需要浏览器和服务器的同时支持。
- 浏览器端会自动向请求头添加origin字段,表明当前请求来源。
- 服务器端需要设置响应头的Access-Control-Allow-Methods,Access-Control-Allow-Headers,Access-Control-Allow-Origin等字段,指定允许的方法,头部,源等信息。
- 请求分为简单请求和非简单请求,非简单请求会先进行一次OPTION方法进行预检,看是否允许当前跨域请求。
简单请求¶
请求方法是以下三种方法之一:
- HEAD
- GET
- POST
HTTP的请求头信息不超出以下几种字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
后端的响应头信息:
- Access-Control-Allow-Origin:该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。
- Access-Control-Allow-Credentials:该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。
- Access-Control-Expose-Headers:该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。
非简单请求¶
非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。
- Access-Control-Request-Method:该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT。
- Access-Control-Request-Headers:该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header。
如果浏览器否定了"预检"请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest对象的onerror回调函数捕获。
JSONP和CORS的对比¶
- JSONP只支持GET请求,CORS支持所有类型的HTTP请求
- JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据
其他跨域解决方案¶
- Nginx反向代理
postMessage
document.domain
HTTP2和HTTP1有什么区别¶
相对于HTTP1.0,HTTP1.1的优化:
- 缓存处理:多了Entity tag,If-Unmodified-Since, If-Match, If-None-Match等缓存信息(HTTTP1.0 If-Modified-Since,Expires)
- 带宽优化及网络连接的使用
- 错误通知的管理
- Host头处理
- 长连接: HTTP1.1中默认开启Connection: keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点。
相对于HTTP1.1,HTTP2的优化:
- HTTP2支持二进制传送(实现方便且健壮),HTTP1.x是字符串传送
- HTTP2支持多路复用
- HTTP2采用HPACK压缩算法压缩头部,减小了传输的体积
- HTTP2支持服务端推送
你能说说缓存么¶
小提示:如果平常有遇到过缓存的坑或者很好的利用缓存,可以讲解一下自己的使用场景。如果没有使用注意过缓存问题你也可以尝试讲解一下和我们息息相关的Webpack构建(每一次构建静态资源名称的hash值都会变化),它其实就跟缓存相关。有兴趣的同学可以查看张云龙的博客大公司里怎样开发和部署前端代码?。
缓存分为强缓存和协商缓存。强缓存不过服务器,协商缓存需要过服务器,协商缓存返回的状态码是304。两类缓存机制可以同时存在,强缓存的优先级高于协商缓存。当执行强缓存时,如若缓存命中,则直接使用缓存数据库中的数据,不再进行缓存协商。
强缓存¶
Expires(HTTP1.0):Exprires的值为服务端返回的数据到期时间。当再次请求时的请求时间小于返回的此时间,则直接使用缓存数据。但由于服务端时间和客户端时间可能有误差,这也将导致缓存命中的误差。另一方面,Expires是HTTP1.0的产物,故现在大多数使用Cache-Control替代。
缺点:使用的是绝对时间,如果服务端和客户端的时间产生偏差,那么会导致命中缓存产生偏差。
Pragma(HTTP1.0):HTTP1.0时的遗留字段,当值为"no-cache"时强制验证缓存,Pragma禁用缓存,如果又给Expires定义一个还未到期的时间,那么Pragma字段的优先级会更高。服务端响应添加'Pragma': 'no-cache',浏览器表现行为和刷新(F5)类似。
Cache-Control(HTTP1.1):有很多属性,不同的属性代表的意义也不同:
- private:客户端可以缓存
- public:客户端和代理服务器都可以缓存
- max-age=t:缓存内容将在t秒后失效
- no-cache:需要使用协商缓存来验证缓存数据
- no-store:所有内容都不会缓存
请注意no-cache指令很多人误以为是不缓存,这是不准确的,no-cache的意思是可以缓存,但每次用应该去想服务器验证缓存是否可用。no-store才是不缓存内容。当在首部字段Cache-Control 有指定 max-age 指令时,比起首部字段 Expires,会优先处理 max-age 指令。命中强缓存的表现形式:Firefox浏览器表现为一个灰色的200状态码。Chrome浏览器状态码表现为200 (from disk cache)或是200 OK (from memory cache)。
协商缓存¶
协商缓存需要进行对比判断是否可以使用缓存。浏览器第一次请求数据时,服务器会将缓存标识与数据一起响应给客户端,客户端将它们备份至缓存中。再次请求时,客户端会将缓存中的标识发送给服务器,服务器根据此标识判断。若未失效,返回304状态码,浏览器拿到此状态码就可以直接使用缓存数据了。
Last-Modified:服务器在响应请求时,会告诉浏览器资源的最后修改时间。
if-Modified-Since:浏览器再次请求服务器的时候,请求头会包含此字段,后面跟着在缓存中获得的最后修改时间。服务端收到此请求头发现有if-Modified-Since,则与被请求资源的最后修改时间进行对比,如果一致则返回304和响应报文头,浏览器只需要从缓存中获取信息即可。
- 如果真的被修改:那么开始传输响应一个整体,服务器返回:200 OK
- 如果没有被修改:那么只需传输响应header,服务器返回:304 Not Modified
if-Unmodified-Since: 从某个时间点算起, 是否文件没有被修改,使用的是相对时间,不需要关心客户端和服务端的时间偏差。
- 如果没有被修改:则开始`继续'传送文件,服务器返回: 200 OK
- 如果文件被修改:则不传输,服务器返回: 412 Precondition failed (预处理错误)
这两个的区别是一个是修改了才下载一个是没修改才下载。如果在服务器上,一个资源被修改了,但其实际内容根本没发生改变,会因为Last-Modified时间匹配不上而返回了整个实体给客户端(即使客户端缓存里有个一模一样的资源)。为了解决这个问题,HTTP1.1推出了Etag。
Etag:服务器响应请求时,通过此字段告诉浏览器当前资源在服务器生成的唯一标识(生成规则由服务器决定)
If-Match:条件请求,携带上一次请求中资源的ETag,服务器根据这个字段判断文件是否有新的修改
If-None-Match: 再次请求服务器时,浏览器的请求报文头部会包含此字段,后面的值为在缓存中获取的标识。服务器接收到次报文后发现If-None-Match则与被请求资源的唯一标识进行对比。
- 不同,说明资源被改动过,则响应整个资源内容,返回状态码200。
- 相同,说明资源无心修改,则响应header,浏览器直接从缓存中获取数据信息。返回状态码304.
但是实际应用中由于Etag的计算是使用算法来得出的,而算法会占用服务端计算的资源,所有服务端的资源都是宝贵的,所以就很少使用Etag了。
- 浏览器地址栏中写入URL,回车浏览器发现缓存中有这个文件了,不用继续请求了,直接去缓存拿(最快)
- F5就是告诉浏览器,别偷懒,好歹去服务器看看这个文件是否有过期了。于是浏览器就胆胆襟襟的发送一个请求带上If-Modify-since
- Ctrl+F5告诉浏览器,你先把你缓存中的这个文件给我删了,然后再去服务器请求个完整的资源文件下来。于是客户端就完成了强行更新的操作
缓存场景¶
对于大部分的场景都可以使用强缓存配合协商缓存解决,但是在一些特殊的地方可能需要选择特殊的缓存策略
- 对于某些不需要缓存的资源,可以使用 Cache-control: no-store ,表示该资源不需要缓存
- 对于频繁变动的资源,可以使用 Cache-Control: no-cache 并配合 ETag 使用,表示该资源已被缓存,但是每次都会发送请求询问资源是否更新
- 对于代码文件来说,通常使用 Cache-Control: max-age=31536000 并配合策略缓存使用,然后对文件进行指纹处理,一旦文件名变动就会立刻下载新的文件
能说说首屏加载优化有哪些方案么¶
小提示:如果做过类似优化的同学,可能就比较好回答,没有做过类似优化的同学可以重点讲解一下懒加载(当然我这里被面试官追问过懒加载的Webpack配置问题)。同时不知道使用Vue技术栈的同学们有没有仔细观察过Vue CLI 3构建的html文件中的link标签的rel属性。
- Vue-Router路由懒加载(利用Webpack的代码切割)
- 使用CDN加速,将通用的库从vendor进行抽离
- Nginx的gzip压缩
- Vue异步组件
- 服务端渲染SSR
- 如果使用了一些UI库,采用按需加载
- Webpack开启gzip压缩
- 如果首屏为登录页,可以做成多入口
- Service Worker缓存文件处理
- 使用link标签的rel属性设置 prefetch(这段资源将会在未来某个导航或者功能要用到,但是本资源的下载顺序权重比较低,prefetch通常用于加速下一次导航)、preload(preload将会把资源得下载顺序权重提高,使得关键数据提前下载好,优化页面打开速度)
如何在Node端配置路径别名(类似于Webpack中的alias配置)¶
- 全局变量
- 环境变量
- 自己HACK一个@符号,指向特定的路径
- HACK
require
方法
参考¶
这种问题还是附上参考链接
segmentfault.com/a/119000001… chashaobao.net/2017/09/03/…www.zhihu.com/question/26…
谈谈你对作用域链的理解¶
小提示:同类型的问题还可以是原型链、继承、闭包等,这种概念性的问题你肯定不是一句两句能说清楚的,建议在理解之后自己尝试总结一下,如何把重要的知识点用简短的话语说明白。
了解作用域链之前我们要知道一下几个概念:
- 函数的生命周期
- 变量和函数的声明
- Activetion Object(AO)、Variable Object(VO)
函数的生命周期:
- 创建:JS解析引擎进行预解析,会将函数声明提前,同时将该函数放到全局作用域中或当前函数的上一级函数的局部作用域中。
- 执行:JS引擎会将当前函数的局部变量和内部函数进行声明提前,然后再执行业务代码,当函数执行完退出时,释放该函数的执行上下文,并注销该函数的局部变量。
变量和函数的声明:如果变量名和函数名声明时相同,函数优先声明。
Activetion Object(AO)、Variable Object(VO):
- AO:Activetion Object(活动对象)
- VO:Variable Object(变量对象)
VO对应的是函数创建阶段,JS解析引擎进行预解析时,所有的变量和函数的声明,统称为Variable Object。该变量与执行上下文相关,知道自己的数据存储在哪里,并且知道如何访问。VO是一个与执行上下文相关的特殊对象,它存储着在上下文中声明的以下内容:
- 变量 (var, 变量声明);
- 函数声明 (FunctionDeclaration, 缩写为FD);
- 函数的形参
AO对应的是函数执行阶段,当函数被调用执行时,会建立一个执行上下文,该执行上下文包含了函数所需的所有变量,该变量共同组成了一个新的对象就是Activetion Object。该对象包含了:
- 函数的所有局部变量
- 函数的所有命名参数
- 函数的参数集合
- 函数的this指向
作用域链:
当代码在一个环境中创建时,会创建变量对象的一个作用域链(scope chain)来保证对执行环境有权访问的变量和函数。作用域第一个对象始终是当前执行代码所在环境的变量对象(VO)。如果是函数执行阶段,那么将其activation object(AO)作为作用域链第一个对象,第二个对象是上级函数的执行上下文AO,下一个对象依次类推。
在《JavaScript深入之变量对象》中讲到,当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。
你知道null
和undefined
有什么区别么¶
闭包有什么作用¶
Vue响应式原理¶
小提示:如果面试者使用的是Vue技术栈,那么响应式原理是一个必问的问题,同时面试官经常也会问Vue 3.0在响应式原理上的优化方案。
如果对于响应式原理不是很清楚可以查看我之前写的文章基于Vue实现一个简易MVVM/数据劫持的实现。
了解Event Loop么¶
小提示:这个题目问到的概率还是蛮大的,这里面试官询问了我浏览器端和Node端的Event Loop有什么不同点。如果想要知道更多浏览器端的Event Loop机制可以查看我之前写的文章你真的理解$nextTick么/JS引擎线程和事件触发线程/事件循环机制。
事件触发线程管理的任务队列是如何产生的呢?事实上这些任务就是从JS引擎线程本身产生的,主线程在运行时会产生执行栈,栈中的代码调用某些异步API时会在任务队列中添加事件,栈中的代码执行完毕后,就会读取任务队列中的事件,去执行事件对应的回调函数,如此循环往复,形成事件循环机制。JS中有两种任务类型:微任务(microtask)和宏任务(macrotask),在ES6中,microtask称为 jobs,macrotask称为 task:
- 宏任务: script (主代码块)、
setTimeout
、setInterval
、setImmediate
、I/O 、UI rendering - 微任务:
process.nextTick
(Nodejs) 、Promise
、Object.observe
、MutationObserver
Node.js中Event Loop和浏览器中Event Loop有什么区别
┌───────────────────────┐
┌─>│ timers │<————— 执行 setTimeout()、setInterval() 的回调
│ └──────────┬────────────┘
| |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
│ ┌──────────┴────────────┐
│ │ pending callbacks │<————— 执行由上一个 Tick 延迟下来的 I/O 回调(待完善,可忽略)
│ └──────────┬────────────┘
| |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
│ ┌──────────┴────────────┐
│ │ idle, prepare │<————— 内部调用(可忽略)
│ └──────────┬────────────┘
| |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
| | ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │ - (执行几乎所有的回调,除了 close callbacks、timers、setImmediate)
│ │ poll │<─────┤ connections, │
│ └──────────┬────────────┘ │ data, etc. │
│ | | |
| | └───────────────┘
| |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
| ┌──────────┴────────────┐
│ │ check │<————— setImmediate() 的回调将会在这个阶段执行
│ └──────────┬────────────┘
| |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
│ ┌──────────┴────────────┐
└──┤ close callbacks │<————— socket.on('close', ...)
└───────────────────────┘
Node.js中宏任务分成了几种类型,并且放在了不同的task queue里。不同的task queue在执行顺序上也有区别,微任务放在了每个task queue的末尾:
setTimeout/setInterval
属于 timers 类型;setImmediate
属于 check 类型;- socket 的 close 事件属于 close callbacks 类型;
- 其他 MacroTask 都属于 poll 类型。
process.nextTick
本质上属于 MicroTask,但是它先于所有其他 MicroTask 执行;- 所有 MicroTask 的执行时机在不同类型的 MacroTask 切换后。
- idle/prepare 仅供内部调用,我们可以忽略。
- pending callbacks 不太常见,我们也可以忽略。
如何避免回流和重绘¶
浏览器渲染过程¶
- 浏览器使用流式布局模型 (Flow Based Layout)
- 浏览器会把HTML解析成DOM,把CSS解析成CSSOM,DOM和CSSOM合并就产生了Render Tree
- 有了RenderTree就能知道所有节点的样式,计算节点在页面上的大小和位置,把节点绘制到页面上
- 由于浏览器使用流式布局,对Render Tree的计算通常只需要遍历一次就可以完成,但table及其内部元素除外,通常需要多次计算且要花费3倍于同等元素的时间,这也是为什么要避免使用table布局的原因之一
浏览器渲染过程如下:
- 解析HTML,生成DOM树
- 解析CSS,生成CSSOM树
- 将DOM树和CSSOM树结合,生成渲染树(Render Tree)
- Layout(回流):根据生成的渲染树,进行回流(Layout),得到节点的几何信息(位置,大小)
- Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素
- Display:将像素发送给GPU,展示在页面上。(这一步其实还有很多内容,比如会在GPU将多个合成层合并为同一个层,并展示在页面中。而css3硬件加速的原理则是新建合成层,这里我们不展开,之后有机会会写一篇博客)
何时触发回流和重绘¶
何时发生回流:
- 添加或删除可见的DOM元素
- 元素的位置发生变化
- 元素的尺寸发生变化(包括外边距、内边框、边框大小、高度和宽度等)
- 内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代。
- 页面一开始渲染的时候(这肯定避免不了)
- 浏览器的窗口尺寸变化(因为回流是根据视口的大小来计算元素的位置和大小的)
何时发生重绘(回流一定会触发重绘):
当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘。
有时即使仅仅回流一个单一的元素,它的父元素以及任何跟随它的元素也会产生回流。现代浏览器会对频繁的回流或重绘操作进行优化,浏览器会维护一个队列,把所有引起回流和重绘的操作放入队列中,如果队列中的任务数量或者时间间隔达到一个阈值的,浏览器就会将队列清空,进行一次批处理,这样可以把多次回流和重绘变成一次。你访问以下属性或方法时,浏览器会立刻清空队列:
clientWidth
、clientHeight
、clientTop
、clientLeft
offsetWidth
、offsetHeight
、offsetTop
、offsetLeft
scrollWidth
、scrollHeight
、scrollTop
、scrollLeft
width
、height
getComputedStyle()
getBoundingClientRect()
以上属性和方法都需要返回最新的布局信息,因此浏览器不得不清空队列,触发回流重绘来返回正确的值。因此,我们在修改样式的时候,**最好避免使用上面列出的属性,他们都会刷新渲染队列。**如果要使用它们,最好将值缓存起来。
如何避免触发回流和重绘¶
CSS:
- 避免使用table布局。
- 尽可能在DOM树的最末端改变class。
- 避免设置多层内联样式。
- 将动画效果应用到
position
属性为absolute
或fixed
的元素上 - 避免使用CSS表达式(例如:
calc()
) - CSS3硬件加速(GPU加速)
JavaScript:
- 避免频繁操作样式,最好一次性重写style属性,或者将样式列表定义为class并一次性更改class属性
- 避免频繁操作DOM,创建一个
documentFragment
,在它上面应用所有DOM操作,最后再把它添加到文档中 - 也可以先为元素设置
display: none
,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘 - 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来
- 对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流
有赞(二面)¶
小提示:进入现场面试需要注意好好准备自己的简历,面试官一般会根据项目进行问答。
笔试题环节¶
一开始面试官就发了两张笔试题试卷,总共四道题目,大致考了以下知识点:
- 作用域
- 原型链(例如实例属性和原型属性一样,删除实例属性后可以继续访问原型属性问题)
- 宏任务和微任务的打印顺序
Array.prototype.map
的第二个参数
项目问答环节¶
答完试卷面试官就开始问简历上的一些项目,我记得其中几个问题如下(事实上他问的一些问题和简历不是很相关):
- 你们产品的服务器部署在哪里
- 你是如何实现一个Tooltip组件的,能写一下怎么使用这个组件么(这算什么问题...)
- 我认识你们海康的一些开发,我知道你们的产品按套数卖的...
我当场就感受到了面试官问的问题很敷衍,可能他觉得我的简历不够好,又或者觉得我能力不行,接下来面试官又让我做了一道算法题...
算法题环节¶
- 1块、4块、5块,求总数n块的最小硬币数
当时没做出来,非科班出身可能做这些确实有些困难,也没有系统的学习,面试官看我很困难的样子,就换了一道题。
- 1、1、2、3、5、8...计算第n个数的值(斐波那契数列)
这道题还是做出来了,毕竟比较简单,然后面试官说今天先到这里,面试结果会在一星期内通知,然后回来的那天晚上就收到了面试没过的通知。
小结¶
还是蛮感谢这次现场面试的经历,让我知道如果自身不够硬,到哪里都会很被动。面试的好处不仅仅在于检验自己到底有多少能力,更应该发现自身的不足,同时不断的去弥补这些不足。于是我再次捧起之前搁置的《算法导论》,并且创建了一个算法学习演示文档I-Algorithms,希望可以简化《算法导论》的一些理论知识,使大家对于算法的学习可以变得更加系统全面和简单,也希望通过这个学习使得算法面试会变得更加得心应手,希望感兴趣的同学可以star一下。
滴滴(一面)¶
你知道哪些安全问题,如何避免¶
小提示:这里我简单讲解了一下Vue中的
v-html
防范XSS攻击。
XSS(跨站脚本攻击)¶
XSS,即 Cross Site Script,中译是跨站脚本攻击;其原本缩写是 CSS,但为了和层叠样式表(Cascading Style Sheet)有所区分,因而在安全领域叫做 XSS。
XSS 攻击是指攻击者在网站上注入恶意的客户端代码,通过恶意脚本对客户端网页进行篡改,从而在用户浏览网页时,对用户浏览器进行控制或者获取用户隐私数据的一种攻击方式。
攻击者对客户端网页注入的恶意脚本一般包括 JavaScript,有时也会包含 HTML 和 Flash。有很多种方式进行 XSS 攻击,但它们的共同点为:将一些隐私数据像 cookie、session 发送给攻击者,将受害者重定向到一个由攻击者控制的网站,在受害者的机器上进行一些恶意操作。
XSS攻击可以分为3类:反射型(非持久型)、存储型(持久型)、基于DOM。
反射型¶
反射型 XSS 只是简单地把用户输入的数据 “反射” 给浏览器,这种攻击方式往往需要攻击者诱使用户点击一个恶意链接(攻击者可以将恶意链接直接发送给受信任用户,发送的方式有很多种,比如 email, 网站的私信、评论等,攻击者可以购买存在漏洞网站的广告,将恶意链接插入在广告的链接中),或者提交一个表单,或者进入一个恶意网站时,注入脚本进入被攻击者的网站。最简单的示例是访问一个链接,服务端返回一个可执行脚本:
const http = require('http');
function handleReequest(req, res) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.writeHead(200, {'Content-Type': 'text/html; charset=UTF-8'});
res.write('<script>alert("反射型 XSS 攻击")</script>');
res.end();
}
const server = new http.Server();
server.listen(8001, '127.0.0.1');
server.on('request', handleReequest);
存储型¶
存储型 XSS 会把用户输入的数据 "存储" 在服务器端,当浏览器请求数据时,脚本从服务器上传回并执行。这种 XSS 攻击具有很强的稳定性。比较常见的一个场景是攻击者在社区或论坛上写下一篇包含恶意 JavaScript 代码的文章或评论,文章或评论发表后,所有访问该文章或评论的用户,都会在他们的浏览器中执行这段恶意的 JavaScript 代码:
// 例如在评论中输入以下留言
// 如果请求这段留言的时候服务端不做转义处理,请求之后页面会执行这段恶意代码
<script>alert('xss 攻击')</script>
基于DOM¶
基于 DOM 的 XSS 攻击是指通过恶意脚本修改页面的 DOM 结构,是纯粹发生在客户端的攻击:
<h2>XSS: </h2>
<input type="text" id="input">
<button id="btn">Submit</button>
<div id="div"></div>
<script>
const input = document.getElementById('input');
const btn = document.getElementById('btn');
const div = document.getElementById('div');
let val;
input.addEventListener('change', (e) => {
val = e.target.value;
}, false);
btn.addEventListener('click', () => {
div.innerHTML = `<a href=${val}>testLink</a>`
}, false);
</script>
点击 Submit 按钮后,会在当前页面插入一个链接,其地址为用户的输入内容。如果用户在输入时构造了如下内容:
'' onclick=alert(/xss/)
用户提交之后,页面代码就变成了:
<a href onlick="alert(/xss/)">testLink</a>
此时,用户点击生成的链接,就会执行对应的脚本。
XSS攻击防范¶
HttpOnly 防止劫取 Cookie:HttpOnly 最早由微软提出,至今已经成为一个标准。浏览器将禁止页面的Javascript 访问带有 HttpOnly 属性的Cookie。上文有说到,攻击者可以通过注入恶意脚本获取用户的 Cookie 信息。通常 Cookie 中都包含了用户的登录凭证信息,攻击者在获取到 Cookie 之后,则可以发起 Cookie 劫持攻击。所以,严格来说,HttpOnly 并非阻止 XSS 攻击,而是能阻止 XSS 攻击后的 Cookie 劫持攻击。
输入检查:不要相信用户的任何输入。 对于用户的任何输入要进行检查、过滤和转义。建立可信任的字符和 HTML 标签白名单,对于不在白名单之列的字符或者标签进行过滤或编码。在 XSS 防御中,输入检查一般是检查用户输入的数据中是否包含 <,> 等特殊字符,如果存在,则对特殊字符进行过滤或编码,这种方式也称为 XSS Filter。而在一些前端框架中,都会有一份 decodingMap, 用于对用户输入所包含的特殊字符或标签进行编码或过滤,如 <,>,script,防止 XSS 攻击:
// vuejs 中的 decodingMap
// 在 vuejs 中,如果输入带 script 标签的内容,会直接过滤掉
const decodingMap = {
'<': '<',
'>': '>',
'"': '"',
'&': '&',
' ': '\n'
}
输出检查:用户的输入会存在问题,服务端的输出也会存在问题。一般来说,除富文本的输出外,在变量输出到 HTML 页面时,可以使用编码或转义的方式来防御 XSS 攻击。例如利用 sanitize-html 对输出内容进行有规则的过滤之后再输出到页面中。
CSRF/XSRF(跨站请求伪造)¶
CSRF,即 Cross Site Request Forgery,中译是跨站请求伪造,是一种劫持受信任用户向服务器发送非预期请求的攻击方式。通常情况下,CSRF 攻击是攻击者借助受害者的 Cookie 骗取服务器的信任,可以在受害者毫不知情的情况下以受害者名义伪造请求发送给受攻击服务器,从而在并未授权的情况下执行在权限保护之下的操作。
Cookie¶
Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。Cookie 主要用于以下三个方面:
- 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
- 个性化设置(如用户自定义设置、主题等)
- 浏览器行为跟踪(如跟踪分析用户行为等)
而浏览器所持有的 Cookie 分为两种:
- Session Cookie(会话期 Cookie):会话期 Cookie 是最简单的Cookie,它不需要指定过期时间(Expires)或者有效期(Max-Age),它仅在会话期内有效,浏览器关闭之后它会被自动删除。
- Permanent Cookie(持久性 Cookie):与会话期 Cookie 不同的是,持久性 Cookie 可以指定一个特定的过期时间(Expires)或有效期(Max-Age)。
res.setHeader('Set-Cookie', ['mycookie=222', 'test=3333; expires=Sat, 21 Jul 2018 00:00:00 GMT;']);
上述代码创建了两个 Cookie:mycookie 和 test,前者属于会话期 Cookie,后者则属于持久性 Cookie。
CSRF攻击¶
使登录用户访问攻击者的网站,发起一个请求,由于 Cookie 中包含了用户的认证信息,当用户访问攻击者准备的攻击环境时,攻击者就可以对服务器发起 CSRF 攻击。
在这个攻击过程中,攻击者借助受害者的 Cookie 骗取服务器的信任,但并不能拿到 Cookie,也看不到 Cookie 的内容。而对于服务器返回的结果,由于浏览器同源策略的限制,攻击者也无法进行解析。(攻击者的网站虽然是跨域的,但是他构造的链接是源网站的,跟源网站是同源的,所以能够携带cookie发起访问)。
但是攻击者无法从返回的结果中得到任何东西,他所能做的就是给服务器发送请求,以执行请求中所描述的命令,在服务器端直接改变数据的值,而非窃取服务器中的数据。例如删除数据、修改数据,新增数据等,无法获取数据。
CSRF攻击防范¶
验证码:验证码被认为是对抗 CSRF 攻击最简洁而有效的防御方法。从上述示例中可以看出,CSRF 攻击往往是在用户不知情的情况下构造了网络请求。而验证码会强制用户必须与应用进行交互,才能完成最终请求。因为通常情况下,验证码能够很好地遏制 CSRF 攻击。但验证码并不是万能的,因为出于用户考虑,不能给网站所有的操作都加上验证码。因此,验证码只能作为防御 CSRF 的一种辅助手段,而不能作为最主要的解决方案。
Referer Check:根据 HTTP 协议,在 HTTP 头中有一个字段叫 Referer,它记录了该 HTTP 请求的来源地址。通过 Referer Check,可以检查请求是否来自合法的"源"。
添加token验证:要抵御 CSRF,关键在于在请求中放入攻击者所不能伪造的信息,并且该信息不存在于 Cookie 之中。可以在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。
介绍一下Graphql¶
小提示:这道题是给自己挖了一个坑,抱着学习的心态尝试使用Graphql技术,却没有好好理解是在什么场景下为了解决什么问题才应该使用,也没有好好准备如何描述新技术,往往这种不熟悉的技术自己在简历中应该留存一些心眼,尽量不要提,否则答不上来会很尴尬,让面试官怀疑你的项目成分。
什么是Graphql¶
GraphQL是一种API查询语言。API接口的返回值可以从静态变为动态,即调用者来声明接口返回什么数据,可以进一步解耦前后端。在Graphal中,预先定义Schema和声明Type来达到动态获取接口数据的目的:
- 对于数据模型的抽象是通过Type来描述的
- 对于接口获取数据的逻辑是通过Schema来描述的
为什么要使用Graphql:¶
- 接口数量众多维护成本高
- 接口扩展成本高
- 接口响应的数据格式无法预知
- 减少无用数据的请求, 按需获取
- 强类型约束(API的数据格式让前端来定义,而不是后端定义)
Type(数据模型的抽象)¶
Type简单可以分为两种,一种叫做Scalar Type(标量类型),另一种叫做Object Type(对象类型):
- Scalar Type(标量类型):内建的标量包含,String、Int、Float、Boolean、Enum
- Object Type(对象类型):感觉类似于TypeScript的接口类型
- Type Modifier(类型修饰符):用于表明是否必填等
Schema(模式)¶
定义了字段的类型、数据的结构,描述了接口数据请求的规则
Query(查询、操作类型)¶
查询类型: query(查询)、mutation(更改)和subscription(订阅)
- query(查询):当获取数据时,应当选取Query类型
- mutation(更改):当尝试修改数据时,应当使用mutation类型
- subscription(订阅):当希望数据更改时,可以进行消息推送,使用subscription类型
Resolver(解析函数)¶
提供相关Query所返回数据的逻辑。Query和与之对应的Resolver是同名的,这样在GraphQL才能把它们对应起来。解析的过程可能是递归的,只要遇到非标量类型,会尝试继续解析,如果遇到标量类型,那么解析完成,这个过程叫做解析链。
说说Vue中$nextTick
的实现原理¶
小提示:如果面试者使用的是Vue技术栈,那么
$nextTick
的原理是一个高频问题,面试者借此可以追问的东西较多,例如浏览器的Event Loop、微任务和宏任务、Node.js的Event Loop、异步更新DOM(响应式的数据for循环改变了1000次为什么视图只更新了一次)、$nextTick
历史版本问题等等。
这个如果不是很清楚的具体可查看我之前写的文章你真的理解$nextTick么。
Vue响应式原理¶
谈谈对闭包的理解¶
JSONP的实现原理¶
CSS中的BFC¶
如何实现居中¶
水平居中¶
- 若是行内元素,给其父元素设置
text-align:center
即可实现行内元素水平居中 - 若是块级元素,该元素设置
margin:0 auto
即可(元素需要定宽) - 若是块级元素,设置父元素为flex布局,子元素设置
margin:0 auto
即可(子元素不需要定宽) - 使用flex 2012年版本布局,可以轻松的实现水平居中,子元素设置如下:
// flex容器
<div class="box">
// flex项目
<div class="box-center">
</div>
</div>
.box {
width: 200px;
height: 200px;
display: flex;
// 使内部的flex项目水平居中
justify-content: center;
background-color: pink;
}
/* .box-center {
width: 50%;
background-color: greenyellow;
} */
- 使用绝对定位和CSS3新增的属性
transform
(这个属性还和GPU硬件加速、固定定位相关)
.box {
width: 200px;
height: 200px;
position: relative;
background-color: pink;
}
.box-center {
position: absolute;
left:50%;
// width: 50%;
height: 100%;
// 通过 translate() 方法,元素从其当前位置移动,根据给定的 left(x 坐标) 和 top(y 坐标) 位置参数:
// translate(x,y) 定义 2D 转换。
// translateX(x) 定义转换,只是用 X 轴的值。
// translateY(y) 定义转换,只是用 Y 轴的值。
// left: 50% 先整体向父容器的左侧偏移50%,此时是不能居中的,因为元素本身有大小
// 接着使用transform使用百分比向左偏移本身的宽度的一半实现水平居中(这里的百分比以元素本身的宽高为基准)
transform:translate(-50%,0);
background-color: greenyellow;
}
- 使用绝对定位和
margin-left
(元素定宽)
.box {
width: 200px;
height: 200px;
position: relative;
background-color: pink;
}
.box-center {
position: absolute;
left:50%;
height: 100%;
// 类似于transform
// width: 50%;
// margin-left: -25%;
width: 100px;
margin-left: -50px;
background-color: greenyellow;
}
垂直居中¶
- 若元素是单行文本, 则可设置
line-height
等于父元素高度 - 若是块级元素,设置父元素为flex布局,子元素设置
margin: auto 0
即可(子元素不需要定宽) - 若元素是行内块级元素,基本思想是使用
display: inline-block, vertical-align: middle
和一个伪元素让内容块处于容器中央:
.box {
height: 100px;
}
.box::after, .box-center{
display:inline-block;
vertical-align:middle;
}
.box::after{
content:'';
height:100%;
}
居中元素高度不定¶
- 可用
vertical-align
属性(vertical-align
只有在父层为 td 或者 th 时才会生效,,对于其他块级元素,例如 div、p 等,默认情况是不支持的),为了使用vertical-align
,我们需要设置父元素display:table
, 子元素display:table-cell;vertical-align:middle
:
.box {
height: 100px;
display: table;
}
.box-center{
display: table-cell;
vertical-align:middle;
}
- 可用 Flex 2012版, 这是CSS布局未来的趋势。Flexbox是CSS3新增属性,设计初衷是为了解决像垂直居中这样的常见布局问题:
.box {
height: 100px;
display: flex;
align-items: center;
}
优点:内容块的宽高任意, 优雅的溢出. 可用于更复杂高级的布局技术中. 缺点:IE8/IE9不支持、需要浏览器厂商前缀、渲染上可能会有一些问题。
- 可用
transform
,设置父元素相对定位:
.box {
height: 100px;
position: relative;
background-color: pink;
}
.box-center {
position: absolute;
top: 50%;
transform: translate(0, -50%);
background-color: greenyellow;
}
缺点:IE8不支持, 属性需要追加浏览器厂商前缀,可能干扰其他 transform
效果,某些情形下会出现文本或元素边界渲染模糊的现象。
居中元素高度固定¶
- 设置父元素相对定位,子元素如下css样式:
.box {
position:relative;
height: 100px;
background-color: pink;
}
.box-center{
position:absolute;
top:50%;
// 注意不能使用百分比
// margin的百分比计算是相对于父容器的width来计算的,甚至包括margin-top和margin-bottom
height: 50px;
margin-top: -25px;
}
- 设置父元素相对定位, 子元素如下css样式:
.box {
position:relative;
width: 200px;
height: 200px;
background-color: pink;
}
.box-center{
position:absolute;
top: 0;
bottom: 0;
margin: auto 0;
height: 100px;
background-color: greenyellow;
}
水平垂直居中¶
- Flex布局(子元素是块级元素)
.box {
display: flex;
width: 100px;
height: 100px;
background-color: pink;
}
.box-center{
margin: auto;
background-color: greenyellow;
}
- Flex布局
.box {
display: flex;
width: 100px;
height: 100px;
background-color: pink;
justify-content: center;
align-items: center;
}
.box-center{
background-color: greenyellow;
}
- 绝对定位实现(定位元素定宽定高)
.box {
position: relative;
height: 100px;
width: 100px;
background-color: pink;
}
.box-center{
position: absolute;
left: 0;
right: 0;
bottom: 0;
top: 0;
margin: auto;
width: 50px;
height: 50px;
background-color: greenyellow;
}
用过Flex么,能简单介绍一下么¶
小提示:如果在项目中使用过,可简单介绍一下自己使用Flex解决过什么问题,这里我在项目中印象比较深刻的是使用Flex解决上面内容高度不固定,下面内容高度自动撑满父容器剩余高度的问题。
如果不是很清楚Flex,可以查看阮一峰的文章Flex 布局教程:语法篇。面试官追问,那么除了Flex,你还知道Grid么?这个由于兼容性问题,我一直没有好好研究过,这里可查看阮一峰的文章CSS Grid网格布局教程。
bind
的源码实现¶
小提示:这里我回答使用函数柯里化加上
apply
或者call
可实现bind
,面试官追问了一些具体的实现细节。
后来我自己粗糙的实现了一下,仅供参考:
Function.prototype.myCall = function (obj) {
obj.fn = this
let args = [...arguments].splice(1)
let result = obj.fn(...args)
delete obj.fn
return result
}
Function.prototype.myApply = function (obj) {
obj.fn = this
let args = arguments[1]
let result
if (args) {
result = obj.fn(...args)
} else {
result = obj.fn()
}
delete obj.fn
return result
}
Function.prototype.myBind = function (obj) {
let context = obj || window
let _this = this
let _args = [...arguments].splice(1)
return function () {
let args = arguments
// 产生副作用
// return obj.fn(..._args, ...args)
return _this.apply(context, [..._args, ...args])
}
}
function myFun (argumentA, argumentB) {
console.log(this.value)
console.log(argumentA)
console.log(argumentB)
return this.value
}
let obj = {
value: 'ziyi2'
}
console.log(myFun.myCall(obj, 11, 22))
console.log(myFun.myApply(obj, [11, 22]))
console.log(myFun.myBind(obj, 33)(11, 22))
伪类和伪元素的区别¶
小提示:这个问题我当时懵了一下,一下子没反应过来面试官想要问什么,就答了这两者在CSS优先级上有区别,然后由于遇到不会的问题有些紧张就多说了一些废话,但显然这不是面试官想要的答案并且消耗了面试官面试的耐心,说他问的不是这个。这里再次提示大家,如果你感觉你说不清楚,但是你又知道一点,我建议你说不知道,不要纠结,面试官不会因为你不知道一个问题就PASS你,相反你说了一些无关紧要的废话,反而在消耗面试官的耐性,增加负面印象。
伪类和伪元素是用来修饰不在文档树中的部分,比如,一句话中的第一个字母,或者是列表中的第一个元素。下面分别对伪类和伪元素进行解释:
伪类用于当已有元素处于的某个状态时,为其添加对应的样式,这个状态是根据用户行为而动态变化的。比如说,当用户悬停在指定的元素时,我们可以通过:hover来描述这个元素的状态。虽然它和普通的css类相似,可以为已有的元素添加样式,但是它只有处于dom树无法描述的状态下才能为元素添加样式,所以将其称为伪类。
伪元素用于创建一些不在文档树中的元素,并为其添加样式。比如说,我们可以通过:before来在一个元素前增加一些文本,并为这些文本添加样式。虽然用户可以看到这些文本,但是这些文本实际上不在文档树中。
区别¶
伪类的操作对象是文档树中已有的元素,而伪元素则创建了一个文档树外的元素。因此,伪类与伪元素的区别在于:有没有创建一个文档树之外的元素。
CSS3规范中的要求使用双冒号(::)表示伪元素,以此来区分伪元素和伪类,比如::before和::after等伪元素使用双冒号(::),:hover和:active等伪类使用单冒号(:)。除了一些低于IE8版本的浏览器外,大部分浏览器都支持伪元素的双冒号(::)表示方法。
小结¶
对于滴滴的这次面试,我感觉到自己准备的不是很充分,尤其是自己简历上的项目技术Graphql。同时对于自己不会的题目强行做了一些解释说明,其实应该简洁明了的告诉面试官不会。
51信用卡(一面)¶
说说DOM事件流¶
在ES5中如何实现继承¶
小提示:这里我说了很多,从借用构造函数到组合继承到寄生组合继承,但面试官其实最想听到的是寄生组合继承。面试官还追问我具体要如何实现寄生组合继承。当然这里其实问的问题还可以很多,比如ES6的类继承和ES5中的继承有什么区别。
如果对于继承以及继承的区别不是很清楚的,可以随便看看我之前写的大笔记js类和继承。
绝对定位¶
小提示:这个建议大家好好回忆一下,例如子元素是相对父元素的padding、border还是content进行定位之类的,当时面试官问的就这么细。
消抖和节流¶
小提示:面试官只是问了一下具体的使用场景,没有问实现原理。
简单消抖¶
function debounce (fn, wait = 1000) {
let timeOutId
return function () {
let context = this
if (timeOutId) {
clearTimeout(timeOutId)
}
timeOutId = setTimeout(() => {
fn.apply(context, arguments)
}, wait)
}
}
带立即执行参数的消抖¶
function debounceImmediate (fn, wait = 1000, immediate) {
let timeOutId, context, args
const later = (immediate) => setTimeout(() => {
if (!immediate) {
fn.apply(context, args)
timeOutId = context = args = null
}
}, wait)
return function () {
if (!timeOutId) {
timeOutId = later(true)
if (immediate) {
fn.apply(this, arguments)
}
context = this
args = arguments
} else {
clearTimeout(timeOutId)
timeOutId = later(false)
}
}
}
节流¶
function throttle (fn, wait) {
let timeoutId = null
return function () {
let context = this
if (!timeoutId) {
timeoutId = setTimeout(() => {
fn.apply(context, arguments)
timeoutId = null
}, wait)
}
}
}
Vue中的computed实现原理¶
小提示:这个问题面试官问的很细,绝对是想问你是否阅读过源码。他首先问computed的实现原理,其次问了这样一个问题:现在有两个computed计算值,其中一个computed计算值为什么可以依赖另外一个computed计算值。这里顺便将watch的实现原理也贴上。
watch的实现原理¶
watch的分类:
- deep watch(深层次监听)
- user watch(用户监听)
- computed watcher(计算属性)
- sync watcher(同步监听)
watch实现过程:
- watch的初始化在data初始化之后(此时的data已经通过
Object.defineProperty
的设置成响应式) - watch的key会在Watcher里进行值的读取,也就是立马执行get获取value(从而实现data对应的key执行getter实现对于watch的依赖收集),此时如果有
immediate
属性那么立马执行watch对应的回调函数 - 当data对应的key发生变化时,触发user watch实现watch回调函数的执行
computed运行原理¶
- computed的属性是动态挂载到vm实例上的,和普通的响应式数据在data里声明不同
- 设置computed的getter,如果执行了computed对应的函数,由于函数会读取data属性值,因此又会触发data属性值的getter函数,在这个执行过程中就可以处理computed相对于data的依赖收集关系了
- 首次计算computed的值时,会执行vm.computed属性对应的getter函数(用户指定的computed函数,如果没有设置getter,那么将当前指定的函数赋值computed属性的getter),进行上述的依赖收集
- 如果computed的属性值又依赖了其他computed计算属性值,那么会将当前target暂存到栈中,先进行其他computed计算属性值的依赖收集,等其他计算属性依赖收集完成后,在从栈中pop出来,继续进行当前computed的依赖收集
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
})
由于 this.firstName
和 this.lastName
(上面是Vue官方示例)都是响应式变量,因此会触发它们的 getter,根据我们之前的分析,它们会把自身持有的 dep 添加到当前正在计算的 watcher 中,这个时候Dep.target
就是这个 computed watcher,具体步骤如下:
- data 属性初始化 getter setter
- computed 计算属性初始化,提供的函数将用作属性
vm.fullName
的 getter - 当首次获取
fullName
计算属性的值时,Dep 开始依赖收集 - 在执行 message getter 方法时,如果 Dep 处于依赖收集状态,则判定
firstName
和lastName
为fullName
的依赖,并建立依赖关系 - 当
firstName
或lastName
发生变化时,根据依赖关系,触发fullName
的重新计算 - 如果计算值没有发生变化,不会触发视图更新
通过以上的分析,我们知道计算属性本质上就是一个 computed watcher,也了解了它的创建过程和被访问触发 getter 以及依赖更新的过程,其实这是最新的计算属性的实现,之所以这么设计是因为 Vue 想确保不仅仅是计算属性依赖的值发生变化,而是当计算属性最终计算的值发生变化才会触发渲染 watcher 重新渲染,本质上是一种优化。
computed计算值为什么还可以依赖另外一个computed计算值¶
小提示:这个问题当时完全不知道,哎,官方源码的套路太深了......
这里希望有大神可以补充说明一下。
周期函数有哪些(beforeCreated
和created
中间都做了什么¶
)
初始化 data
、props
、computed
、watcher
、provide
。官方源码具体位置src/core/instance/init.js
:
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
小结¶
51信用卡的这次面试其实面试官考察的点还是蛮深入的,问了一些Vue底层源码的实现,总体感觉自己回答的还可以,但是面试官说:你应该去阿里...
阿里部门未知(一面)¶
说说Webpack的实现原理¶
小提示:这个直接回答不知道,问题较大,我这里猜测一下是类似Babel和AST抽象语法树相关,有空去看下源码。
这个问题希望同学可以补充一下。
首屏优化有哪些解决方案¶
小提示:这个问题在回答懒加载的过程中,面试官追问懒加载的Webpack配置,我说了和代码切割相关。
关于懒加载,这里推荐一篇非常好的文章:Webpack 大法之 Code Splitting。
Node.js的加载机制(require
和module.exports
)¶
小提示:这个问题其实是非常常见的问题,建议大家阅读一下源码,有些也可能会问一下比较简单的问题,例如
module.exports
和exports
的区别,或者也可能问CommonJS引入和ES6引入的区别。
你觉得你最擅长什么¶
小提示:这个问题是个大坑阿,我这里直接回答我什么都不擅长,这样回答显然面试官是不会不满意的,建议大家在面试前好好想想自己到底擅长啥。
React和Vue的区别¶
小提示:这里React真的好久没用了,几乎忘记了,大致说了下单向数据流、双向数据绑定、数据监听方式、JSX以及Vue的单文件组件、函数式编程、Vue的指令之类的。
这个问题希望同时熟悉React和Vue的同学可以补充一下。
React、Vue和JQuery在什么场景下怎么选型¶
这个问题希望同学可以补充一下。
Vue的响应式原理¶
什么情况下会阻塞DOM渲染¶
小提示:面试官这里应该想问DOM渲染的过程中可能有哪些情况会阻塞渲染。我当时回答不知道。
这个问题希望同学可以补充一下。
有哪些异步函数¶
小提示:回答了宏任务和微任务。
讲讲MVVM,说说与MVC有什么区别¶
小提示:这个问题我专门发了一篇掘金文章,但是很多人好像都不是很感兴趣的样子,但是面试官真的就问了这样一个问题。
这里推荐我之前写的掘金文章基于Vue实现一个简易MVVM/MV*设计模式的演变历史,一开始重点讲解了MVC、MVP以及MVVM的演变过程和区别。
阿里CBU技术部(一面)¶
说说z-index有什么需要注意的地方¶
小提示:真的忘记的差不多了,就简单说了只能在同一层叠上下文中进行
z-index
值比较、和绝对定位的关系,z-index
值不需要设置过大,只需要理清楚层级关系即可。面试官追问了z-index
值和background
的覆盖关系,还追问了绝对定位元素以及后来居上的准则。面试官还问了z-index默认值是什么,0
和auto
有没有区别?真的对于CSS可能平常就用的不多,所以这个问题答的不是很好。
可能面试官最想知道的是下面这张图:
这里附上张鑫旭的文章深入理解CSS中的层叠上下文和层叠顺序。
这里由于回答了定位,面试官追问固定定位的元素是相对于什么进行定位?相对定位会脱离正常文档流么?绝对定位是相对于什么元素进行定位?
熟悉CSS3动画么¶
小提示:CSS3动画硬件加速?CSS3动画的性能问题(重绘和重流,是否需要脱离正常文档流)?这个我当时答不知道,确实平常用的很少,如果熟悉Vue过渡动画的同学可以讲讲过渡动画?
有没有做过什么可视化的项目¶
小提示:我的回答:地图算么?基于OpenLayers设计过地图的Vue组件库。
对于可视化希望同学可以补充一下。
你觉得你最擅长的是什么¶
小提示:这个问题简直就是给人挖坑。
Flex实现两列布局¶
这里简单实现一下(其实应该使用flex-basis
属性):
<div class="box">
<div class="box-left"></div>
<div class="box-right"></div>
</div>
.box {
height: 200px;
display: flex;
}
.box > div {
height: 100%;
}
.box-left {
width: 200px;
background-color: blue;
}
.box-right {
flex: 1; // 设置flex-grow属性为1,默认为0
overflow: hidden;
background-color: red;
}
ES6/ES7/ES8的特性¶
说说DOM事件流¶
小提示:面试官追问事件委托有什么优点(起码两个以上)、
target
/currentTarget
/relateTarget
具体指向什么目标。
你觉得你有做过推动流程或者改善流程的事件么,举例说明¶
小提示:这个如果做过什么规范或者开发工具之类的,应该比较好回答。
小结¶
总体来说这次面试面得很细,有些知识点已经忘记,建议大家面试前把一些感觉不是很熟悉的原生知识点回忆起来,尤其是在开发中都不怎么会使用一些CSS样式设计的童鞋(现在很多都是组件库的设计方案,样式早已经封装掉了)。
阿里企业智能事业部(一面)¶
Event Loop¶
Webpack的loader和plugins的区别¶
小提示:当时直接回答不知道,确实Webpack我只会用,还没了解过内部的实现原理和构成。这个后续无论如何都要好好理解一下原理。
这个问题希望同学可以补充一下。
HTTP状态码206是干什么的¶
小提示:工作中没有遇到过需要上传下载大型文件,所以这个问题当时老老实实回答不知道。具体应该和断点续传相关,可能也需要回答一些
range
的头部信息等。
React高阶组件的作用有哪些¶
小提示:好久没用过React了,大致只知道Racct是单向数据流的,利用高阶组件可以实现类似于Vue的双向数据绑定。
这个问题希望同学可以补充一下。
React和Vue的区别¶
Service Worker有哪些作用¶
小提示:当时怕说错,老老实实回答不知道。后来查了一下应该和缓存以及HTTP请求拦截相关。
这个问题希望同学可以补充一下。
跨域¶
文件上传的二进制具体是怎么处理的¶
小提示:只知道上传的头信息是
application/x-www-form-urlencoded
,也可以对上传的文件的数据进行拦截处理,例如对上传文件的信息进行加密处理。
这个问题希望同学可以补充一下。
Vue响应式原理¶
首屏加载性能优化¶
小结¶
其实这一次面试自己感觉面试的不是很好(尽管面试官问的确实比我上面列出的问题多),因为有好几个问题自己确实不清楚。这里再次建议大家不知道就是回答不知道,这样不会对面试官造成一些负面印象。这一次面试能够通过运气占了很大一部分。
阿里企业智能事业部(二面)¶
computed的实现原理¶
Vue的整个实现原理¶
小提示:当时面试官问的蛮好玩的,他问从开始写一个.vue文件开始到DOM渲染到页面上,Vue做了哪些工作。然后我当时没理解面试官是要问vue-loader?DOM树的渲染过程?来来回回试探性的问了面试官几次,才理解原来面试官想知道Vue源码的整个实现过程。
大家如果想了解Vue源码实现的整个粗略过程,可以看下之前写的文章基于Vue实现一个简易MVVM/Vue的运行机制简述。
通讯¶
小提示:由于这边涉及到一些海康的设备(上下位机通信),面试官问我如何知道上位机软件给下位机设备发送了5次信息。这个其实大部分Web前端开发在工作上很难遇到类似的问题,辛亏我以前毕业设计中做过上下位机的TCP通讯。后来我从Leader面那里了解到二面面试官应该是做iot物联网开发这一块的。
请求帧数据结构如下:
帧头 | 帧序号 | 帧负载 | 帧校验 | 帧尾 |
---|---|---|---|---|
2 Byte | 1 Byte | N Byte | 1 Byte | 1 Byte |
这里帧头使用2字节识别,校验可以采用CRC校验,帧序号用来识别发送了几次信息。
Chrome插件如何屏蔽广告¶
小提示:这个问题当时回答不知道,其实后面想想最简单的办法是先找出广告元素的一些通用特性,然后在Chrome插件中通过注入脚本的形式将这些广告元素隐藏掉。
这里不知道有没有更好的其他方式,例如不知道Service Work对请求拦截处理是否可以有效屏蔽广告等,这个问题希望同学可以补充一下。
如何判断两个变量相等¶
小提示:这里需要分基本类型和引用类型,面试官在这里具体想问的是
Object.is
的实现原理。这是面试官问我的第一个问题,当时直接回答不知道,内心都觉得接下来要凉凉了。
Watch的运行原理¶
Vue的数据为什么频繁变化但只会更新一次¶
小提示:这里问的是Vue源码对于视图更新的优化。我这里的回答是乱糟糟的,希望有同学能够给出一个精准并且简短的回答。
Vue 异步执行 DOM 更新。只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部尝试对异步队列使用原生的 Promise.then
和 MessageChannel
,如果执行环境不支持,会采用 setTimeout(fn, 0) 代替。
另外,关于waiting
变量,这是很重要的一个标志位,它保证flushSchedulerQueue
回调($nextTick中执行)允许被置入callbacks
一次。
因为Vue的事件机制是通过事件队列来调度执行,会等主进程执行空闲后进行调度,所以先会去等待所有的同步代码执行完成之后再去一次更新。这样的性能优势很明显,比如:
现在有这样的一种情况,mounted
的时候test
的值会被循环执行++1000次。 每次++时,都会根据响应式触发setter->Dep->Watcher->update->run
。 如果这时候没有异步更新视图,那么每次++都会直接操作DOM更新视图,这是非常消耗性能的。 所以Vue实现了一个queue队列,在下一个tick(或者是当前tick的微任务阶段)统一执行queue中Watcher的run。同时,拥有相同id的Watcher不会被重复加入到该queue中去,所以不会执行1000次Watcher的run。最终更新视图只会直接将test对的DOM的0变成1000。 保证更新视图操作DOM的动作是在当前栈执行完以后下一个tick(或者是当前tick的微任务阶段)的时候调用,大大优化了性能。
执行顺序update -> queueWatcher -> 维护观察者队列(重复id的Watcher处理) -> waiting标志位处理(保证需要更新DOM或者Watcher视图更新的方法flushSchedulerQueue只会被推入异步执行的$nextTick回调数组一次) -> 处理$nextTick(在为微任务或者宏任务中异步更新DOM)->
- Vue是异步更新Dom的,Dom的更新放在下一个宏任务或者当前宏任务的末尾(微任务)中进行执行
由于VUE的数据驱动视图更新是异步的,即修改数据的当下,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。在同一事件循环中的数据变化后,DOM完成更新,立即执行nextTick(callback)
内的回调。
vue和react一样,对dom的修改都是异步的。它会在队列里记录你对dom的操作并进行diff操作,后一个操作会覆盖前一个,然后更新dom。
Event Loop¶
除了Flex还可以用什么进行布局¶
小提示:我猜这里面试官想问的是Grid,当时说不知道。
绝对定位、固定定位和z-index¶
小提示:感谢CBU技术部的面试官。
绝对定位¶
- 一旦给元素加上
absolute
或float
就相当于给元素加上了display:block
absolute
元素覆盖正常文档流内元素(不用设z-index,自然覆盖)- 可以减少重绘和回流的开销(如
absolute+ top:-9999em
,或absolute + visibility:hidden
,将动画效果放到absolute
元素中)
属性介绍¶
static
,默认值。位置设置为static的元素,它始终会处于文档流给予的位置。inherit
,规定应该从父元素继承 position 属性的值。但是任何的版本的 Internet Explorer (包括 IE8)都不支持属性值 “inherit”。fixed
,生成绝对定位的元素。默认情况下,可定位于相对于浏览器窗口的指定坐标。元素的位置通过 “left”, “top”, “right” 以及 “bottom” 属性进行规定。不论窗口滚动与否,元素都会留在那个位置。但当祖先元素具有transform
属性且不为none时,就会相对于祖先元素指定坐标,而不是浏览器窗口。absolute
,生成绝对定位的元素,相对于距该元素最近的已定位的祖先元素进行定位。此元素的位置可通过 “left”、”top”、”right” 以及 “bottom” 属性来规定。relative
,生成相对定位的元素,相对于该元素在文档中的初始位置进行定位。通过 “left”、”top”、”right” 以及 “bottom” 属性来设置此元素相对于自身位置的偏移。
浮动、绝对定位和固定定位会脱离文档流,相对定位不会脱离文档流,绝对定位相对于该元素最近的已定位的祖先元素,如果没有一个祖先元素设置定位,那么参照物是body层。
绝对定位相对于包含块的起始位置:
- 如果祖先元素是块级元素,包含块则设置为该元素的内边距边界。
- 如果祖先元素是行内元素,包含块则设置为该祖先元素的内容边界。
问答题:
- 定位的元素的起始位置为父包含块的内边距(不会在border里,除非使用负值,会在padding里)
- 定位的元素的margin还是能起作用的
- background属性是会显示在border里的
- z-index是有层叠层级的,需要考虑同一个层叠上下文的层叠优先级
- z-index是负值不会覆盖包含块的背景色(但是如果有内容,会被包含块的内容覆盖)
- z-index的值影响的元素是定位元素以及flex盒子
- 上面一个定位元素,下面一个正常流的元素,定位元素会覆盖在正常流元素之上,除非给z-index是负值
- 页面根元素html天生具有层叠上下文,称之为“根层叠上下文”
小结¶
这一次面试官问我的第一个问题Object.is
就没答上来,不过面试官显然没有因为开头答的不好就否定面试者。大家如果在面试时第一个问题就答不上来,不要慌,要保持良好的心态,把接下来能答的问题好好答上来。可能很多同学会疑问,好像还有好几个问题感觉没答上来,但是可能只要有一个问题答的非常出彩,仍然可以弥补那些没答上来的问题(这里面试官当时说Vue源码的实现过程我说的比较清楚,还没有一个面试者答的比我更清楚的)。
阿里企业智能事业部(Leader面)¶
三面是Leader现场面,我当时特别担心有赞二面的情况发生,冷不丁又给你来一道算法题,这些真是我最不擅长的点。因为有点心虚我就问了下在阿里的师兄(师兄可能也做招聘工作,当时还怪我没有找他内推...),他说现场面其实最主要的是好好准备简历上的内容,面试官一般都会根据简历进行问答,还说他当时面试阿里时会让他画一些框架层次图(这个我当时没在意,结果面试官确实让我根据其中某个项目画一个框架层次图)。Leader面的时候在场的有两个面试官和一个HR。
Leader(一)面试¶
先是进来一个气场很足的Leader,看起来很权威,但是问问题还蛮随意的,就简单的让我介绍一下自己做的项目,然后翻看了我做的一些东西。感觉他好像有点心不在焉,翻看的很随意,我在回答问题的时候用余光关注了一下大佬的表情,感觉他在我**项目经历**那一块停留了非常长的时间。
Leader(二)面试¶
我正回答着自己的项目经历,Leader二和HR进来了,等我回答完Leader一就让Leader二开始面我。Leader二就问了我其中的两个项目。问我的第一个项目是自己做的公司内部的工具,他问这个平台有什么可以衡量的数据表明公司内部人员的使用情况。我回答当时因为领导觉得没必要做,就没有做数据统计这一块,告诉了他数据库里的一些真实数据情况。然后他问PV、UV应该怎么统计(我当时还厚脸皮的问他PV和UV是什么)?如果访问的页面出不来PV怎么统计?页面有没有做什么行为监测?页面访问量过大怎么处理?我大致讲了一些我的思路。
接着问我第二个项目(Low Code相关),我就回答了这个项目的技术体系,从以前做了什么到现在做到什么程度,到未来需要做成什么样,统统仔细的说了一遍。Leader二就问我未来做成什么样能不能思考一下怎么做,给了我5分钟的时间(这期间他一直反复的在翻阅我的简历)。然后我就假装思考了5分钟左右,其实脑子里一片空白,当时对于未来要做成什么样还只是个构思。然后Leader二还是很体贴的,他说你可以在墙上画一画(墙上可以写字),我就大致画了画,Leader二问我能不能画一画这个项目的框架层次图,我就简单的画了画...最后Leader二直接说你们做的太Low了,这个(Low Code)在我们这里已经是两年前的技术了...(这个我还是要解释下,我所在的部门从开始用Vue到目前只有短短的两年时间,在这两年时间里技术体系还是飞速的在沉淀和发展,我离开之前已经构思并实现了部分Vue技术栈的Low Code解决方案,如果这方面感兴趣的同学也可以找我沟通)。
Leader二还蛮好玩的,他说Low Code如果真的做出来了,都没前端什么事情了,那你干嘛去?顺着这个问题他还问我未来的前端应该怎么发展?未来前端有哪些可以挖掘的点?我回答了一些Graphql、可视化等,我还说了一个特别搞笑的回答,我说从以往的发展来看,前端应该抢占后端的资源,把后端限制我们的事情让前端也能做,让前端更加解放。Leader二当场就进行了反驳,说是要有价值才做,而不是为了能做而做,吓得我不轻...然后Leader二还详细的跟我解释了未来发展这个问题他希望得到什么回答,当时还是觉得Leader二蛮亲切的。
HR面试¶
Leader二问完以后HR就接着问我了以下几个问题:
- 为什么要离开现在的公司
- 以前公司的岗位制度是什么样
- 你是校招进去的么
- 你现在的岗位等级情况
- 你的绩效情况
- 你领导对你的评价是怎么样的
- 领导是不是经常找你沟通
然后Leader一顺着HR问了一个小问题:
- 你未来对于你的职业有什么规划
最后问我还有什么想问的,我当时已经被三个人问的有点迷迷糊糊了,然后想了想说没有。
小结¶
这次现场面其实我感觉自己面得不是很好,总感觉自己要挂了。总共面了将近1个半小时左右,尤其是Leader二的问题很多不是他想要的答案,但是最终居然过了。
阿里企业智能事业部(HR面)¶
企业智能事业部Leader面后又收到了HR面的面试通知,这一轮面试大致问了以下问题:
- 你为什么要离开现在的公司
- 你们公司的岗位等级是怎么评定的,你现在是什么岗位等级
- 谈谈你在公司的绩效情况
- 你觉得你做的最有成就感的一件事
- 你一般解决问题的方法有哪些
- 你是因为什么契机选择做前端
- 你有对你所在的公司做过什么流程或制度规范上的改进么
- 你最近在看什么书,和工作相关么,你为什么要看这些书
- 看到你之前还面试了其他两个部门都挂在了一面,你感觉是什么原因
- 你期望的薪资待遇是多少
小提示:这里HR会问的其实不止这些问题,例如你为什么喜欢Web前端这个岗位、你未来的职业规划、你觉得你的优点和缺点有哪些、为什么选择阿里巴巴、对之前几个面试官做下评价、你用过阿里的哪些产品顺便谈谈这些产品的优缺点、你对于互联网是怎么理解的...
小结¶
对于HR面还是要好好准备的,尤其是有些问题还是很容易挖坑的,例如你为什么离开现在的公司(你当然不应该抱怨现在的公司有哪些不好的地方,更多的应该表明自己想要寻找更好的发展机会,自己的一些现实因素,比如对于我而言是现在应聘的公司离自己的家更近,又或者是自己工作到达了迷茫期,想跳出迷茫期等等),你觉得你做的最有成就感的一件事(你要是说个简单的,HR会觉得你工作能力不强),你一般解决问题的方法有哪些(HR当然也想考察你解决问题的能力,你要是说什么百度啊之类的HR当然会觉得你解决问题的能力不强),你期望的薪资待遇是多少(你要是不喜欢这家公司,可以期望高一些,你要是很喜欢这家公司面试过程很愉快上浮个30%左右,面试过程一般上浮个20%左右)。
友情链接¶
这里推荐阅读之前写的文章(前面两篇实用型,后面三篇对面试应该会有帮助):
- Vue CLI 3结合Lerna进行UI框架设计
- Cz工具集使用介绍 - 规范Git提交说明
- 你真的理解$nextTick么
- 基于Vue实现一个简易MVVM
- 2019 前端之路(干货满满)(墙裂推荐)