对浏览器原理的一些理解和记录

本文是学习浏览器原理的知识笔记,部分地方加注了自己的理解,可能比较主观或存在错误,希望各位看官海涵。其中部分图片来源于网络,如有侵权,请及时联系在下删除,感谢。

目录

1.浏览器内核总结

2.输入URL到界面加载的过程

3.缓存相关知识

4. 浏览器的本地存储

5.浏览器的解析过程*

6.浏览器的渲染过程 *

7.重绘、回流与合成

8.为什么HTTPS数据传输更安全?

9.事件的防抖和节流

10.图片懒加载(建议看视频讲解)

11.浏览器的异步相关问题

1.浏览器内核总结

这里直接背就可以,注意部分浏览器引擎的发展历史

                                               图1:浏览器内容及引擎总结表                    

2.输入URL到界面加载的过程

                                                     图2:浏览器加载过程流程图

1、浏览器的地址栏输入URL并按下回车

2、浏览器查找当前URL是否存在缓存,并比较缓存是否过期。(查找强缓存)

3、DNS解析URL对应的IP,浏览器提供了DNS数据缓存功能(DNS计网基础,自行补充)

4、根据IP建立TCP连接(三次握手)。(Chrome 在同一个域名下要求同时最多只能有 6 个 TCP 连接,超过 6 个的话剩下的请求就得等待)

PS:TCP 连接通过什么手段来保证数据传输的可靠性?一是三次握手确认连接,二是数据包校验保证数据到达接收方,三是通过四次挥手断开连接。

5、HTTP发起请求, 共发送请求头、请求体、请求行三种

// 请求行:由请求方法、请求URI和HTTP版本协议。请求方法是GET,路径为根路径,HTTP协议版本为1.1
GET / HTTP/1.1
​
//请求头:之前说的Cache-Control、If-Modified-Since、If-None-Match都由可能被放入请求头中作为缓存的标识信息
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cache-Control: no-cache
Connection: keep-alive
Cookie: /* 省略cookie信息 */
Host: www.baidu.com
Pragma: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1
​
//请求体:只有在Post方法下存在,常见的场景是表单提交

6、服务器处理请求,浏览器接收HTTP响应。其中,网络响应具有三个部分:响应行响应头响应体

//响应行,由HTTP协议版本、状态码和状态描述组成
HTTP/1.1 200 OK
​
//请求头:包含了服务器及其返回数据的一些信息, 服务器生成数据的时间、返回的数据类型以及对即将写入的Cookie信息
Cache-Control: no-cache
Connection: keep-alive
Content-Encoding: gzip
Content-Type: text/html;charset=utf-8
Date: Wed, 04 Dec 2019 12:29:13 GMT
Server: apache
Set-Cookie: rsv_i=f9a0SIItKqzv7kqgAAgphbGyRts3RwTg%2FLyU3Y5Eh5LwyfOOrAsvdezbay0QqkDqFZ0DfQXby4wXKT8Au8O7ZT9UuMsBq2k; path=/; domain=.baidu.com

7、渲染页面,构建DOM树(详见后面的解析)

8、关闭TCP连接(四次挥手)(计网基础,三次挥手是朋友,四次挥手是路人)

PS:在相应完成后,不一定TCP连接断开,需要判断Connection字段。如果请求头或响应头中包含Connection: Keep-Alive,表示建立了持久连接,这样TCP连接会一直保持,之后请求统一站点的资源会复用这个连接;反之则断开连接

3.缓存相关知识

A:为什么要缓存?简述一下缓存机制?

答:缓存主要是为了节约网络资源加速浏览。缓存机制主要分为强缓存 - 协商缓存 - 缓存位置

强缓存:浏览器中的缓存作用分为两种情况,一种是需要发送HTTP请求,一种是不需要发送。首先是检查强缓存,这个阶段不需要发送HTTP请求。HTTP1.0使用的是Expires,用具体时间标识缓存是否过期。HTTP1.1使用Cache-Control,使用max-age过期时长控制缓存,同时Cache-Control可以配合很多命令一起使用

// HTTP1.0 Expires
Expires: Wed, 22 Nov 2019 08:41:00 GMT
//HTTP1.1 Cache-Control
Cache-Control:max-age=3600
//Cache-Control常见的配合命令
private: 这种情况就是只有浏览器能缓存了,中间的代理服务器不能缓存。
​
no-cache: 跳过当前的强缓存,发送HTTP请求,即直接进入`协商缓存阶段`。
​
no-store:非常粗暴,不进行任何形式的缓存。
​
s-maxage:这和`max-age`长得比较像,但是区别在于s-maxage是针对代理服务器的缓存时间。

协商缓存:强缓存失效之后,浏览器在请求头中携带相应的缓存tag来向服务器发请求,由服务器根据这个tag,来决定是否使用缓存,这就是协商缓存。主要分为Last-ModifiedETag,各有优劣:

1:Last-Modified :最后修改时间,浏览器会在请求头上携带If-Modified-Since的字段,和这个服务器中该资源的最后修改时间对比:

  • 如果请求头中的这个值小于最后修改时间,说明是时候更新了。

  • 返回新的资源,跟常规的HTTP请求响应的流程一样

2:ETag:ETag 是服务器根据当前文件的内容,给文件生成的唯一标识,只要里面的内容有改动,这个值就会变。浏览器请求头发送If-None-Match字段,服务器接收到If-None-Match后,会跟服务器上该资源的ETag进行比对:

  • 如果两者不一样,说明要更新了。返回新的资源,跟常规的HTTP请求响应的流程一样。

  • 否则返回304,告诉浏览器直接用缓存。

3:两者对比:

  1. 精准度上,ETag优于Last-Modified。优于 ETag 是按照内容给资源上标识,因此能准确感知资源的变化。而 Last-Modified 就不一样了,它在一些特殊的情况并不能准确感知资源变化,主要有两种情况:

    • 编辑了资源文件,但是文件内容并没有更改,这样也会造成缓存失效。

    • Last-Modified 能够感知的单位时间是秒,如果文件在 1 秒内改变了多次,那么这时候的 Last-Modified 并没有体现出修改了。

  2. 性能上,Last-Modified优于ETag,也很简单理解,Last-Modified仅仅只是记录一个时间点,而 Etag需要根据文件的具体内容生成哈希值。

另外,如果两种方式都支持的话,服务器会优先考虑ETag

B:缓存位置

浏览器中的缓存位置一共有四种,按优先级从高到低排列分别是: Service Worker -> Memory Cache -> Disk Cache -> Push Cache

  • Service Worker 借鉴了 Web Worker的 思路,即让 JS 运行在主线程之外,是一个服务器与浏览器之间的中间人角色。如果网站中注册了service worke,那么它可以拦截当前网站所有的请求,进行判断(需要编写相应的判断程序)。如果需要向服务器发起请求的就转给服务器,如果可以直接使用缓存的就直接返回缓存不再转给服务器。从而大大提高浏览体验。由于它脱离了浏览器的窗体,因此无法直接访问DOM。虽然如此,但它仍然能帮助我们完成很多有用的功能,比如离线缓存消息推送网络代理等功能。其中的离线缓存就是 Service Worker Cache

  • Memory Cache指的是内存缓存,从效率上讲它是最快的。但是从存活时间来讲又是最短的,当渲染进程结束后,内存缓存也就不存在了。

  • Disk Cache就是存储在磁盘中的缓存,从存取效率上讲是比内存缓存慢的,但是他的优势在于存储容量和存储时长。比较大的JS、CSS文件会直接被丢进磁盘,反之丢进内存 - 内存使用率比较高的时候,文件优先进入磁盘

  • Push Cache即推送缓存,这是浏览器缓存的最后一道防线。它是 HTTP/2 中的内容,设置了Last-Modifed但没有设置Cache-Control或者Expires时触发,也就是只拿到最后更新时间,但没有设置过期时间,这种情况下浏览器会有一个默认的缓存策略push cache,自动设置过期时间:(Date - Last-Modified)*0.1,也就是当前时间减去最后更新时间后再乘10%。

C:总结

对浏览器的缓存机制来做个简要的总结:

首先通过 Cache-Control 验证强缓存是否可用 - 如果强缓存可用,直接使用 - 否则进入协商缓存,即发送 HTTP 请求,服务器通过请求头中的If-Modified-Since或者If-None-Match字段检查资源是否更新 - 若资源更新,返回资源和200状态码 - 否则,返回304,告诉浏览器直接从缓存获取资源

4. 浏览器的本地存储

浏览器的本地存储主要分为CookieWebStorageIndexedDB, 其中WebStorage又可以分为localStoragesessionStorage

A:Cookie

HTTP 协议是一个无状态协议,客户端向服务器发请求,服务器返回响应,故事就这样结束了,但是下次发请求如何让服务端知道客户端是谁呢?这种背景下,就产生了 Cookie。Cookie 本质上就是浏览器里面存储的一个很小的文本文件,内部以键值对的方式来存储。向同一个域名下发送请求,都会携带相同的 Cookie,服务器拿到 Cookie 进行解析,便能拿到客户端的状态。首先,客户端向服务器发送HTTP请求,服务器收到HTTP请求后,在相应头中添加一个set-cookiie字段。浏览器收到响应后保存cookie,之后对该服务器每一次请求中都通过Cookie字段将Cookie信息发送给服务器。cookie的过期时间主要由Last-ModifiedETag设定。

Cookie 的作用很好理解,就是用来做状态存储的,但它也是有诸多致命的缺陷的:

  1. 容量缺陷。Cookie 的体积上限只有4KB,只能用来存储少量的信息。

  2. 性能缺陷。Cookie 紧跟域名,不管域名下面的某一个地址需不需要这个 Cookie ,请求都会携带上完整的 Cookie,这样随着请求数的增多,其实会造成巨大的性能浪费的,因为请求携带了很多不必要的内容。

  3. 安全缺陷。由于 Cookie 以纯文本的形式在浏览器和服务器中传递,很容易被非法用户截获,然后进行一系列的篡改,在 Cookie 的有效期内重新发送给服务器,这是相当危险的。另外,在HttpOnly为 false 的情况下,Cookie 信息能直接通过 JS 脚本来读取。HttpOnly原则

B:localStorage

和Cookie异同,localStorage有一点跟Cookie一样,就是针对一个域名,即在同一个域名下,会存储相同的一段localStorage。利用localStorage的较大容量和持久特性,可以利用localStorage存储一些内容稳定的资源,比如官网的logo,存储Base64格式的图片资源,因此利用localStorage

不过它相对Cookie还是有相当多的区别的:

  • 容量:localStorage 的容量上限为5M,相比于Cookie的 4K 大大增加。当然这个 5M 是针对一个域名的,因此对于一个域名是持久存储的

  • 只存在客户端:默认不参与与服务端的通信。这样就很好地避免了 Cookie 带来的性能问题和安全问题。

  • 接口封装:通过localStorage暴露在全局,并通过它的 setItem 和 getItem等方法进行操作,非常方便。

C:sessionStorage

应用场景:对表单信息进行维护,将表单信息存储在里面,可以保证页面即使刷新也不会让之前的表单信息丢失;可以用它存储本次浏览记录。如果关闭页面后不需要这些记录,用sessionStorage就再合适不过了。事实上微博就采取了这样的存储方式。

sessionStorage和localStorage一下内容一致:

  • 容量。容量上限也为 5M。

  • 只存在客户端,默认不参与与服务端的通信。

  • -接口封装。除了sessionStorage名字有所变化,存储方式、操作方式均和localStorage一样。

sessionStoragelocalStorage有一个本质的区别,那就是前者只是会话级别的存储,并不是持久化存储。会话结束,也就是页面关闭,这部分sessionStorage就不复存在了。

D:IndexedDB

IndexedDB是运行在浏览器中的非关系型数据库, 本质上是数据库, 理论上容量没有上限。除了拥有数据库本身的特性,比如支持事务存储二进制数据,还有这样一些特性需要格外注意:

  • 键值对存储:内部采用对象仓库存放数据,在这个对象仓库中数据采用键值对的方式来存储

  • 异步操作:数据库的读写属于 I/O 操作, 浏览器中对异步 I/O 提供了支持

  • 受同源策略限制:即无法访问跨域的数据库。

E:安全补充(新开文章总结)

HTTPOnly、SameSite、CSRF等

5.浏览器的解析过程*

                                                           图3:浏览器解析过程流程图

由于浏览器无法直接理解HTML字符串,因此将这一系列的字节流转换为一种有意义并且方便操作的数据结构,这种数据结构就是DOM树DOM树本质上是一个以document为根节点的多叉树。

DOM树:解析器首先会创建一个document对象。标记生成器会把每个标记的信息发送给建树器建树器接收到相应的标记时,会创建对应的 DOM 对象。创建这个DOM对象后会做两件事情: 1. 将DOM对象加入 DOM 树中。 2. 将对应标记压入存放开放(与闭合标签意思对应)元素的栈中。

DOM样式:先格式化与标准化,之后计算每个节点的具体样式信息,主要遵从两个规则:继承和层叠 –> 每个子节点都会默认继承父节点的样式属性,如果父节点中没有找到,就会采用浏览器默认样式,也叫UserAgent样式

生成布局树:1. 遍历生成的 DOM 树节点,并把他们添加到布局树中。 2. 计算布局树节点的坐标位置。

6.浏览器的渲染过程 *

浏览器的渲染主要分为以下几个模块:

1、HTML解析器:解释HTML文档的解析器,主要作用是将HTML文本解释为DOM树

2、CSS解析器:它的作用是为DOM中的各个元素对象计算出样式信息,为布局提供基础设施

3、JavaScript引擎:JavaScript引擎能够解释JavaScript代码,并通过DOM接口和CSS接口来修改网页内容 和样式信息,从而改变渲染的结果

4、布局(layout):在DOM创建之后,WebKit需要将其中的元素对象同样式信息结合起来,计算他们的大小位置等布局信息,形成一个能表达着所有信息的内部表示模型

5、绘图模块(paint):使用图形库将布局计算后的各个网页的节点绘制成图像结果

渲染分为以下几个步骤:

1、浏览器会从上到下解析文档

2、遇见HTML标记,调用HTML解析器解析为对应的token(一个token就是一个标签文本的序列化)并构建DOM树(就是一块内存,保存着tokens,建立他们之间的关系)

3、遇见style/link标记调用相应解析器处理CSS标记,并构建出CSS样式树

PS:

  • style标签中的样式:由HTML解析器进行解析,不会阻塞浏览器渲染(可能会产生“闪屏现象”),不会阻塞DOM解析

  • link引入的CSS样式:

    • 由CSS解析器进行解析,阻塞浏览器渲染:因为HTML和CSS是并行解析的,所以CSS不会阻塞HTML解析

    • 会阻塞整体页面的DOM渲染:因为最后要渲染必须CSS和HTML一起解析完并合成一处(Render Tree));

    • 会阻塞后面的js语句执行.

      • JavaScript 是可操纵 DOM 和 css 样式的, 如果在修改这些元素属性同时渲染界面(即 JavaScript 线程和 UI 线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了

      • 脚本的内容是获取元素的样式,宽高等CSS控制的属性,浏览器是需要计算的,也就是依赖于CSS。浏览器也无法感知脚本内容到底是什么,为避免样式获取,因而只好等前面所有的样式下载完后,再执行JS

    • 不阻塞DOM的解析

  • 优化:使用CDN节点进行外部资源加速,对CSS进行压缩,优化CSS代码(不要使用太多层选择器)

4、遇见script标记,调用JavaScript引擎处理script标记,绑定事件,修改DOM树/CSS树等

PS:

  • js会阻塞后续DOM的解析,原因是:浏览器不知道后续脚本的内容,如果先去解析了下面的DOM,而随后的js删除了后面所有的DOM,那么浏览器就做了无用功,浏览器无法预估脚本里面具体做了什么操作,例如像document.write这种操作,索性全部停住,等脚本执行完了,浏览器再继续向下解析DOM

  • js会阻塞页面渲染,原因是:js中也可以给DOM设置样式,浏览器等该脚本执行完毕,渲染出一个最终结果,避免做无用功。

  • js会阻塞后续js的执行,原因是维护依赖关系,例如:必须先引入jQuery再引入bootstrap

  • 无论css阻塞,还是js阻塞,都不会阻塞浏览器加载外部资源(图片、视频、样式、脚本等)

    原因:浏览器始终处于一种:先把请求发出去的工作模式,只要是涉及到网络请求的内容,无论是:图片、样式、脚本,都会先发送请求去获取资源,至于资源到本地之后什么时候用,由浏览器自己协调。这种做法效率很高。

5、将DOM树与CSS合并成一个渲染树

6、根据渲染树来渲染,以计算每个节点的几何信息(这一过程需要依赖GPU)

7、最终将各个节点绘制在屏幕上

7.重绘、回流与合成

渲染流水线流程如下:(图片来源知乎@神三,如果侵权,请联系立刻删除)

                                                        图4:浏览器渲染过程流程图

A:回流(也称重排)

触发条件:当我们对 DOM 结构的修改引发 DOM 几何尺寸变化的时候,会发生回流的过程。相当于将解析和合成的过程重新又走了一篇,开销是非常大的。有以下的操作会触发回流:

  • 一个 DOM 元素的几何属性变化,常见的几何属性有widthheightpaddingmarginlefttopborder 等等, 这个很好理解。

  • 使 DOM 节点发生增减或者移动。当DOM 结构发生改变,则重新渲染 DOM 树,然后将后面的流程(包括主线程之外的任务)全部走一遍,包括生成DOM树、计算样式、生成布局树、建图层树生成绘制列表

  • 读写 offset族、scroll族和client族属性的时候,浏览器为了获取这些值,需要进行回流操作。 调用 window.getComputedStyle 方法。

  • 常见属性有:width、 top、 text-align 、height、 bottom、overflow-y 、 padding、 left、 font-weight 、margin、overflow 、 display、 position、font-family border-width、float、 line-height 、 border

B:重绘

触发条件:当 DOM 的修改导致了样式的变化,并且没有影响几何属性的时候,会导致重绘(repaint)。只会修改“计算样式”、“绘制列表”两个阶段。

即有:重绘不一定导致回流,但回流一定发生了重绘。常见触发重绘的属性如下:color、 background、 outline-color 、border-style、outline 、border-radius、 outline-style 、background-size、 box-shadow

C:合成

利用 CSS3 的transformopacityfilter这些属性就可以实现合成的效果,也就是大家常说的GPU加速。在合成的情况下,会直接跳过布局和绘制流程,直接进入非主线程处理的部分,即直接交给合成线程处理。交给它处理有两大好处: 1. 能够充分发挥GPU的优势。合成线程生成位图的过程中会调用线程池,并在其中使用GPU进行加速生成,而GPU 是擅长处理位图数据的。2.没有占用主线程的资源,即使主线程卡住了,效果依然能够流畅地展示。

D: 指导意义/优化方案

  1. 将多次改变样式属性的操作合为一次,避免频繁使用 style,而是采用修改class的方式,。例:元素位置移动变换时尽量使用CSS3的transform来代替top,left等操作,不使用table布局

  2. 使用createDocumentFragment进行批量的 DOM 操作。

  3. 对于 resize、scroll 等进行防抖/节流处理。

  4. 添加 will-change: tranform ,让渲染引擎为其单独实现一个图层,当这些变换发生时,仅仅只是利用合成线程去处理这些变换,而不牵扯到主线程,大大提高渲染效率。当然这个变化不限于tranform, 任何可以实现合成效果的 CSS 属性都能用will-change来声明

  5. 利用文档素碎片(documentFragment),vue使用了该方式提升性能

  6. 动画实现过程中,启用GPU硬件加速:transform:tranlateZ(0)。同时也可为动画元素新建图层,提高动画元素的z-index

  7. 编写动画时,尽量使用requestAnimationFrame

8.为什么HTTPS数据传输更安全?

原理是在HTTPTCP之间建立了一个中间层,当HTTPTCP通信时并不是像以前那样直接通信,直接经过了一个中间层进行加密,将加密后的数据包传给TCP, 响应的,TCP必须将数据包解密,才能传给上面的HTTP。这个安全层称之为安全层,核心作用就是对数据进行加密。

在安全层中,主要有对称加密、非对称加密、对称与非对称加密结合、添加数字证书方法来保证数据安全。具体原理自行百度

9.事件的防抖和节流

A:节流(throttle)

节流的核心思想: 如果在定时器的时间范围内再次触发,则不予理睬,等当前定时器完成,才能启动下一个定时器任务。这就好比公交车,10 分钟一趟,10 分钟内有多少人在公交站等我不管,10 分钟一到我就要发车走人!通常利用函数节流,让函数有节制地执行,而不是毫无节制的触发一次就执行一次,规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。通常与setTimeout方法一起进行

适用场景:鼠标的点击事件,比如mousedown只触发一次;监听滚动事件,比如是否滑到底部加载更多;游戏中子弹发射的频率

const throttle = function(fn, interval) {
  let last = 0;
  return function (...args) {
    let context = this;
    let now = +new Date();
    // 还没到时间
    if(now - last < interval) return;
    last = now;
    fn.apply(this, args)
  }
}

B:防抖(debounce)

每次事件触发则删除原来的定时器,建立新的定时器。跟王者荣耀回城功能类似,你反复触发回城功能,那么只认最后一次,从最后一次触发开始计时。

函数防抖是指在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。

适用场景:Search搜索,用户不断输入值时,用防抖来节约Ajax请求,也就是输入框事件;window触发resize时,不断调整浏览器大小会不断触发这个事件,用防抖让其只触发一次

C:加强版节流

原因:防抖有时候触发的太频繁会导致一次响应都没有,我们希望到了固定的时间必须给用户一个响应

function throttle(fn, delay) {
  let last = 0, timer = null;
  return function (...args) {
    let context = this;
    let now = new Date();
    if(now - last < delay){
      clearTimeout(timer);
      setTimeout(function() {
        last = now;
        fn.apply(context, args);
      }, delay);
    } else {
      // 这个时候表示时间到了,必须给响应
      last = now;
      fn.apply(context, args);
    }
  }
}

10.图片懒加载(建议看视频讲解)

A:方法一:滚动事件

//首先给图片一个占位资源:,data-src表示自定义属性,存放真正需要显示的图片路径,
当页面滚动直至此图片出现在可视区域时,用js取到该图片的data-src的值赋给src

<img src="default.jpg" data-src="http://www.xxx.com/target.jpg" />

//通过监听 scroll 事件(鼠标滚动)来判断图片是否到达视口。当图片距离视窗顶部的距离(imageTop)
大于窗口显示区的高度(window.innerHeight),表明图片不能看到,不用加载。
也要对scroll事件做节流,以免频繁触发:

window.addEventListener('scroll', throttle(lazyload, 200));
​

//一些属性的补充
页可见区域宽: document.body.clientWidth;
网页可见区域高: document.body.clientHeight;
网页可见区域宽: document.body.offsetWidth (包括边线的宽);
网页可见区域高: document.body.offsetHeight (包括边线的宽);
网页正文全文宽: document.body.scrollWidth;
网页正文全文高: document.body.scrollHeight;
网页被卷去的高: document.body.scrollTop;
网页被卷去的左: document.body.scrollLeft;
网页正文部分上: window.screenTop;
网页正文部分左: window.screenLeft;
屏幕分辨率的高: window.screen.height;
屏幕分辨率的宽: window.screen.width;
屏幕可用工作区高度: window.screen.availHeight;
 

B:方法二:DOM元素的getBoundingClientRect方法

function lazyload() {
  for(let i = count; i <num; i++) {
    // 元素现在已经出现在视口中
    if(img[i].getBoundingClientRect().top < document.documentElement.clientHeight) {
      if(img[i].getAttribute("src") !== "default.jpg") continue;
      img[i].src = img[i].getAttribute("data-src");
      count ++;
    }
  }
}

C:方法三:浏览器提供的构造函数IntersectionObserver(推荐)

这是浏览器内置的一个API,实现了监听window的scroll事件判断是否在视口中以及节流三大功能。

let img = document.getElementsByTagName("img");
​
//IntersectionObserver是一个函数,所以需要创建一个实例对象
const observer = new IntersectionObserver(changes => {                    
  //changes 是被观察的元素集合,是个数组
  for(let i = 0, len = changes.length; i < len; i++) {
    let change = changes[i];
    
    if(change.isIntersecting) {            // 通过这个属性判断是否在视口中,即是否进行到可视窗口,看到了图片
      const imgElement = change.target;   //如果看见了目标元素(targrt),则获取图表节点
      imgElement.src = imgElement.getAttribute("data-src");   //修改自定义属性data-src为常规的src属性
      observer.unobserve(imgElement);     //在图片加载出来后,停止观察元素以实现节流功能
    }
  }
})
Array.from(img).forEach(item => observer.observe(item));

11.浏览器的异步相关问题

A:defer和async的区别

两者都是异步去加载外部JS文件,不会阻塞DOM解析

Async是在外部JS加载完成后,浏览器空闲时,Load事件触发前执行,标记为async的脚本并不保证按照指定他们的先后顺序执行,该属性对于内联脚本无作用 (即没有「src」属性的脚本)。

defer是在JS加载完成后,整个文档解析完成后,触发 DOMContentLoaded 事件前执行,如果缺少 src 属性(即内嵌脚本),该属性不应被使用,因为这种情况下它不起作用


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值