8000字总结的前端性能优化

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

抛出一个问题,从输入 url 地址栏到所有内容显示到界面上做了哪些事?

  • 1.浏览器向 DNS 服务器请求解析该 URL 中的域名所对应的  IP  地址;
  • 2.建立 TCP 连接(三次握手);
  • 3.浏览器发出读取文件( URL  中域名后面部分对应的文件)的 HTTP  请求,该请求报文作为  TCP  三次握手的第三个报文的数据发送给服务器;
  • 4.服务器对浏览器请求作出响应,并把对应的 html 文本发送给浏览器;

  • 5.浏览器将该  html  文本并显示内容;
  • 6.释放  TCP 连接(四次挥手);

上面这个问题是一个面试官非常喜欢问的问题,我们下面把这6个步骤分解,逐步细谈优化。

一、 DNS 解析

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

    - 走缓存
    - 浏览器DNS缓存
    - 本机DNS缓存
    - 路由器DNS缓存
    - 网络运营商服务器DNS缓存 (80%的DNS解析在这完成的)
    - 递归查询
    

优化策略:尽量允许使用浏览器的缓存,能给我们节省大量时间,下面有 dns-prefetch 的介绍,每次 dns 解析大概需要 20-120秒

二、 TCP 的三次握手

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

  • 第一次握手: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之间可以开始传输数据了。

三、浏览器发送请求

优化策略:

  • 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 连接在传送完最后一个响应后,还需要 hold keepalive_timeout 秒后,才开始关闭这个连接),如果想更详细了解可以看这篇文章[keep-alve性能优化的测试结果][1]
  • 3.使用 webScoket 通信协议,仅一次 TCP 握手就一直保持连接,而且他对二进制数据的传输有更好的支持,可以应用于即时通信,海量高并发场景。[webSocket的原理以及详解][2]
  • 4.减少 HTTP 请求次数,每次 HTTP 请求都会有请求头,返回响应都会有响应头,多次请求不仅浪费时间而且会让网络传输很多无效的资源,使用前端模块化技术  AMD CMD commonJS ES6等模块化方案 将多个文件压缩打包成一个,当然也不能都放在一个文件中,因为这样传输起来可能会很慢,权衡取一个中间值
  • 5.配置使用懒加载,对于一些用户不立刻使用到的文件到特定的事件触发再请求,也许用户只是想看到你首页上半屏的内容,但是你却请求了整个页面的所有图片,如果用户量很大,那么这是一种极大的浪费

  • 6.服务器资源的部署尽量使用同源策略

  • 7.在需要多个 cookie 去辨识用户的多种状况时,使用 session 替代,把数据储存在服务器端或者服务器端的数据库中,这样只需要一个 cookie 传输,节省大量的无效传输,而且储存的数据可以是永久无线大的。
  • 8.使用 preload dns-prefetch prefetch ,预请求资源,这种请求方式不会阻塞浏览器的解析,而且能将预请求的资源缓存起来,而且可以设置 crossorgin 进行跨域资源的缓存,不会推迟首屏的渲染时间,还会加快后面的加载时间,因为后面的本身需要的资源会直接从缓存中读取,而不会走网络请求。
  • 9.使用 defer async 属性的脚本,异步加载的方式,会先发请求,然后 JS 引擎继续解析下面的内容。 async 的属性脚本会无序加载,谁先请求回来就立刻加载谁,当请求回来的时候,无论是在 DOM 解析还是脚本的解析,接下来都先会解析这个 asncy 脚本,它会阻塞 DOM 的解析。 defer 属性的会按 HTML 结构的按顺序加载,在 DOMContentLoad 前加载,但是加载之前所有的 DOM 解析肯定已经完成了, defer 属性的脚本不会阻塞 DOM 的解析,它也叫延迟脚本。 由于实际中它不确定是否在 DOMContentLoaded 前加载,所以一般只放一个 defer 的脚本,参考移动端京东网页 。 [async和defer详解][3]
  • 详情参考[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需要转发的服务端列表,并没有指定分配策略。

  • 默认情况下采用的策略,将所有客户端请求轮询分配给服务端。这种策略是可以正常工作的,但是如果其中某一台服务器压力太大,出现延迟,会影响所有分配在这台服务器下的用户。

  • 最小连接数策略 将请求优先分配给压力较小的服务器,它可以平衡每个队列的长度,并避免向压力大的服务器添加更多的请求。

    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 大都使用了这种配置:

  • 配置GZIP

  • GZIP 是规定的三种标准 HTTP 压缩格式之一。目前绝大多数的网站都在使用 GZIP 传输  HTML、CSS、JavaScript  等资源文件。

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

  • 启用  GZip 所需的 HTTP  最低版本默认值为 HTTP/1.1

  • 启用 gzip 同时需要客户端和服务端的支持,如果客户端支持 gzip 的解析,那么只要服务端能够返回 gzip 的文件就可以启用 gzip 了,我们可以通过 nginx 的配置来让服务端支持gzip。下面的 respone content-encoding:gzip ,指服务端开启了 gzip 的压缩方式。

  • 具体可以看这篇文字文章 [Nginx配置GZIP][5]

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

Nginx 功能非常强大,配置也非常方便,有兴趣的可以多看看这篇文章 [Nginx解析][6]

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

  • 先预解析(将需要发送请求的标签的请求发出去)

  • 从上到下解析 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详解][7],图片优化对性能提升最大。
  • webpack4 配置 代码分割,提取公共代码成单独模块。方便缓存
    /*
    runtimeChunk 设置为 true, webpack 就会把 chunk 文件名全部存到一个单独的 chunk 中,
    这样更新一个文件只会影响到它所在的 chunk 和 runtimeChunk,避免了引用这个 chunk 的文件也发生改变。
    */
    runtimeChunk: true,
    splitChunks: {
      chunks: 'all'  // 默认 entry 的 chunk 不会被拆分, 配置成 all, 就可以了
    }
  }
    //因为是单入口文件配置,所以没有考虑多入口的情况,多入口是应该分别进行处理。
  • 对于需要事件驱动的 webpack4 配置懒加载的,可以看这篇[webpack4优化教程][8],写得非常全面

  • 一些原生 javaScript DOM 操作等优化会在下面总结

六、 TCP 的四次挥手,断开连接

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

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

如何使网页更丝滑?

  • 使用requestAnimationFrame

    • 即便你能保证每一帧的总耗时都小于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))来强制创建一个新层。

  • 有兴趣的可以看看这篇文字 [前端页面优化][9]

  • 样式的切换最好提前定义好 class ,通过 class 的切换批量修改样式,避免多次重绘重排

  • 可以先切换 display:none 再修改样式

  • 多次的 append 操作可以先插入到一个新生成的元素中,再一次性插入到页面中。

  • 代码复用,函数柯里化,封装高阶函数,将多次复用代码封装成普通函数(俗称方法), React 中封装成高阶组件, ES6 中可以使用继承, TypeScript 中接口继承,类继承,接口合并,类合并。

  • 在把数据储存在 localstorage和sessionstorage 中时,可以再自己定义一个模块,把这些数据在内存中存储一份,这样只要可以直接从内存中读书,速度更快,性能更好。

  • 能不定义全局变量就不定义全局变量,最好使用局部变量代替全局变量,查找的速度要高一倍。

  • 强力推荐阅读:[阮一峰ES6教程][10]

  • 以及[什么是TypeScript以及入门][11]

下面加入 React 的性能优化方案:

  • 在生命周期函数 shouldComponentUpdate 中对 this.state prev state 进行浅比较,使用 for-in 循环遍历两者, 只要得到他们每一项值,只要有一个不一样就返回 true ,更新组件。

  • 定义组件时不适用React.component , 使用PureComponent代替,这样 React 机制会自动在 shouldComponentUpdate 中进行浅比较,决定是否更新。

  • 上面两条优化方案只进行浅比较,只对比直接属性的值,当然你还可以在上面加入 this.props prevprops 的遍历比较,因为 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
      
  • 总结:使用以上方式,可以减少不必要的重复渲染。

  • React JSX 语法要求必须包裹一层根标签,为了减少不必要的 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上下文详解][12],但是复杂项目的多个不同层次组件使用到的 state ,必须上 Redux

  • 所有的原生监听事件,定时器等,必须在 componentWillUnmount 中清除,否则大型项目必定会发生内存泄露,极度影响性能!!!

  • React Hooks?

  • 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文档][13] 注意

    • 只能在顶层调用钩子。不要在循环,控制流和嵌套的函数中调用钩子。

    • 只能从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 属性了,相信未来开发会越来越方便。

以上都是根据本人的知识点总结得出,后期还会有更多性能优化方案等出来,路过点个赞收藏收藏~,欢迎提出问题补充~

最后

  • 欢迎加我微信( CALASFxiaotan ),拉你进技术群,长期交流学习...

  • 欢迎关注「 前端巅峰 」,认真学前端,做个有专业的技术人...

点个在看支持我吧,转发就更好了

好文我在看

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章