前端面试题合集-第一篇

前端面试题合集-第一篇

🔔每周不定时更新!

⛄不要让自己失去竞争力!

☀️哪里都不是避风港,保持竞争力!

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的优化:

  1. 缓存处理,多了Entity tag ,if-Unmodified-Since, if-Match等缓存信息。
  2. 带宽优化以及网络连接的使用
  3. 错误通知的管理
  4. Host头的处理
  5. 长连接: HTTP1.1中默认开启Connetion:keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点。

** 相对于HTTP1.1,HTTP2的优化:**

  1. HTTP2支持二进制传送,HTTP1.x是字符串传送
  2. HTTP2支持多路复用
  3. HTTP2采用 HPACK压缩算法压缩头部,减少了传输体积
  4. HTTP2支持服务端推送。

5.你了解缓存吗

graph LR A[缓存] B(强缓存) C(协商缓存) A --优先级高--> B A -.优先级低.-> C

当执行强缓存时,若缓存命中,则直接使用缓存数据库中的数据,不再进行缓存协商。

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)
})
  1. 首先将整个script代码块作为一个宏任务执行,输出start

  2. 接着遇到setTimeout,0ms后将其回调函数放入宏任务队列。

  3. 接下来遇到Promise,由于Promise本身是立即执行函数,所以先输出children4

    • 3.1 在promise代码块里遇到setTimeout,将其放入宏任务队列中,整体代码执行完毕。
  4. 之后检查所有的微任务,没有微任务,所以第一次事件循环结束,开始第二轮。

  5. 执行第2步放入的宏任务,输出children2

    • 5.1 遇到Promise,并直接调用了resolve,将.then回调加入到微任务队列中。
  6. 检查并执行所有微任务,输出children3,没有多余的微任务,所以第二轮事件结束,开始第三轮事件循环。

  7. 执行3.1中放入的宏任务输出children5,并调用了resolve,所以将后面的.then回调放入到微任务队列中。

  8. 检查并执行所有微任务,先输出children7,遇到setTimeout,将其加入到宏任务队列中,开始四轮事件循环。

  9. 执行第8加入的宏任务,输出children6,没有微任务,第四轮事件循环结束。

posted @ 2023-01-20 11:25  抗争的小青年  阅读(58)  评论(0编辑  收藏  举报