性能优化 - 多方面性能优化

性能优化

CSS性能优化

CSS性能优化的8个技巧

内联首屏关键CSS

减少页面的首要内容出现在屏幕上的时间

大家应该都习惯于通过link标签引用外部CSS文件。但需要知道的是,将CSS直接内联到HTML文档中能使CSS更快速地下载。而使用外部CSS文件时,需要在HTML文档下载完成后才知道所要引用的CSS文件,然后才下载它们。所以说,内联CSS能够使浏览器开始页面渲染的时间提前,因为在HTML下载完成之后就能渲染了。

异步加载CSS

JavaScript动态创建样式表link元素

// 创建link标签
const myCSS = document.createElement( "link" );
myCSS.rel = "stylesheet";
myCSS.href = "mystyles.css";
// 插入到header的最后位置
document.head.insertBefore( myCSS, document.head.childNodes[ document.head.childNodes.length - 1 ].nextSibling );

将link元素的media属性设置为用户浏览器不匹配的媒体类型

// 在文件加载完成之后,将media的值设为screen或all,从而让浏览器开始解析CSS。
<link rel="stylesheet" href="mystyles.css" media="noexist" onload="this.media='all'">

// 通过rel属性将link元素标记为alternate可选样式表,也能实现浏览器异步加载。同样别忘了加载完成之后,将rel改回去。
<link rel="alternate stylesheet" href="mystyles.css" onload="this.rel='stylesheet'">

上述的三种方法都较为古老。现在,rel="preload"5这一Web标准指出了如何异步加载资源,包括CSS类资源。

<link rel="preload" href="mystyles.css" as="style" onload="this.rel='stylesheet'">

注意,as是必须的。忽略as属性,或者错误的as属性会使preload等同于XHR请求,浏览器不知道加载的是什么内容,因此此类资源加载优先级会非常低。as的可选值可以参考上述标准文档。

看起来,rel="preload"的用法和上面两种没什么区别,都是通过更改某些属性,使得浏览器异步加载CSS文件但不解析,直到加载完成并将修改还原,然后开始解析。

但是它们之间其实有一个很重要的不同点,那就是使用preload,比使用不匹配的media方法能够更早地开始加载CSS。所以尽管这一标准的支持度还不完善,仍建议优先使用该方法。

文件压缩

去除无用CSS

有选择地使用选择器

  1. 保持简单,不要使用嵌套过多过于复杂的选择器。
  2. 通配符和属性选择器效率最低,需要匹配的元素最多,尽量避免使用。
  3. 不要使用类选择器和ID选择器修饰元素标签,如h3#markdown-content,这样多此一举,还会降低效率。
  4. 不要为了追求速度而放弃可读性与可维护性。

减少使用昂贵的属性

尽量减少使用昂贵属性,如box-shadow/border-radius/filter/透明度/:nth-child等。

优化重排与重绘

减少重排

避免频繁触发重排操作

  • 改变font-size和font-family
  • 改变元素的内外边距
  • 通过JS改变CSS类
  • 通过JS获取DOM元素的位置相关属性(如width/height/left等)
  • CSS伪类激活
  • 滚动滚动条或者改变窗口大小
避免不必要的重绘

不要使用@import

使用link标签就行了。

原因:

首先,使用@import引入CSS会影响浏览器的并行下载。使用@import引用的CSS文件只有在引用它的那个css文件被下载、解析之后,浏览器才会知道还有另外一个css需要下载,这时才去下载,然后下载后开始解析、构建render tree等一系列操作。这就导致浏览器无法并行下载所需的样式文件。
其次,多个@import会导致下载顺序紊乱。在IE中,@import会引发资源文件的下载顺序被打乱,即排列在@import后面的js文件先于@import下载,并且打乱甚至破坏@import自身的并行下载。
所以不要使用这一方法,使用link标签就行了。

加载技术

脚本加载的优化

通常有三种解决方案:将脚本放在HTML末尾、动态加载脚本以及异步加载脚本。

动态加载

所谓动态加载脚本就是利用javascript代码来加载脚本,通常是手工创建script元素,然后等到HTML文档解析完毕后插入到文档中去。这样就可以很好地控制脚本加载的时机,从而避免阻塞问题。

function loadJS(src) {
  const script = document.createElement('script');
  script.src = src;
  document.getElementsByTagName('head')[0].appendChild(script);
}
loadJS('http://example.com/scq000.js');

异步加载

利用脚本的async和defer属性就可以实现这种需求:

<script type="text/javascript" src="./a.js" async></script>
<script type="text/javascript" src="./b.js" defer></script>

虽然利用了这两个属性的script标签都可以实现异步加载,同时不阻塞脚本解析。但是使用async属性的脚本执行顺序是不能得到保证的。而使用defer属性的脚本执行顺序可以得到保证。另一方面,defer属性是在html文档解析完成后,DOMContentLoaded事件之前就会执行js。async一旦加载完js后就会马上执行,最迟不超过window.onload事件。所以,如果脚本没有操作DOM等元素,或者与DOM时候加载完成无关,直接使用async脚本就好。如果需要DOM,就只能使用defer了。

解决异步加载脚本的问题

// 执行脚本
function exec(src) {
    const script = document.createElement('script');
    script.src = src;

      // 返回一个独立的promise
    return new Promise((resolve, reject) => {
        var done = false;

        script.onload = script.onreadystatechange = () => {
            if (!done && (!script.readyState || script.readyState === "loaded" || script.readyState === "complete")) {
              done = true;

              // 避免内存泄漏
              script.onload = script.onreadystatechange = null;
              resolve(script);
            }
        }

        script.onerror = reject;
        document.getElementsByTagName('head')[0].appendChild(script);
    });
}

function asyncLoadJS(dependencies) {
    return Promise.all(dependencies.map(exec));
}

asyncLoadJS(['https://code.jquery.com/jquery-2.2.1.js', 'https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js']).then(() => console.log('all done'));

懒加载(lazyload)

虚拟代理加载

所谓虚拟代理加载,即为真正加载的对象事先提供一个代理或者说占位符。最常见的场景是在图片的懒加载中,先用一种loading的图片占位,然后再用异步的方式加载图片。等真正图片加载完成后就填充进图片节点中去。

// 页面中的图片url事先先存在其data-src属性上
const lazyLoadImg = function() {
  const images = document.getElementsByTagName('img');
  for(let i = 0; i < images.length; i++) {
      if(images[i].getAttribute('data-src')) {
          images[i].setAttribute('src', images[i].getAttribute('data-src'));
          images[i].onload = () => images[i].removeAttribute('data-src');
      }
  }
}

惰性初始化

利用webpack实现脚本加载优化

import()方法

我们知道,在原生es6的语法中,提供了import和export的方式来管理模块。而其import关键字是被设置成静态的,因此不支持动态绑定。不过在es6的stage 3规范中,引入了一个新的方法import()使得动态加载模块成为可能。所以,你可以在项目中使用这样的代码:

$('#button').click(function() {
  import('./dialog.js')
    .then(dialog => {
        //do something
    })
    .catch(err => {
        console.log('模块加载错误');
    });
});

//或者更优雅的写法
$('#button').click(async function() {
    const dialog = await import('./dialog.js');
  //do something with dialog

});

由于该语法是基于promise的,所以如果需要兼容旧浏览器,请确保在项目中使用es6-promise或者promise-polyfill。同时,如果使用的是babel,需要添加syntax-dynamic-import插件。

require.ensure

预加载

preload规范

preload 是w3c新出的一个标准。利用link的rel属性来声明相关“proload",从而实现预加载的目的。就像这样:

<link rel="preload" href="example.js" as="script">

其中rel属性是用来告知浏览器启用preload功能,而as属性是用来明确需要预加载资源的类型,这个资源类型不仅仅包括js脚本(script),还可以是图片(image),css(style),视频(media)等等。浏览器检测到这个属性后,就会预先加载资源。
这个规范目前兼容性方面还不是很好,所以可以先稍微了解一下。webpack现在也已经有相关的插件,如果感兴趣的话,请移步preload-webpack-plugin。

DNS Prefetch 预解析

还有一个可以优化网页速度的方式是利用dns的预解析技术。同preload类似,DNS Prefetch在网络层面上优化了资源加载的速度。我们知道,针对DNS的前端优化,主要分为减少DNS的请求次数,还有就是进行DNS预先获取。DNS prefetch就是为了实现这后者。其用法也很简单,只要在link标签上加上对应的属性就行了。

<meta http-equiv="x-dns-prefetch-control" content="on" /> /* 这是用来告知浏览器当前页面要做DNS预解析 */
<link rel="dns-prefetch" href="//example.com">

在支持该标准的浏览器上,会自动对链接中的地址域名做DNS解析缓存。不过,像Goolge、火狐这样的现代浏览器即使不设置这个属性,也能在后台做自动预解析。如果你的页面中需要大量访问不同域名的资源,可以利用这项技术加快资源的获取,从而获得更好的用户体验。需要注意的是,DNS预解析虽好,但是也不能滥用。如果对多页面重复DNS预解析,会增加DNS的查询次数。

作者:scq000
链接:https://juejin.im/post/59b73ef75188253db70acdb5
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

posted @ 2021-06-22 17:28  zc-lee  阅读(56)  评论(0编辑  收藏  举报