写在前面
没有错,就是我啦dog cheng,好久不见,从17年在博客园写下第一篇文章,转身间已然两年,从大二到现在的大四预备毕业生,我仍然在这条道路上前进。秋招早已经结束,在拿到用友,滴滴的offer之后,最终在九月选择了百度APP,但是我没有停下,重新拾起了当初的热忱,怀着学习的态度完成了一份90页PDF , 近140+commits的面试小册之后,写下了这段文字
互联网发展迅猛之余也伴随着互联网寒冬,行业不景气这样的词,等毕业季去各个求职网站投简历,去各个人才市场找机会,才发现四处碰壁,作为应届求职者更需要打好基础,明确发展规划,跟上行业步伐。下面是本人2019年秋招前端面试经历,结合个人博客和牛油们面经中的高频问题以及行业前辈们复习资料的综合整理,包含基础篇、Vue框架篇、HTTP&浏览器、构建工具篇、安全篇、算法篇,欢迎交流斧正,希望大家在毕业季都能一帆风顺,从容斩获OFFER。近100+前端面试题及推荐解答,资源合集,这个冬天不会很冷
因为篇幅有限,下面留下了前两篇各五道面试题,这个项目已经在github上开源,欢迎大家取用:Github
HTML/CSS
浏览器解析渲染页面过程
大致过程:
HTML解析构建DOM->CSS解析构建CSSOM树->根据DOM树和CSSOM树构建render树->根据render树进行布局渲染render layer->根据计算的布局信息进行绘制
不同浏览器的内核不同,所以渲染过程其中有部分细节有不一样,以webkit主流程为例:
一篇很棒的文章:How Browser Work
我有话说:浏览器解析渲染页面过程是一个复杂的过程,其中有不少的细节和规则,如果把上面分享的文章翻译成译文,至少有3~5页PDF左右,所以这里只能总结大致过程(作为面试回答【很可能让回答的尽可能详细】了解来说已经足够,更深入的了解可以好好读下上面那篇文章)
较详细过程:
HTML解析构建DOM树:其中HTML Parser就起到了将HTML标记解析成DOM Tree的作用,HTML Parser将文本的HTML文档,提炼出关键信息,嵌套层级的树形结构,便于计算拓展;这其中也有很多的规则和操作,比如容错机制,识别特殊标签<br></br>
等
CSS解析构建CSSOM树:CSS Parser将很多个CSS文件中的样式合并解析出具有树形结构Style Rules,也叫做CSSOM。
※其中还有一个细节是浏览器解析文档:当遇到
<script>
标签的时候会停止解析文档,立即解析脚本,将脚本中改变DOM和CSS的地方分别解析出来,追加到DOM Tree和CSSOM上
根据DOM树和CSSOM树构建Render树:Render Tree的构建其实就是DOM Tree和CSSOM Attach的过程,在webkit中,解析样式和创建呈现器的过程称为"附加",每个DOM节点都有一个"attach"方法,Render Tree其实就相当于一个计算好样式,与HTML对应的Tree
根据Render树进行布局渲染render layer:创建渲染树后,Layout根据根据渲染树中渲染对象的信息,计算好每一个渲染对象的位置和尺寸,将其放在浏览器窗口的正确位置,某些时候会在文档布局完成之后进行DOM修改,重新布局的过程就称为回流
※其中计算(样式计算)一个复杂的过程,因为DOM中的一个元素可以对应样式表中的多个元素,Firefox采用了规则树和样式上下文树来简化样式计算,规则树包含了所有已知规则的匹配路径,样式上下文包含端值,webkit也有样式对象,但它们不保存在类似上下文树这样的结构中,只是由DOM节点指向此类对象的相关样式
根据计算的布局信息进行绘制:绘制阶段则会遍历呈现树,并调用呈现器的paint方法,将呈现器的内容显示在屏幕上,绘制的顺序其实就是元素进入堆栈样式上下文的顺序,例如,块呈现器的堆栈顺序如下:1.背景颜色,2.背景图片,3.边框,4.子代,5.轮廓
移动端点透现象有遇到过嘛
首先需要了解的是,移动端在touch上一共有4个事件,
执行顺序为touchstart -> touchmove -> touchend -> touchcancel
当用户点击屏幕时,会触发touch和click事件,touch事件会优先处理,touch事件经过捕获,目标,冒泡一系列流程处理完成之后,才会触发click,所有我们经常会谈到移动端点击事件300ms延迟的问题
移动端点击事件300ms问题,常见的解决方案:
- 阻止用户双击缩放,并限制视口大小
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"/>
- 设置css
touch-action
用于指定某个给定的区域是否允许用户操作,以及如何相应用户操作
* {
touch-action: none;
}
- fastclick.js来解决,其原理是在检测到touchend事件的时候,会通过自定义事件立即触发模拟一个click事件,并在300ms之后把真正的click事件阻止掉
点透现象:
发生条件:①按钮A和按钮B不是后代继承关系,②A发生touch,A touch后立即消失,B绑定click,③A z-index大于B,即 A 显示在 B 浮层之上
发生原因:当点击屏幕时,系统生成touch和click两个事件,touch先执行,touch执行完之后A消失,然后要执行click的时候,就会发现用户点击的是B,所以就执行了B的click
解决方法:①阻止默认事件,在touch的某个时间段执行event.preventDefault,去取消系统生成的click事件,一半在 touchend 中执行。②要消失的元素延迟300ms后在消失
margin塌陷和合并问题
首先,margin塌陷是相对于父子级关系的两个元素,而margin合并是相对两个兄弟级关系的两个元素
两个兄弟级关系的元素,垂直方向上的margin,其外边距会发生重叠现象,两者两个的外边距取的是两个所设置margin的最大值,就是所说的margin合并问题
两个父子级关系的元素,垂直方向上的margin会粘合在一起,外层和模型的margin-top取两个元素中margin-top的最大值,发生margin塌陷的内层元素相对于整个文档移动
解决方案:两者都可以通过触发BFC来解决
CSS定位的方式有哪些分别相对于谁
static(默认值)
absolute(绝对定位,相对于最近已定位的父元素,如果没有则相对于<html>)
fixed(固定定位,相对于窗口)
relative(相对定位,相对于自身)
sticky(2017年浏览器开始支持,粘性定位)
absolute会使元素位置与文档流无关,不占据空间,absolute 定位的元素和其他元素重叠
relative相对定位时,无论元素是否移动,仍然占据原来的空间
sticky是2017年浏览器才开始支持,会产生动态效果,类似relative和fixed的结合,一个实例是"动态固定",生效前提是必须搭配top,left,bottom,right
一起使用,不能省略,否则等同于relative
定位,不产生"动态固定"的效果
移动端布局的解决方案,平时怎么做的处理
- 使用Flexbox
- 百分比布局结合媒体查询
- 使用rem
rem转换像素大小(根元素的大小乘以rem值),取决与页面根元素的字体大小,即HTML元素的字体大小
em转换像素大小(em值乘以使用em单位的元素的字体大小),比如一个div的字体大小为16px,那么10em就是180px(或者接近它)
rem平时怎么做的转换:为了方便计算,时常将html的字体大小设置为62.5%,那么12px就会是1.2rem
JavaScript
列表无限滚动曾经有遇到过嘛
简单列表滚动加载是监听滚动条在满足条件的时候触发回调,然后通过把新的元素加入到页面页尾的方法完成,但是如果用户加载过多列表数据(比如我这一个列表页有一万条数据需要展示),那么用户不断加载,页面不断增加新的元素,很容易就导致页面元素过多而造成卡顿,所以就提出的列表的无限滚动加载,主要是在删除原有元素并且维持高度的基础上,生成并加载新的数据
如果滚动过快怎么办,高频率触发事件解决方案-防抖和节流
节流:在一段时间内不管触发了多少次都只认为触发了一次,等计时结束进行响应(假设设置的时间为2000ms,再触发了事件的2000ms之内,你在多少触发该事件,都不会有任何作用,它只为第一个事件等待2000ms。时间一到,它就会执行了。 )
//时间戳方式
function throttle(fn,delay){
let pre = Date.now();
return function(){
let context = this;
let args = arguments;
let now = Date.now();
if(now - pre > delay){
fn.apply(context,args);
pre = Date.now();
}
}
}
//定时器方式
function throttle(fn,delay){
let timer = null;
return function(){
let context = this;
let args = arguments;
if(!timer){
timer = setTimeout(function(){
fn.apply(context,args);
timer = null;
},delay)
}
}
}
防抖:在某段时间内,不管你触发了多少次事件,都只认最后一次(假设设置时间为2000ms,如果触发事件的2000ms之内,你再次触发该事件,就会给新的事件添加新的计时器,之前的事件统统作废。只执行最后一次触发的事件。)
//定时器方式
function debounce(fn,delay){
let timer = null;
return function(){
let context = this;
let args = arguments;
clearTimeout(timer);
timer = setTimeout(function(){
fn.apply(context,args);
},delay)
}
}
解释一下变量提升
JavaScript引擎会先进行预解析,获取所有变量的声明复制undefined,然后逐行执行,这导致所有被声明的变量,都会被提升到代码的头部(被提升的只有变量,值不会被提升),也就是变量提升(hoisting)
console.log(a) // undefined
var a = 1
function b(){
console.log(a)
}
b() // 1
预解析阶段会先获的变量a赋值undefined,并将var a = undefined放在最顶端
,此时a = 1还在原来的位置,实际就是:
var a = undefined
console.log(a) // undefined
a = 1
function b(){
console.log(a)
}
b() // 1
然后会是执行阶段,逐行执行造成了打印出a是undefined
js为什么0.1+0.2不等于0.3
主要是因为JavaScript同样采用IEEE754标准,在64位中存储一个数字的有效数字形式
第0位表示符号位,0表示整数1表示负数,第1~11位存储指数部分,第12~63位存小数部分;
由于二进制的有效数字总是表示为1.xxx...
这样的形式,尾数部分在规约形式下的第一位默认为1,故存储时第一位省略不写,尾数部分存储有效数字小数点后的xxx...,最长52位,因此,JavaScript提供的有效数字最长为53个二进制位(尾数部分52位+被省略的1位)
由于需要对求和结果规格化(用有效数字表示),右规导致低位丢失,此时需对丢失的低位进行舍入操作,遵循IEEE754舍入规则,会有精度损失
对eventloop事件循环机制的了解
首先,JavaScript一大特点就是单线程,这样的设计让它在同一时间只做一件事;作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM,避免了复杂性,比如假设JavaScript有两个线程,那么在同一时间进行添加删除节点操作,为浏览器分辨以哪个线程为准带来困难,所以单线程是它作为浏览器脚本语言的优势,也是它的核心特征。
注:虽然为了利用多核CPU的计算能力,HTML5提出了web worker标准,允许JavaScript创建多个线程,但是子线程完全受主线程控制,且不得操作DOM,所以也并没有改变JavaScript单线程的本质
那么,单线程就意味着,所有任务需要排队,前一个任务结束才会执行后一个任务,所以为了提高CPU的利用效率,就把所有任务分成了同步任务(synchronous)和异步任务(asynchronous),同步任务在主线程顺序执行,异步任务则不进入主线程而是进入到任务队列(task queue)中。在主线程上会形成一个执行栈,等执行栈中所有任务执行完毕之后,会去任务队列中查看有哪些事件,此时异步任务结束等待状态,进入执行栈中,开始执行。
主线程从任务队列中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)
写在后面
篇幅有限,上面留下了小册中前两篇的各五道高频问题,更多问题以及资源合集,在Github可以直接看到,而且除了Github还提供了其他两种方案,gitbook和pdf(近90页),都可以选择
那么,最后为了突出主题呢,还是要写一些对于这份小册的愿景吧:如果你是应届生(当然,大牛除外),正面临找前端开发的工作,或者即将成为毕业生的预备生,我相信这份前端面试小册多多少少会帮到你,在这"不景气"的"寒冬"之季,我们仍要提高自身综合素质,坚持技术生活,通过不断给自己设立小目标并实现来督促自身学习提高,最后就以习大大给我们寄语结束吧—2020愿我们只争朝夕不负韶华