前端跳槽面试总结之渲染机制、运行机制、页面性能和错误监控

一、渲染机制
  1. 对于渲染机制,会从 DOCTYPE 及作用、浏览器渲染过程、重排 Reflow、重绘 Repaint和 布局 Layout 这几个方面。
  2. 对于 DOCTYPE 及作用,DTD 是文档类型定义,是一系列的语法规则,用来定义 XMLXHTML 的文件类型。浏览器会使用它来判断文档类型,决定使用何种协议来解析,以及切换浏览器模式。 DOCTYPE 是用来声明文档类型和 DTD 规范的,一个主要的用途是文件的合法性验证。如果文件代码不合法,那么浏览器解析时便会出一些差错。
  3. 常见的 DOCTYPE,如下所示:
  • HTML 5<!DOCTYPE html>
  • HTML 4.01 Strict,这个 DTD 包含所有 HTML 元素和属性,但不包括表象或过时的元素(如 font ),框架集是不允许的,<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
  • HTML 4.01 Transitional,这个 DTD 包含所有 HTML 元素和属性,包括表象或过时的元素(如 font ),框架集是不允许的,<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
  1. 对于浏览器渲染过程,HTML 按照 HTML Parser 结合 DOM 形成 DOM TreeStyle Sheets 按照 CSS Parser 形成 Style RulesDOM TreeStyle Rules 形成 Render Tree 渲染树,通过 Layout 布局,Render Tree 计算每个节点的位置大小等信息,通过 Painting 绘制根据计算好的信息绘制整个页面,最后 Display 呈现出来。
  2. 解析 HTML 过程中的问题,自上而下解析 HTML,逐渐构建起 DOM tree,遇到stylelink标签,会下载解析样式表,同时构建CSSOM tree,不会阻塞 html的解析。但是遇到script标签,它会立即下载并执行得到的脚本,会阻塞HTML的解析。直到脚本里的同步代码部分(settimeout等异步操作之外的代码)执行完之后,再接着解析接下来的HTML。直到将整个HTML文档的最后一个标签解析完毕,DOM tree生成完毕。然后CSSOM treerender tree生成,开始渲染。虽然script的下载、执行均会阻塞HTML的解析,但DOM tree的生成是在文档的最后一个标签解析完之后才生成,那么script标签的在HTML中的位置对DOM tree完全生成的时间应该是没有任何影响的。那为什么有把script标签放在</body>闭合之前的说法?

解答:这是因为实际上浏览器在正式渲染之前,会进行预渲染(first paint,怎么翻译的我忘了,反正可以理解为预渲染)。那么什么时候预渲染呢:当浏览器认为DOM tree生成得差不多了的时候。那什么是差不多呢,解析到body部分的第一个script标签之前。所以script如果放在head里,会推迟预渲染; 如果放在body的一开头,后面的一大堆标签还没解析,等于欺骗浏览器说已经“差不多了”,也就等于违背了设计预渲染的初衷,会造成整体渲染完毕的推迟。放在body中间也是一样的道理,所以还是放在最尾巴上比较好。

  1. 对于重排 ReflowDOM 结构中的各个元素都有自己的盒子模型,这些都需要浏览器根据各种样式来计算并根据计算结果将元素放到它该出现的位置,这个过程称为 reflow
  2. 触发 reflow 的条件,如下所示:
  • 当你增加、删除、修改 DOM 结点时,会导致 ReflowRepaint
  • 当你移动 DOM 的位置,或是搞个动画的时候
  • 当你修改 CSS 样式的时候
  • 当你 Resize 窗口的时候,但是移动端是没有这个问题,或是滚动的时候
  • 当你修改网页的默认字体时
  1. 对于重绘 Repaint,当各种盒子的时候、大小及其他的属性,例如颜色、字体大小等都确定下来后,浏览器于是便把这些元素都按照各自的特性绘制了一遍,于是页面的内容出现了,这个过程称为 repaint
  2. 触发 repaint 的条件,如下所示:
  • DOM 改动
  • CSS 改动
二、运行机制
  1. 对于 JS 的运行机制,分为 JS 的单线程、任务队列和 Event Loop 这几个方面。
  2. 对于 JS 的单线程,JavaScript 语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。javaScript 的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript 的主要用途是与用户互动,以及操作 DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。为了利用多核 CPU 的计算能力,HTML5 提出 Web Worker 标准,允许 JavaScript 脚本创建多个线程,但是子线程完全受主线程控制,且不得操作 DOM。所以,这个新标准并没有改变 JavaScript 单线程的本质。
  3. 对于任务队列,单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。JS 引擎执行异步代码而不用等待,是因有为有 消息队列和事件循环。消息队列是消息队列是一个先进先出的队列,它里面存放着各种消息。事件循环是事件循环是指主线程重复从消息队列中取消息、执行的过程。
  4. 主线程只会做一件事情,就是从消息队列里面取消息、执行消息,再取消息、再执行。当消息队列为空时,就会等待直到消息队列变成非空。而且主线程只有在将当前的消息执行完成后,才会去取下一个消息。这种机制就叫做事件循环机制,取一个消息并执行的过程叫做一次循环。
  5. JS 中分为两种任务类型:macrotaskmicrotask,在 ECMAScript 中,microtask 称为 jobsmacrotask 可称为 taskmacrotask(又称之为宏任务),可以理解是每次执行栈执行的代码就是一个宏任务。microtask(又称为微任务),可以理解是在当前 task 执行结束后立即执行的任务。宏任务就是说执行栈里的每一个被执行的代码就是一个宏任务,包括一个事件产生的回调执行,会在执行完毕一段代码之后先对 dom 进行一次渲染,然后再执行下一个宏任务。微任务是再宏任务执行完毕之后立即执行的,他在 dom 重新渲染之前,微任务的相应速度比宏任务是要快的。
  6. 任务队列就是等候执行的一系列任务,总结如下:
  • 任务队列又分为macro-task(宏任务)和micro-task(微任务);
  • macro-task大概包括:script(整体代码),setTimeout,setInterval,setImmediate,I/O,UI rendering;
  • micro-task大概包括:process.nextTick,Promise,Object.observe(已废弃),MutationObserver(html5新特性)
  • setTimeout/Promise 等我们称之为任务源。而进入任务队列的是他们指定的具体执行任务。
  • 来自不同任务源的任务会进入到不同的任务队列
  • 对于优先级,micro-task > macro-task
  • 对于micro-taskprocess.nextTick > Promise.then
  • 对于macro-tasksetTimeout > setImmediate
  1. 对于Event Loop 事件循环机制,JS 的运行机制,如下所示:
  • 执行栈执行宏任务
  • 执行栈没有任务就去轮询事件队列
  • 如果执行期间遇到微任务,就添加到微任务队列
  • 一个宏任务执行完毕后会立即执行当前微任务队列的任务
  • 宏任务执行完毕后开始渲染
  • 然后开启下一轮宏任务
  1. 对于异步任务,常用的有,如下所示:
  • setTimeoutsetInterval
  • DOM 事件
  • ES6 中的 Promise
三、页面性能
  1. 题目:提升页面性能的方法有哪些。
  2. 对于提升页面性能,如下所示:
  • 资源压缩合并,减少 HTTP 请求
  • 非核心代码异步加载,异步加载的方式和异步加载的区别
  • 利用浏览器缓存,缓存的分类和缓存的原理
  • 使用 CDN
  • 预解析 DNS
  1. 对于异步加载,异步加载的方式主要分为三种,动态脚本加载、deferasync,它们之间的区别,如下所示:
  • defer 是在 HTML 解析完以后才执行,如果是多个,按照加载的顺序依次执行
  • async 是在加载完以后立即执行,如果是多个,执行顺序和加载顺序无关
  1. 对于浏览器缓存,主要分为强缓存和协商缓存。
  2. 强缓存,不会向服务器发送请求,直接从缓存中读取资源,在hrome控制台的Network选项中可以看到该请求返回200的状态码,并且Size显示from disk cachefrom memory cache。强缓存可以通过设置两种 HTTP Header 实现:ExpiresCache-ControlCache-Conctrol的优先级比Expires高。强制缓存就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程,强制缓存的情况主要有三种,如下所示:
  • 不存在该缓存结果和缓存标识,强制缓存失效,则直接向服务器发起请求(跟第一次发起请求一致)
  • 存在该缓存结果和缓存标识,但是结果已经失效,强制缓存失效,则使用协商缓存
  • 存在该缓存结果和缓存标识,且该结果没有还没有失效,强制缓存生效,直接返回该结果
  1. ExpiresCache-Control,如下所示:
  • Expires,缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。Expires=max-age + 请求时间,需要和Last-modified结合使用。ExpiresWeb服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。ExpiresHTTP/1 的产物,受限于本地时间,如果修改了本地时间,可能会造成缓存失效。Expires: Wed, 22 Oct 2018 08:41:00 GMT表示资源会在 Wed, 22 Oct 2018 08:41:00 GMT 后过期,需要再次请求。ExpiresHTTP/1.0控制网页缓存的字段,其值为服务器返回该请求的结果缓存的到期时间,即再次发送请求时,如果客户端的时间小于Expires的值时,直接使用缓存结果。到了HTTP/1.1Expires已经被Cache-Control替代,原因在于Expires控制缓存的原理是使用客户端的时间与服务端返回的时间做对比,如果客户端与服务端的时间由于某些原因(时区不同;客户端和服务端有一方的时间不准确)发生误差,那么强制缓存直接失效,那么强制缓存存在的意义就毫无意义。
  • Cache-Control,在HTTP/1.1中,Cache-Control是最重要的规则,主要用于控制网页缓存。比如当Cache-Control:max-age=300时,则代表在这个请求正确返回时间(浏览器也会记录下来)的5分钟内再次加载资源,就会命中强缓存。Cache-Control 可以在请求头或者响应头中设置,并且可以组合使用多种指令,如下所示:
    • public:所有内容都将被缓存(客户端和代理服务器都可缓存)
    • private:所有内容只有客户端可以缓存,Cache-Control的默认取值
    • no-cache:客户端缓存内容,但是是否使用缓存则需要经过协商缓存来验证决定,设置了no-cache之后,并不是说浏览器就不再缓存数据,只是浏览器在使用缓存数据时,需要先确认一下数据是否还跟服务器保持一致
    • no-store:所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存
    • max-age=xxx (xxx is numeric):缓存内容将在xxx秒后失效
    • max-stale:能容忍的最大过期时间
    • min-fresh:能够容忍的最小新鲜度
  1. ExpiresCache-Control两者对比,区别就在于 Expireshttp1.0的产物,Cache-Controlhttp1.1的产物,两者同时存在的话,Cache-Control优先级高于Expires;在某些不支持HTTP1.1的环境下,Expires就会发挥用处。所以Expires其实是过时的产物,现阶段它的存在只是一种兼容性的写法。强缓存判断是否缓存的依据来自于是否超出某个时间或者某个时间段,而不关心服务器端文件是否已经更新,这可能会导致加载文件不是服务器端最新的内容,那我们如何获知服务器端内容是否已经发生了更新呢?此时我们需要用到协商缓存策略。
  2. 协商缓存,协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,可以通过设置两种 HTTP Header 实现:Last-ModifiedETag,情况如下所示:
  • 协商缓存生效,返回304Not Modified
  • 协商缓存失效,返回200和请求结果
  1. Last-ModifiedIf-Modified-Since,如下所示:
  • 浏览器在第一次访问资源时,服务器返回资源的同时,在response header中添加 Last-Modifiedheader,值是这个资源在服务器上的最后修改时间,浏览器接收后缓存文件和header,如 Last-Modified: Fri, 22 Jul 2016 01:47:00 GMT
  • 浏览器下一次请求这个资源,浏览器检测到有 Last-Modified这个header,于是添加If-Modified-Since这个header,值就是Last-Modified中的值;服务器再次收到这个资源请求,会根据 If-Modified-Since 中的值与服务器中这个资源的最后修改时间对比,如果没有变化,返回304和空的响应体,直接从缓存读取,如果If-Modified-Since的时间小于服务器中这个资源的最后修改时间,说明文件有更新,于是返回新的资源文件和200
  • Last-Modified 存在一些弊端,如果本地打开缓存文件,即使没有对文件进行修改,但还是会造成 Last-Modified 被修改,服务端不能命中缓存导致发送相同的资源。因为 Last-Modified 只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源
  • 根据文件修改时间来决定是否缓存尚有不足,能否可以直接根据文件内容是否修改来决定缓存策略,所以在 HTTP / 1.1 出现了 ETagIf-None-Match
  1. ETagIf-None-Match,如下所示:
  • Etag是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),只要资源有变化,Etag就会重新生成
  • 浏览器在下一次加载资源向服务器发送请求时,会将上一次返回的Etag值放到request header里的If-None-Match里,服务器只需要比较客户端传来的If-None-Match跟自己服务器上该资源的ETag是否一致,就能很好地判断资源相对客户端而言是否被修改过了。如果服务器发现ETag匹配不上,那么直接以常规GET 200回包形式将新的资源(当然也包括了新的ETag)发给客户端;如果ETag是一致的,则直接返回304知会客户端直接使用本地缓存即可
  1. Last-ModifiedEtag 的比较,如下所示:
  • 在精确度上,Etag要优于Last-ModifiedLast-Modified的时间单位是秒,如果某个文件在1秒内改变了多次,那么他们的Last-Modified其实并没有体现出来修改,但是Etag每次都会改变确保了精度;如果是负载均衡的服务器,各个服务器生成的Last-Modified也有可能不一致
  • 在性能上,Etag要逊于Last-Modified,毕竟Last-Modified只需要记录时间,而Etag需要服务器通过算法来计算出一个hash
  • 在优先级上,服务器校验优先考虑Etag
  1. 缓存机制,强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,返回200,重新返回资源和缓存标识,再存入浏览器缓存中;生效则返回304,继续使用缓存。如果什么缓存策略都没设置,那么浏览器会采用一个启发式的算法,通常会取响应头中的 Date 减去 Last-Modified 值的 10% 作为缓存时间。
  2. 实际场景应用缓存策略,如下所示:
  • 频繁变动的资源,Cache-Control: no-cache。对于频繁变动的资源,首先需要使用Cache-Control: no-cache 使浏览器每次都请求服务器,然后配合 ETag 或者 Last-Modified 来验证资源是否有效。这样的做法虽然不能节省请求数量,但是能显著减少响应数据大小
  • 不常变化的资源,Cache-Control: max-age=31536000。通常在处理这类资源时,给它们的 Cache-Control 配置一个很大的 max-age=31536000 (一年),这样浏览器之后请求相同的 URL 会命中强制缓存。而为了解决更新的问题,就需要在文件名(或者路径)中添加 hash, 版本号等动态字符,之后更改动态字符,从而达到更改引用 URL 的目的,让之前的强制缓存失效
  1. 用户行为对浏览器缓存的影响,用户在浏览器如何操作时,会触发怎样的缓存策略,如下所示:
  • 打开网页,地址栏输入地址: 查找 disk cache 中是否有匹配。如有则使用;如没有则发送网络请求
  • 普通刷新 (F5):因为 TAB 并没有关闭,因此 memory cache 是可用的,会被优先使用(如果匹配的话)。其次才是 disk cache
  • 强制刷新 (Ctrl + F5):浏览器不使用缓存,因此发送的请求头部均带有 Cache-control: no-cache(为了兼容,还带了 Pragma: no-cache),服务器直接返回 200 和最新内容
四、错误监控
  1. 对于错误监控,分为前端错误的分类、错误的捕获方式和上报错误的基本原理。
  2. 对于前端错误的分类,主要是分为即时运行出现的代码错误和资源加载错误。
  3. 对于错误的捕获方式,如下所示:
  • 即时运行错误的捕获方式,有 try...catchwindow.onerror
  • 资源加载错误,有 object.onerrorperformance.getEntries()Error 事件捕获
  1. 问题:跨域的 JS 运行错误可以捕获吗,错误提示什么,应该怎么处理?

解答:可以捕获,错误信息为 Script error,错误详情为 null,处理方式为 在 script标签增加 crossorigin 属性,设置 JS 资源响应头 Access-Control-Allow-Origin

  1. 对于上报错误的基本原理,如下所示:
  • 采用 Ajax 通信的方式上报
  • 利用 Image 对象上报
  1. 错误监控的代码,如下所示:
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>错误监控</title>
    <script type="text/javascript">
      window.addEventListener('error', function (e) {
          console.log('捕获', e);
      }, false);
    </script>
  </head>
  <body>
    <script src="//badu.com/test.js" charset="utf-8"></script>
    <script type="text/javascript">
      (new Image()).src = 'http://baidu.com/tesjk?r=tksjk';
    </script>
  </body>
</html>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值