HolyZheng / holyZheng-blog

草稿
36 stars 0 forks source link

前端性能优化思路与具体实践 #6

Open HolyZheng opened 6 years ago

HolyZheng commented 6 years ago

作者:holyZheng 转载请注明出处

参考雅虎35条中的优化方案的实践。随个人实践增多而增加内容。

前端是一种远程部署,运行时增量下载的GUI软件,性能优化主要再两个方面

网络性能

1. 尽量减少http请求数量

CSS Sprites雪碧图

将一些颜色较为丰富的小图片合成一张大图片,在需要使用这些小图片的地方使用同一张大图,通过background-image,background-position属性来定位要显示的部分。

background-image: url(bgimage.gif);
background-position:center;
/*
如果您仅规定了一个关键词,那么第二个值将是"center"。

x% y% 
第一个值是水平位置,第二个值是垂直位置

top left
top center
top right
center left
center center
center right
bottom left
bottom center
bottom right
*/

总大小虽然一样,但是减少了http请求的数量,加快了页面加载数量。 缺点:定位过程麻烦。

font-icon

行内图片(base64编码)

将较小的图片(转base64后会变重)以 data: url的方式嵌入到页面中,减少http请求。

> 缺点:图片编码内嵌入html文件,使得html文件变重

#### **图片懒加载**:
```html
<img data-src="./img/ceng.png" alt="">

当图片出现在用户可视区域的时候才加载图片,减少没必要的图片加载

// web 端
/* 获取元素到达文档顶部的距离
* clientTop元素顶部边框高度
* offsetParent 返回一个最近的(包含层级上的最近)包含该元素的定位元素。
* offsetTop 当前元素相对于其 offsetParent 元素的顶部的距离。
*/
function getTop(el) {
    let top = el.offsetTop;
    let parent = el.offsetParent;
    while(parent !== null) {
        top += parent.offsetTop + parent.clientTop;
        parent = parent.offsetParent;
    }
    return top;
}
document.documentElement.clientHeight; // 视口高度,也就是窗口的高度。
document.documentElement.scrollTop // 视口上部因滚动条滚动而被遮挡部分的高度

// 判断是否到达可视区域
function inSight(el) {
    const clientHeight = document.documentElement.clientHeight;
    const Height = document.documentElement.scrollTop + clientHeight;

    return getTop(el) < Height;
}

// 对图片操作
function loadImg(el) {
    if (!el.src) {
        el.src = el.dataset.src;
    }
}

function checkImgs() {
    const imgs = document.getElementsByTagName('img');
    Array.from(imgs).forEach(el => {
        if (inSight(el)) {
            loadImg(el);
        }
    })
}
// 普通绑定
window.addEventListener('scroll', checkImgs, false);
window.onload = checkImgs;

/* 为避免短时间多次触发,加入一节流函数
*  1. 比较上一次触发和这次触发的时间,如果大于一定值就直接触发
*  2. 如果小于一定值就延迟触发
*  3. 然后如果下一次在触发在这次延迟触发之前的话,取消当前的延迟触发。
*/
function throttle(fun, delay, time) {
    let timeout;
    let previous = new Date();
    return function () {
        let now = new Date();
        if (timeout) clearTimeout(timeout);
        if (now - previous >= time) {
            fun()
            previous = now;
        } else {
            timeout = setTimeout(function () {
                fun()
            }, delay);
        }
    }
}
window.addEventListener('scroll', throttle(checkImgs, 200, 1000), false);
// 小程序端
// createIntersectionObserver() 创建 IntersectionObserver 对象
// relativeToViewport() 指定页面显示区域作为参照区域
// observe 指定目标节点并开始监听
const ctx = this
  let className = '.brief-image-' + ctx.properties.index
  wx.createIntersectionObserver().relativeToViewport().observe(className, res => {
    if (res.intersectionRatio > 0) {
      ctx.setData({
        load: true
      })
    }
  })

缓存控制

2. 减少代码体积,提高下载速度

代码压缩

对JavaScript和css进行压缩,提升加载速度。比如使用gulp或webpack对代码进行压缩

// 获取 uglify 模块(用于压缩 JS)
var uglify = require('gulp-uglify')
// 获取 minify-css 模块(用于压缩 CSS)
var minifyCSS = require('gulp-minify-css')

// 压缩 js 文件
// 在命令行使用 gulp script 启动此任务
gulp.task('script', function() {
    // 1\. 找到文件
    gulp.src('js/*.js')
    // 2\. 压缩文件
        .pipe(uglify())
    // 3\. 另存压缩后的文件
        .pipe(gulp.dest('dist/js'))
})

// 压缩 css 文件
// 在命令行使用 gulp css 启动此任务
gulp.task('css', function () {
    // 1\. 找到文件
    gulp.src('css/*.css')
    // 2\. 压缩文件
        .pipe(minifyCSS())
    // 3\. 另存为压缩文件
        .pipe(gulp.dest('dist/css'))
})
plugins: [
    new UglifyjsWebpackPlugin()
  ]
// ps: webpack4 的代码压缩可通过指定mode为production来实现
mode: “production”

gulp图片压缩

// 使用到gulp-imagemin模块
var gulp = require('gulp')
var imagemin = require('gulp-imagemin')
gulp.task('images', function () {

// 匹配图片
  gulp.src('image/*.*')
// 压缩图片
  .pipe(imagemin({
    progressive: true
   }))
// 输出图片
  .pipe(gulp.dest('dist/images'))
})
// 在命令行使用 gulp auto 启动此任务
gulp.task('auto', function () {
    // 监听文件修改,当文件被修改则执行 images 任务
    gulp.watch('images/*.*)', ['images'])
});

// 使用 gulp.task('default') 定义默认任务
// 在命令行使用 gulp 启动 images 任务和 auto 任务
gulp.task('default', ['images', 'auto'])

tree shaking

移除JavaScript上下文未引用代码(dead-code)

// 在webpack4中通过package.json的“sideEffects”属性作为标记,向compiler提供提示
{
  "name": "your-project",
  "sideEffects": [
// 这里列出具有副作用的代码文件
    "./src/some-side-effectful-file.js",
    "*.css"
  ]
}
// 也可以在module.rules中定义
module.rules: [
  {
    include: path.resolve("node_modules", "lodash"),
    sideEffects: false
  }
]

代码分离(提取公共代码)(可配合浏览器缓存——chunkhash)

比如使用webpack时,提取公共代码模块,减少代码冗余,提高加载速度

// webpack3
new webpack.optimize.CommonsChunkPlugin()

// webpack4 
optimization: {
  splitChunks: {
    // ...
   }
}

运行时性能

避免使用css表达式

表达式最大的问题就是容易出现反复计算的情况,比如

.one {
  width: calc(100% - 100px);
  background-color: expression( (new Date()).getHours()%2 ? "#B8D4FF" : "#F08A00" ); 
/* ie5 引入 ie8开始不支持 */
}

当其父元素宽度发生改变的时候,表达式就会反复计算,而且计算次数会比我们想象中的还多,每次宽度改变都很可能会引起页面的重排或重绘,引起性能的下降。 重绘(Repaint)和回流(Reflow),重绘是当节点需要更改外观而不会影响布局的,比如改变 color 就叫称为重绘。回流是布局或者几何属性需要改变就称为回流。(添加或删除可见原生,改变元素的位置和尺寸,浏览器窗口尺寸改变) 方法

  1. 先通过display:none隐藏元素,再修改,再显示,2次重绘和回流。
  2. 添加多个元素时还可以使用文档片段(DocumentFragment)作为参数(例如,任何 Node 接口类似 Node.appendChild 和 Node.insertBefore) 的方法),这种情况下被添加(append)或被插入(inserted)的是片段的所有子节点, 而非片段本身。因为所有的节点会被一次插入到文档中,而这个操作仅发生一个重渲染的操作,而不是每个节点分别被插入到文档中,因为后者会发生多次重渲染的操作。

尾递归优化

异步并行

当我们使用async/await 去处理多个异步操作的时候要注意异步操作之间是否有依赖或先后关系,如果没有就让多个异步操作并行执行。

let [foo, bar] = await Promise.all([getFoo(), getBar()]);
let firstFinishPromise = await Promise.race([getFoo(), getBar()]); // 第一个处理完的promise

事件委托

通过事件委托来减少绑定到页面上的事件处理程序的数量

<ul id="list">
  <li>item 1</li>
  <li>item 2</li>
  <li>item 3</li>
  ......
  <li>item n</li>
</ul>
// 给父层元素绑定事件
document.getElementById('list').addEventListener('click', function (event) {
  // ie6-8  event.srcElement;
  var target = event.target
  // 判断是否匹配目标元素
  if (target.nodeName.toLocaleLowerCase === 'li') {
    console.log('the Content is: ', target.innerHTML);
  }
});