前端性能优化不彻底手册

性能优化是一门大学问,本文仅对我的一些积累知识的阐述,欢迎下面补充。

抛出一个问题,从输入 url地址栏到全部内容显示到界面上作了哪些事?
  • 1.浏览器向 DNS 服务器请求解析该 URL 中的域名所对应的 IP 地址;
  • 2.创建TCP链接(三次握手);
  • 3.浏览器发出读取文件(URL 中域名后面部分对应的文件)的HTTP 请求,该请求报文做为 TCP 三次握手的第三个报文的数据发送给服务器;
  • 4.服务器对浏览器请求做出响应,并把对应的 html 文本发送给浏览器;
  • 5.浏览器将该 html 文本并显示内容;
  • 6.释放 TCP链接(四次挥手);
上面这个问题是一个面试官很是喜欢问的问题,咱们下面把这6个步骤分解,逐步细谈优化。

1、DNS 解析

  • DNS`解析:将域名解析为ip地址 ,由上往下匹配,只要命中便中止css

    • 走缓存
    • 浏览器DNS缓存
    • 本机DNS缓存
    • 路由器DNS缓存
    • 网络运营商服务器DNS缓存 (80%的DNS解析在这完成的)
    • 递归查询
优化策略:尽可能容许使用浏览器的缓存,能给咱们节省大量时间,下面有 dns-prefetch的介绍,每次 dns解析大概须要 20-120秒

2、TCP的三次握手

  • SYN (同步序列编号)ACK(确认字符)html

    • 第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等 待Server确认。
    • 第二次握手:Server收到数据包后由标志位SYN=1知道Client请求创建链接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认链接请求,Server进入SYN_RCVD状态。
    • 第三次握手:Client收到确认后,检查ack是否为J+1,ACK是否为1,若是正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,若是正确则链接创建成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间能够开始传输数据了。

3、浏览器发送请求

优化策略:
    • 1.HTTP协议通讯最耗费时间的是创建TCP链接的过程,那咱们就可使用HTTP Keep-Alive,在HTTP 早期,每一个HTTP 请求都要求打开一个TCP socket链接,而且使用一次以后就断开这个TCP链接。 使用keep-alive能够改善这种状态,即在一次TCP链接中能够持续发送多份数据而不会断开链接。经过使用keep-alive机制,能够减小TCP链接创建次数,也意味着能够减小TIME_WAIT状态链接,以此提升性能和提升http服务器的吞吐率(更少的tcp链接意味着更少的系统内核调用
    • 2.可是,keep-alive并非免费的午饭,长时间的TCP链接容易致使系统资源无效占用。配置不当的keep-alive,有时比重复利用链接带来的损失还更大。因此,正确地设置keep-alive timeout时间很是重要。(这个keep-alive_timout时间值意味着:一个http产生的tcp链接在传送完最后一个响应后,还须要holdkeepalive_timeout秒后,才开始关闭这个链接),若是想更详细了解能够看这篇文章keep-alve性能优化的测试结果
    • 3.使用webScoket通讯协议,仅一次TCP握手就一直保持链接,并且他对二进制数据的传输有更好的支持,能够应用于即时通讯,海量高并发场景。webSocket的原理以及详解
    • 4.减小HTTP请求次数,每次HTTP请求都会有请求头,返回响应都会有响应头,屡次请求不只浪费时间并且会让网络传输不少无效的资源,使用前端模块化技术 AMD CMD commonJS ES6等模块化方案将多个文件压缩打包成一个,固然也不能都放在一个文件中,由于这样传输起来可能会很慢,权衡取一个中间值
    • 5.配置使用懒加载,对于一些用户不马上使用到的文件到特定的事件触发再请求,也许用户只是想看到你首页上半屏的内容,可是你却请求了整个页面的全部图片,若是用户量很大,那么这是一种极大的浪费
    • 6.服务器资源的部署尽可能使用同源策略
    • 7.在须要多个cookie去辨识用户的多种情况时,使用session替代,把数据储存在服务器端或者服务器端的数据库中,这样只须要一个cookie传输,节省大量的无效传输,并且储存的数据能够是永久无线大的。
    • 8.使用preloaddns-prefetchprefetch,预请求资源,这种请求方式不会阻塞浏览器的解析,并且能将预请求的资源缓存起来,并且能够设置crossorgin进行跨域资源的缓存,不会推迟首屏的渲染时间,还会加快后面的加载时间,由于后面的自己须要的资源会直接从缓存中读取,而不会走网络请求。
    • 9.使用deferasync属性的脚本,异步加载的方式,会先发请求,而后JS引擎继续解析下面的内容。async的属性脚本会无序加载,谁先请求回来就马上加载谁,当请求回来的时候,不管是在DOM解析仍是脚本的解析,接下来都先会解析这个asncy脚本,它会阻塞DOM的解析。defer属性的会按HTML结构的按顺序加载,在DOMContentLoad前加载,可是加载以前全部的DOM解析确定已经完成了,defer属性的脚本不会阻塞DOM的解析,它也叫延迟脚本。因为实际中它不肯定是否在DOMContentLoaded前加载,因此通常只放一个defer的脚本,参考移动端京东网页async和defer详解
    • 详情参考preload和prefetch详解

    4、服务器返回响应,浏览器接受到响应数据

    一直没想到这里使用什么优化手段,今晚想到了,使用 Nginx反向代理服务器,主要是对服务器端的优化。
    • Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,并在一个BSD-like 协议下发行。其特色是占有内存少,并发能力强,事实上nginx的并发能力确实在同类型的网页服务器中表现较好,中国大陆使用nginx网站用户有:百度、京东、新浪、网易、腾讯、淘宝等。
    • Nginx 是一个安装很是的简单、配置文件很是简洁(还可以支持perl语法)、Bug很是少的服务。Nginx 启动特别容易,而且几乎能够作到7*24不间断运行,即便运行数个月也不须要从新启动。你还可以不间断服务的状况下进行软件版本的升级。
    • 它能够:解决跨域,请求过滤,配置gzip,负载均衡,静态资源服务器 等...
    • 把服务窗口想像成咱们的后端服务器,然后面终端的人则是无数个客户端正在发起请求。负载均衡就是用来帮助咱们将众多的客户端请求合理的分配到各个服务器,以达到服务端资源的充分利用和更少的请求时间。
    • Nginx如何实现负载均衡前端

      • nginx如何实现负载均衡
    Upstream指定后端服务器地址列表
        upstream balanceServer {
            server 10.1.22.33:12345;
            server 10.1.22.34:12345;
            server 10.1.22.35:12345;
        }
        复制代码在server中拦截响应请求,并将请求转发到Upstream中配置的服务器列表。
            server {
                server_name  fe.server.com;
                listen 80;
                location /api {
                    proxy_pass http://balanceServer;
                }
            }
    • 上面的配置只是指定了nginx须要转发的服务端列表,并无指定分配策略。
    • 默认状况下采用的策略,将全部客户端请求轮询分配给服务端。这种策略是能够正常工做的,可是若是其中某一台服务器压力太大,出现延迟,会影响全部分配在这台服务器下的用户。
    • 最小链接数策略

    将请求优先分配给压力较小的服务器,它能够平衡每一个队列的长度,并避免向压力大的服务器添加更多的请求。java

    upstream balanceServer {
            least_conn; //配置压力较小的服务器
            server 10.1.22.33:12345;
            server 10.1.22.34:12345;
            server 10.1.22.35:12345;
        }
    • 依赖于NGINX Plus,优先分配给响应时间最短的服务器。
    upstream balanceServer {
        fair; //配置响应时间最短的服务器
        server 10.1.22.33:12345;
        server 10.1.22.34:12345;
        server 10.1.22.35:12345;
    }
    • 客户端ip绑定
    来自同一个ip的请求永远只分配一台服务器,有效解决了动态网页存在的session共享问题。
    upstream balanceServer {
        ip_hash; //配置1个IP永远只分配一台服务器
        server 10.1.22.33:12345;
        server 10.1.22.34:12345;
        server 10.1.22.35:12345;
    }
    • 配置静态资源服务器
    location ~* \.(png|gif|jpg|jpeg)$ {
        root    /root/static/;  
        autoindex on;
        access_log  off;
        expires     10h;# 设置过时时间为10小时          
    }
    复制代码匹配以png|gif|jpg|jpeg为结尾的请求,
    并将请求转发到本地路径,root中指定的路径即nginx
    本地路径。同时也能够进行一些缓存的设置。
    • Nginx解决跨域
    nginx解决跨域的原理
    例如:
    
    前端server的域名为:fe.server.com
    后端服务的域名为:dev.server.com
    
    如今我在fe.server.com对dev.server.com发起请求必定会出现跨域。
    如今咱们只须要启动一个nginx服务器,将server_name设置为fe.server.com,
    而后设置相应的location以拦截前端须要跨域的请求,最后将请求代理回dev.server.com。
    以下面的配置:
    server {
            listen       80;
            server_name  fe.server.com;
            location / {
                    proxy_pass dev.server.com;
            }
    }
    复制代码这样能够完美绕过浏览器的同源策略:fe.server.com访问nginx的fe.server.com
    属于同源访问,而nginx对服务端转发的请求不会触发浏览器的同源策略。
    • 最重要的一点来了,如今的BATJ大都使用了这种配置:react

      • 配置GZIPwebpack

        • GZIP是规定的三种标准HTTP压缩格式之一。目前绝大多数的网站都在使用GZIP传输 HTML、CSS、JavaScript 等资源文件。
        • 对于文本文件,GZip 的效果很是明显,开启后传输所需流量大约会降至 1/4 ~ 1/3。
        • 启用 GZip 所需的HTTP 最低版本默认值为HTTP/1.1
        • 启用gzip同时须要客户端和服务端的支持,若是客户端支持gzip的解析,那么只要服务端可以返回gzip的文件就能够启用gzip了,咱们能够经过nginx的配置来让服务端支持gzip。下面的responecontent-encoding:gzip,指服务端开启了gzip的压缩方式。
      • 具体能够看这篇文字文章 Nginx配置GZIP

    对于文本文件,GZip 的效果很是明显,开启后传输所需流量大约会降至 1/4 ~ 1/3。nginx

    Nginx功能很是强大,配置也很是方便,有兴趣的能够多看看这篇文章 Nginx解析

    5、浏览器解析数据,绘制渲染页面的过程

    • 先预解析(将须要发送请求的标签的请求发出去)
    • 从上到下解析html文件
    • 遇到HTML标签,调用html解析器将其解析DOM
    • 遇到css标记,调用css解析器将其解析CSSOM
    • link 阻塞 - 为了解决闪屏,全部解决闪屏的样式
    • style 非阻塞,与闪屏的样式不相关的
    • DOM树和CSSOM树结合在一块儿,造成render
    • layout布局 render渲染
    • 遇到script标签,阻塞,调用js解析器解析js代码,可能会修改DOM树,也可能会修改CSSOM
    • DOM树和CSSOM树结合在一块儿,造成render
    • layout布局 render渲染(重排重绘)
    • script标签的属性 asnyc defer
    性能优化策略:
    • 须要阻塞的样式使用link引入,不须要的使用style标签(具体是否须要阻塞看业务场景)
    • 图片比较多的时候,必定要使用懒加载,图片是最须要优化的,webpack4中也要配置图片压缩,能极大压缩图片大小,对于新版本浏览器可使用webp格式图片webP详解,图片优化对性能提高最大。
    • webpack4配置 代码分割,提取公共代码成单独模块。方便缓存
    /*
        runtimeChunk 设置为 true, webpack 就会把 chunk 文件名所有存到一个单独的 chunk 中,
        这样更新一个文件只会影响到它所在的 chunk 和 runtimeChunk,避免了引用这个 chunk 的文件也发生改变。
        */
        runtimeChunk: true, 
        splitChunks: {
          chunks: 'all'  // 默认 entry 的 chunk 不会被拆分, 配置成 all, 就能够了
        }
      }
        //由于是单入口文件配置,因此没有考虑多入口的状况,多入口是应该分别进行处理。
    • 对于须要事件驱动的webpack4配置懒加载的,能够看这篇webpack4优化教程,写得很是全面
    • 一些原生javaScriptDOM操做等优化会在下面总结

    6、TCP的四次挥手,断开链接


    终结篇:性能只是 load 时间或者 DOMContentLoaded 时间的问题吗?

    • RAILes6

      • Responce 响应,研究代表,100ms内对用户的输入操做进行响应,一般会被人类认为是当即响应。时间再长,操做与反应之间的链接就会中断,人们就会以为它的操做有延迟。例如:当用户点击一个按钮,若是100ms内给出响应,那么用户就会以为响应很及时,不会察觉到丝毫延迟感。
      • Animaton 现现在大多数设备的屏幕刷新频率是60Hz,也就是每秒钟屏幕刷新60次;所以网页动画的运行速度只要达到60FPS,咱们就会以为动画很流畅。
      • Idle RAIL规定,空闲周期内运行的任务不得超过50ms,固然不止RAIL规定,W3C性能工做组的Longtasks标准也规定了超过50毫秒的任务属于长任务,那么50ms这个数字是怎么得来的呢?浏览器是单线程的,这意味着同一时间主线程只能处理一个任务,若是一个任务执行时间过长,浏览器则没法执行其余任务,用户会感受到浏览器被卡死了,由于他的输入得不到任何响应。为了达到100ms内给出响应,将空闲周期执行的任务限制为50ms意味着,即便用户的输入行为发生在空闲任务刚开始执行,浏览器仍有剩余的50ms时间用来响应用户输入,而不会产生用户可察觉的延迟。
      • Load若是不能在1秒钟内加载网页并让用户看到内容,用户的注意力就会分散。用户会以为他要作的事情被打断,若是10秒钟还打不开网页,用户会感到失望,会放弃他们想作的事,之后他们或许都不会再回来。
      如何使网页更丝滑?
      • 使用requestAnimationFrameweb

        • 即使你能保证每一帧的总耗时都小于16ms,也没法保证必定不会出现丢帧的状况,这取决于触发JS执行的方式。假设使用 setTimeout 或 setInterval 来触发JS执行并修改样式从而致使视觉变化;那么会有这样一种状况,由于setTimeout 或 setInterval没有办法保证回调函数何时执行,它可能在每一帧的中间执行,也可能在每一帧的最后执行。因此会致使即使咱们能保障每一帧的总耗时小于16ms,可是执行的时机若是在每一帧的中间或最后,最后的结果依然是没有办法每隔16ms让屏幕产生一次变化,也就是说,即使咱们能保证每一帧整体时间小于16ms,但若是使用定时器触发动画,那么因为定时器的触发时机不肯定,因此仍是会致使动画丢帧。如今整个Web只有一个API能够解决这个问题,那就是requestAnimationFrame,它能够保证回调函数稳定的在每一帧最开始触发。
      • 避免FSL 面试

        • 先执行JS,而后在JS中修改了样式从而致使样式计算,而后样式的改动触发了布局、绘制、合成。但JavaScript能够强制浏览器将布局提早执行,这就叫 强制同步布局FSL

          //读取offsetWidth的值会致使重绘
           const newWidth = container.offsetWidth;
             
            //设置width的值会致使重排,可是for循环内部
            代码执行速度极快,当上面的查询操做致使的重绘
            尚未完成,下面的代码又会致使重排,并且这个重
            排会强制结束上面的重绘,直接重排,这样对性能影响
            很是大。因此咱们通常会在循环外部定义一个变量,这里
            面使用变量代替container.offsetWidth;
           boxes[i].style.width = newWidth + 'px';
          }
      • 使用transform属性去操做动画,这个属性是由合成器单独处理的,因此使用这个属性能够避免布局与绘制。
      • 使用translateZ(0)开启图层,减小重绘重排。特别在移动端,尽可能使用transform代替absolute。建立图层的最佳方式是使用will-change,但某些不支持这个属性的浏览器可使用3D 变形(transform: translateZ(0))来强制建立一个新层。
      • 有兴趣的能够看看这篇文字 前端页面优化
      • 样式的切换最好提早定义好class,经过class的切换批量修改样式,避免屡次重绘重排
      • 能够先切换display:none再修改样式
      • 屡次的append 操做能够先插入到一个新生成的元素中,再一次性插入到页面中。
      • 代码复用,函数柯里化,封装高阶函数,将屡次复用代码封装成普通函数(俗称方法),React中封装成高阶组件,ES6中可使用继承,TypeScript中接口继承,类继承,接口合并,类合并。
      • 在把数据储存在localstorage和sessionstorage中时,能够再本身定义一个模块,把这些数据在内存中存储一份,这样只要能够直接从内存中读书,速度更快,性能更好。
      • 能不定义全局变量就不定义全局变量,最好使用局部变量代替全局变量,查找的速度要高一倍。
      • 强力推荐阅读:阮一峰ES6教程
      • 以及什么是TypeScript以及入门


    下面加入 React的性能优化方案:
    • 在生命周期函数shouldComponentUpdate中对this.stateprev state进行浅比较,使用for-in循环遍历二者,

    只要获得他们每一项值,只要有一个不同就返回true,更新组件。

    • 定义组件时不适用React.component , 使用PureComponent代替,这样React机制会自动在shouldComponentUpdate中进行浅比较,决定是否更新。
    • 上面两条优化方案只进行浅比较,只对比直接属性的值,固然你还能够在上面加入this.propsprevprops的遍历比较,由于shouldComponentUpdate的生命周期函数自带这两个参数。若是props 和 state 的值比较复杂,那么可使用下面这种方式去进行深比较。
    • 解决:

      • 保证每次都是新的值
      • 使用 immutable-js 库,这个库保证生成的值都是惟一的

        var map1 = Immutable.Map({ a: 1, b: 2, c: 3 });
        var map2 = map1.set('b', 50);
        map1.get('b'); // 2
        map2.get('b'); // 50
    • 总结:使用以上方式,能够减小没必要要的重复渲染。
    • ReactJSX语法要求必须包裹一层根标签,为了减小没必要要的DOM层级,咱们使用Fragment标签代替,这样渲染时候不会渲染多余的DOM节点,让DIFF算法更快遍历。
    • 使用Redux管理全局多个组件复用的状态。
    • React构建的是SPA应用,对SEO不够友好,能够选择部分SSR技术进行SEO优化。
    • Ant-design这类的UI组件库,进行按需加载配置,从import Button from 'antd' 的引入方式,变成import {Button} from antd的方式引入。(相似Babel7中的runtime和polifill的区别).
    • React中一些数据的须要更新,可是却不急着使用,或者说每次更新的这个数据不须要更新组件从新渲染的,能够按期成类的实例上的属性,这样能减小屡次的重复无心义的DIFF和渲染。
    • Redux的使用要看状况使用,若是只是一个局部状态(仅仅是一个组件或者父子组件使用就不要使用Redux)。对于一个父子、父子孙多层组件须要用到的state数据,也可使用context上下文去传递. Context上下文详解,可是复杂项目的多个不一样层次组件使用到的state,必须上Redux
    • 全部的原生监听事件,定时器等,必须在componentWillUnmount中清除,不然大型项目一定会发生内存泄露,极度影响性能!!!
    • React Hooks是什么?
      用来定义有状态和生命周期函数的纯函数组件(在过去纯函数组件是没有状态和生命周期函数的~)
      Hooks是React v16.7.0-alpha中加入的新特性,并向后兼容。

      • 什么是钩子(Hook)本质就是函数,能让你使用React组件的状态和生命周期函数
      • 让代码更加可复用,不用在定义繁杂的HOC(高阶组件)和class组件
      • 使用:

        useState(initValue)
          - const [ state, setState ] = React.useState(initValue);
          - 用来定义状态数据和操做状态数据的方法
        useEffect(function)
          - useEffect(() => { do something })
          - 反作用函数(发请求获取数据、订阅事件、修改DOM等)
          - 本质上就是一个生命周期函数,至关于componentDidMount 、 componentDidUpdate 和 componentWillUnmount
        useContext(Context)
          - context指的是React.createContext返回值
        
        ------ 如下Hooks只使用于特殊场景,须要时在用 -----
        useReducer
          - const [state, dispatch] = useReducer(reducer, initialState);
          - 一个 useState 替代方案,至关于redux
        useCallback
          - useCallback(fn, inputs)
          - 至关于 shouldComponentUpdate,只有inputs的值发生变化才会调用fn
        useMemo(create, inputs)
          - 至关于useCallback

    • 更多详见官方文档:HOOKS文档
      注意

      • 只能在顶层调用钩子。不要在循环,控制流和嵌套的函数中调用钩子。
      • 只能从React的函数式组件中调用钩子。不要在常规的JavaScript函数中调用钩子。-(此外,你也能够在你的自定义钩子中调用钩子。)
    原生 JavaScript实现懒加载:
    • 懒加载,从字面意思就能够简单的理解为不到用时就不去加载,对于页面中的元素,咱们能够这样理解:只有当滚动页面内容使得本元素进入到浏览器视窗时(或者稍微提早,需给定提早量),咱们才开始加载图片;
    • 不给img元素的src属性赋值时,不会发出请求【不能使src="",这样即便只给src赋了空值也会发出请求】,而一旦给src属性赋予资源地址值,那么该请求发出,使得图片显示;因此这里咱们利用这一点控制img元素的加载时机。在开始的时候将资源url放置在自定义属性data-src当中,而后在须要加载的时候获取该属性并赋值给元素的src属性
    • 从上面的分析能够看出来,主要要解决的问题就是怎么检测到元素是否在视窗当中,这里咱们要借助于dom操做api当中的el.getBoundingClientRect()来获取其位置,并判断是否在视窗内,这里简单描述。
    • Element.getBoundingClientRect()方法返回元素的大小及其相对于视口的位置。返回值是一个 DOMRect 对象,这个对象是由该元素的 getClientRects() 方法返回的一组矩形的集合, 即:是与该元素相关的CSS 边框集合 。DOMRect 对象包含了一组用于描述边框的只读属性——left、top、right和bottom,单位为像素。除了 width 和 height 外的属性都是相对于视口的左上角位置而言的。

      • 所以咱们可使用如下逻辑判断元素是否进入视窗:
    function isInSight(el){
                var eldom = typeof el == 'object'?el:document.querySelector(el);
                var bound = eldom.getBoundingClientRect();
                // 这里的bound包含了el距离视窗的距离;
                // bound.left是元素距离窗口左侧的距离值;
                // bound.top是袁术距离窗口顶端的距离值;
    
                // 以以上两个数值判断元素是否进入视窗;
                var clientHeigt = window.innerHeight;
                var clientWidth = window.innerWidth;
                // return (bound.top>=0&&bound.left>=0)&&(bound.top<=window.innerHeight+20)&&(bound.left<=window.innerWidth+20);
                return !((bound.top>clientHeigt)||(bound.bottom<0)||(bound.left>clientWidth)||(bound.right<0))
            }

    • 其中window.innerHeight和window.innerWidth分别为视窗的高度和宽度,之因此加上20是为了让懒加载稍稍提早,使用户体验更好;
    • 添加scroll事件监听:

      • 那么何时去检测元素是否在视窗内,并判断是否加载呢,这里因为页面的滚动会使得元素相对于视窗的位置发生变化,也就是说滚动会改变isInSight的结果,因此这里咱们在window上添加scroll事件监听:
    // 当加载完成,检测并加载可视范围内的图片
            window.onload= checkAllImgs;
            // 添加滚动监听,便可视范围变化时检测当前范围内的图片是否能够加载了
            window.addEventListener("scroll",function(){
                checkAllImgs();
            })
    
            // 检测全部图片,并给视窗中的图片的src属性赋值,即开始加载;
            function checkAllImgs(){
                var imgs = document.querySelectorAll("img");
                Array.prototype.forEach.call(imgs,function(el){
                    if(isInSight(el)){
                        loadImg(el);
                    }
                })
            }
            // 开始加载指定el的资源
            function loadImg(el){
                var eldom = typeof el == 'object'?el:document.querySelector(el);
                if(!eldom.src){
                   // 懒加载img定义如:<div class="img"><img  alt="加载中" data-index=7 data-src="http://az608707.vo.msecnd.net/files/MartapuraMarket_EN-US9502204987_1366x768.jpg"></div>
                    var source = eldom.getAttribute("data-src");
                    var index = eldom.getAttribute("data-index");
                    eldom.src = source; 
                    console.log("第"+index+"张图片进入视窗,开始加载。。。。")
                }
                
            }
    • 这样就实现了图片的懒加载的简单实现,固然还能够对scroll进行优化等操做。
    如今最新版本的谷歌浏览器也要支持 <img>标签的内部 loading属性了,相信将来开发会愈来愈方便。 以上都是根据本人的知识点总结得出,后期还会有更多性能优化方案等出来,路过点个赞收藏收藏~,欢迎提出问题补充~
    相关文章
    相关标签/搜索