前端面试题合集-第一篇
前端面试题合集-第一篇
🔔每周不定时更新!
⛄不要让自己失去竞争力!
☀️哪里都不是避风港,保持竞争力!
1. CSS选择器的优先级
!important
>内联
>id选择器
>类选择器
>标签选择器
>通配符选择器
>继承
在同一组属性设置表中有
!important
规则的优先级最大
2. 如何实现左侧宽度固定,右侧宽度自适应的布局
标签结构:
<div class="way_1">
<div id="box" style="height: 300px;">
<div class="left">
左侧固定宽度
</div>
<div class="right">
右侧自适应
</div>
</div>
</div>
方法一:浮动
.way_1 #box .left {
float: left;
width: 100px;
height: 100%;
font-size: 12px;
background-color: pink;
}
.way_1 #box .right {
height: 100%;
font-size: 12px;
background-color: skyblue;
}
方法二:flex
.way_2 #box {
display: flex;
}
.way_2 #box .left {
width: 100px;
height: 100%;
font-size: 12px;
background-color: pink;
}
.way_2 #box .right {
flex: 1;
height: 100%;
font-size: 12px;
background-color: skyblue;
}
方法三:grid布局
.way_3 #box {
display: grid;
overflow: hidden;
/* 属性定义每一列的列宽 */
grid-template-columns: 100px 100%;
/* 属性定义每一列的行高 */
grid-template-rows: 300px;
}
.way_3 #box .left {
width: 100px;
height: 100%;
font-size: 12px;
background-color: pink;
}
.way_3 #box .right {
height: 100%;
font-size: 12px;
background-color: skyblue;
}
方法四:利用calc计算宽度
.way_4 #box {
height: 300px;
}
.way_4 #box .left {
width: 100px;
height: 100%;
float: left;
font-size: 12px;
background-color: pink;
}
.way_4 #box .right {
width: calc(100% - 100px);
float: right;
height: 100%;
font-size: 12px;
background-color: skyblue;
}
3.对跨域的了解
跨域行为
- 同源策略,安全性考虑
- 协议,IP和端口不一致都是跨域行为
跨域的几种解决方案
CORS(跨域资源共享)
1. 什么是cors
- 浏览器端回自动向请求头添加origin字段,表明当前请求来源。
- 服务器端需要设置响应头的Access-Control-Allow-Methods,Access-Control-Allow-Headers,Access-Control-Allow-Origin等字段,指定允许的方法,头部,源等信息。
- 请求分为简单请求和非简单请求,非简单请求会先进行一次OPTION方法进行预检,看是否允许当前跨域请求。
2. 后端的响应头信息
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
里面指定。
4.HTTP2和HTTP1的不同
相对于HTTP1.0,HTTP1.1的优化:
- 缓存处理,多了Entity tag ,if-Unmodified-Since, if-Match等缓存信息。
- 带宽优化以及网络连接的使用
- 错误通知的管理
- Host头的处理
- 长连接: HTTP1.1中默认开启Connetion:keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点。
** 相对于HTTP1.1,HTTP2的优化:**
- HTTP2支持二进制传送,HTTP1.x是字符串传送
- HTTP2支持多路复用
- HTTP2采用 HPACK压缩算法压缩头部,减少了传输体积
- HTTP2支持服务端推送。
5.你了解缓存吗
当执行强缓存时,若缓存命中,则直接使用缓存数据库中的数据,不再进行缓存协商。
1.强缓存
Expires(HTTP1.0): Exprires的值为服务端返回的数据到期时间。当再次请求时的请求小于返回的此时间,则直接使用缓存数据。但由于服务端时间和客户端时间可能有误差,这也将导致缓存命中的误差。另一方面,Expires是HTTP1.0产物,故现在大多数使用Cache-Control替代。
缺点:使用的是绝对时间,如果服务端和客户端的时间产生偏差,那么会导致命中缓存产生偏差。
Cache-Control(HTTP1.1):有很多属性,不同的属性代表的意义也不同
- private:客户端可以缓存
- Public:客户端和代理服务器都可以缓存
- max-age=t:缓存内容将在t秒后失效
- no-chche:需要使用协商缓存来验证缓存数据
- 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)
2.协商缓存
协商缓存需要进行对比判断是否可以使用缓存。浏览器第一次请求数据时,服务器会将缓存标识与数据一起响应给客户端,客户端将它们备份至缓存中。再次请求时,客户端会将缓存中的标识发送给服务器,服务器根据此标识判断。若未失效,返回304状态码,浏览器拿到此状态码就可以直接使用缓存数据了
- Last-Modified:服务器在响应请求时,会告诉浏览器资源的最后修改时间。
- if-Modified-Since:浏览器再次请求服务器的时候,请求头会包含此字段,后面跟着在缓存中获得的最后修改时间。服务端收到此请求头发现有if-Modified-Since,则与被请求资源的最后修改时间进行对比,如果一致则返回304和响应报文头,浏览器只需要从缓存中获取信息即可
- 如果真的被修改:那么开始传输响应一个整体,服务器返回:200 OK
- 如果没有被修改:那么只需传输响应header,服务器返回:304 Not Modified
- if-Unmodified-Since: 从某个时间点算起, 是否文件没有被修改,使用的是相对时间,不需要关心客户端和服务端的时间偏差
- 如果没有被修改:则开始`继续'传送文件,服务器返回: 200 OK
- 如果文件被修改:则不传输,服务器返回:412 Precondition filed(预处理失败)
这两个的区别是一个是修改了才下载一个是没修改才下载。如果在服务器上,一个资源被修改了,但其实际内容根本没发生改变,会因为Last-Modified时间匹配不上而返回了整个实体给客户端(即使客户端缓存里有个一模一样的资源)。为了解决这个问题,HTTP1.1推出了Etag
Etag
:服务器响应请求时,通过此字段告诉浏览器当前资源在服务器生成的唯一标识(生成规则由服务器决定)If-Match
:条件请求,携带上一次请求中资源的ETag,服务器根据这个字段判断文件是否有新的修改If-None-Match
: 再次请求服务器时,浏览器的请求报文头部会包含此字段,后面的值为在缓存中获取的标识。服务器接收到次报文后发现If-None-Match则与被请求资源的唯一标识进行对比- 不同,说明资源被改动过,则响应整个资源内容,返回状态码200。
- 相同,说明资源无心修改,则响应header,浏览器直接从缓存中获取数据信息。返回状态码304.
实际应用中由于Etag的计算是使用算法来的得出的,而算法会占用服务端计算资源,所有服务端的资源都是包贵的,所有很少使用Etag了。
- 浏览器地址中写入URL,回车浏览器发现缓存中有这个文件,不用继续请求了,直接去缓存拿(最快)
- F5就是告诉浏览器,不要再偷懒啦,去看看这个文件是不是过期了,别老拿过期的东西糊弄我。于是浏览器一个请求带上
If-Mondify-since
. - Ctrl+F5告诉浏览器,把缓存中的文件删除,然后再去服务器请求个完整的资源文件来。于是客户端完成了强行更新的操作。
3.缓存场景
大部分的场景都可以使用缓存配合协商缓存解决,但是在特殊地方可能需要特殊的缓存策略。
- 对于某些不需要缓存的资源,可以使用
Cache-contrlo:no-store
,表示该资源不需要缓存。 - 对于频繁变动的资源,可以使用
Cache-Control:no-cache
并配合ETag使用,表示该资源已被缓存,但每次都会发送请求询问资源是否更新。 - 对于代码文件来说,通常使用
Cache-Control:max-age=< seconds >
并配合策略缓存使用。
6. 首屏加载优化方案
- Vue-Router路由懒加载(利用Webpack的代码切割)
{
path: '/about',
name: 'about',
// 动态注册路由
component: () => import('../views/AboutView.vue')
}
- 使用CDN加速,将通用的库从vendor进行抽离
- Nginx的gzip压缩
在http块中或者单个server块里添加后重启nginx
nginx(centOS
)一般都安装在 usr/local/nginx/sbin
./nginx -s reload
#开启gzip
gzip on;
#低于1kb的资源不压缩
gzip_min_length 1k;
#压缩级别1-9,越大压缩率越高,同时消耗cpu资源也越多,建议设置在5左右。
gzip_comp_level 5;
#需要压缩哪些响应类型的资源,多个空格隔开。不建议压缩图片.
gzip_types text/plain application/javascript application/x-javascript text/javascript text/xml text/css;
#配置禁用gzip条件,支持正则。此处表示ie6及以下不启用gzip(因为ie低版本不支持)
gzip_disable "MSIE [1-6]\.";
#是否添加“Vary: Accept-Encoding”响应头
gzip_vary on;
- Vue异步组件
- 服务端渲染SSR
- 如果使用了一些UI库,采用按需加载
- Webpack开启gzip压缩
- 如果首屏为登录页,可以做成多入口
- Service Worker缓存文件处理
- 使用link标签的rel属性设置 prefetch(这段资源将会在未来某个导航或者功能要用到,但是本资源的下载顺序权重比较低,prefetch通常用于加速下一次导航)、preload(preload将会把资源得下载顺序权重提高,使得关键数据提前下载好,优化页面打开速度)
7. Event Loop
为什么会有事件循环?
JavaScript的一大特点就是单线程,但单线程就导致很多任务需要排队,如果中间某个执行时间太长,就容器造成堵塞,为了提高效率加了事件循环机制。
JavaScript单线程任务被分为同步任务和异步任务
从上面流程图中可以看到,主线程不断从任务队列中读取事件,这个过程是循环不断的,这种运行机制就叫做Event Loop(事件循环)!
在JavaScript中,除了广义的同步任务和异步任务,还可以分为宏任务和微任务。
宏任务和微任务的顺序流程图:
每次宏任务执行完毕后,检查微任务队列是否为空,如果不为空,会按照先入先出的规则全部执行完微任务后,清空微任务队列,再执行下一个宏任务,如此循环。
如何区分宏任务与微任务
- 宏任务:macrotask,又称为task, 可以理解为每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。
一般包括:script
(可以理解为外层同步代码)、setTimeout、setInterval 、setImmediate、I/O操作
- 微任务:microtask, 又称为job, 可以理解是在当前 task 执行结束后立即执行的任务。包括:
Promise.then/cath /finally
回调(平时常见的)、 MutationObserver回调(html5新特性)
在当前的微任务没有执行完成时,是不会执行下一个宏任务的。
案例一:
console.log("start");
setTimeout(() => {
console.log("children2")
Promise.resolve().then(() =>{
console.log("children3")
})
}, 0)
new Promise(function(resolve, reject){
console.log("children4")
setTimeout(function(){
console.log("children5")
resolve("children6")
}, 0)
}).then(res =>{
console.log("children7")
setTimeout(() =>{
console.log(res)
}, 0)
})
首先将整个
script
代码块作为一个宏任务执行,输出start
接着遇到setTimeout,0ms后将其回调函数放入宏任务队列。
接下来遇到Promise,由于Promise本身是立即执行函数,所以先输出
children4
。
- 3.1 在promise代码块里遇到
setTimeout
,将其放入宏任务队列中,整体代码执行完毕。之后检查所有的微任务,没有微任务,所以第一次事件循环结束,开始第二轮。
执行第2步放入的宏任务,输出
children2
- 5.1 遇到Promise,并直接调用了resolve,将.then回调加入到微任务队列中。
检查并执行所有微任务,输出
children3
,没有多余的微任务,所以第二轮事件结束,开始第三轮事件循环。执行3.1中放入的宏任务输出
children5
,并调用了resolve,所以将后面的.then回调
放入到微任务队列中。检查并执行所有微任务,先输出
children7
,遇到setTimeout,将其加入到宏任务队列中,开始四轮事件循环。执行第8加入的宏任务,输出
children6
,没有微任务,第四轮事件循环结束。