# RAIL测量模型

  • response:处理事件50ms以内
  • animation:10ms一帧
  • idle:空闲,尽可能增加
  • load:5s内加载并交互

# 性能测试工具

  • chrome devtools
  • lighthouse (npm i -g lighthouse)
  • webpagetest

# webpagetest

webpagetest.org

# lighthouse

npm i -g lighthouse

# lighthouse http://www.bilibili.com 
ctrl shift p调出命令行=>show network request block可以查看哪些请求,并且根据匹配关掉哪些查看是否对页面造成影响
					  =>rendering :绘制查看,有选项配合更好查看回流重绘

# performance 前端性能监控利器

Performance是一个做前端性能监控离不开的API,最好在页面完全加载完成之后再使用,因为很多值必须在页面完全加载之后才能得到。最简单的办法是在window.onload事件中读取各种数据。

// 计算一些关键的性能指标
window.addEventListener('load', (event) => {
    // Time to Interactive
    let timing = performance.getEntriesByType('navigation')[0];
    console.log(timing.domInteractive);
    console.log(timing.fetchStart);
    let diff = timing.domInteractive - timing.fetchStart;
    console.log("TTI: " + diff);
})
// 观察长任务
const observer = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
        console.log(entry)
    }
})

observer.observe({entryTypes: ['longtask']})
// 见面可见性的状态监听
let vEvent = 'visibilitychange';
if (document.webkitHidden != undefined) {
    // webkit prefix detected
    vEvent = 'webkitvisibilitychange';
}

function visibilityChanged() {
    if (document.hidden || document.webkitHidden) {
        console.log("Web page is hidden.")
    } else {
        console.log("Web page is visible.")
    }
}

document.addEventListener(vEvent, visibilityChanged, false);
DNS 解析耗时: domainLookupEnd - domainLookupStart
TCP 连接耗时: connectEnd - connectStart
SSL 安全连接耗时: connectEnd - secureConnectionStart
网络请求耗时 (TTFB): responseStart - requestStart
数据传输耗时: responseEnd - responseStart
DOM 解析耗时: domInteractive - responseEnd
资源加载耗时: loadEventStart - domContentLoadedEventEnd
First Byte时间: responseStart - domainLookupStart
白屏时间: responseEnd - fetchStart
首次可交互时间: domInteractive - fetchStart
DOM Ready 时间: domContentLoadEventEnd - fetchStart
页面完全加载时间: loadEventStart - fetchStart
http 头部大小: transferSize - encodedBodySize
重定向次数:performance.navigation.redirectCount
重定向耗时: redirectEnd - redirectStart

# 浏览器的渲染流程

js=>style=>layout=>paint=>composite

# 布局和绘制

布局(回流):大小和位置,绘制:画出在界面上

# 避免布局抖动
  • 避免回流
  • 读写分离
  • fastdom

# fastdom

cnpm i fastdom --save
fastdom.measure(() => {
  console.log('measure');
});

fastdom.mutate(() => {
  console.log('mutate');
});

fastdom.measure(() => {
  console.log('measure');
});

fastdom.mutate(() => {
  console.log('mutate');
});
//measure
//measure
//mutate
//mutate
const read = fastdom.measure(() => {const width = element.clientWidth;});
const write = fastdom.mutate(() => { element.style.width = width + 'px';});
 
fastdom.clear(read);
fastdom.clear(write);

# 业务场景:给页面上的card设置图片宽度。

// 获取所有的卡片
let cards = documentdocument.getElementsByClassName("MuiCardMedia");

// 轮循更新卡片的图片宽度
const update = (timestamp) => {
	for (let i = 0; i < cards.length; i++ ){
		// 获取offsetTop,设置新的width
		cards[i].style.width = ((math.sin(cards[i].offsetTop + timestamp / 1000 ) + 1) * 500) + 'px';
	}

	window.requestAnimationFrame(update);
}
update(1000);

performance分析结果,load事件之后存在大量的回流,并且chrome都给标记了红色,使用fastDom进行优化,将对 dom 的读和写分离,合并

let cards = document.getElementsByClassName("MuiPaper-rounded");
	const update = (timestamp) => {
	for (let i = 0; i < cards.length; i++) {
	  fastdom.measure(() => {
		let top = cards[i].offsetTop;
		fastdom.mutate(() => {
		  cards[i].style.width =
			Math.sin(top + timestamp / 100 + 1) * 500 + "px";
		});
	  });
	}
	window.requestAnimationFrame(update)
}
update(1000);

performance分析结果,load 事件之后也没有了那么多的红色标记

# 复合线程

图层复合:将页面拆分图层进行绘制再进行复合; (transform opacity,只影响复合,不影响重绘回流)

will-change创建新图层

参考文献 (opens new window)

# 函数的解析方式

V8引擎默认对js函数进行懒解析(lazy parsing),但是如果函数需要立刻执行,则又会进行饥饿解析(eager parsing)

var add=(()=>{})
 //加上括号告诉解释器这句话需要立刻解析而不是懒解析,但是某些构建工具可能会去掉
 //需要使用optimize-js,不过webpack新版本应该没这个问题 

optimize-js可以帮我们把去掉的括号补回来,让函数处于可以饥饿解析状态

cnpm i -g optimize-js 

# 对象优化

  • 以相同顺序初始化对象成员,避免以隐藏类的调整
  • 实例化后避免添加新属性
  • 尽量使用array代替array-like 对象,即使将类数组转为数组需要耗时,但整体操作时间反而在V8引擎上还是会优化的
  • 避免读取超过数组的长度
  • 避免元素类型的转化
/* 1 */
class RectArea { // HC0 
    constructor(l, w) {
        this.l = l; // HC1
        this.w = w; // HC2
    }
}

const rect1 = new RectArea(3,4); // 创建了隐藏类HC0, HC1, HC2
const rect2 = new RectArea(5,6); // 相同的对象结构,可复用之前的所有隐藏类



const car1 = {color: 'red'}; // HC0
car1.seats = 4; // HC1

const car2 = {seats: 2}; // 没有可复用的隐藏类,创建HC2
car2.color = 'blue'; // 没有可复用的隐藏类,创建HC3



/* 2 */
const car1 = {color: 'red'}; // In-object 属性
car1.seats = 4; // Normal/Fast 属性,存储在property store里,需要通过描述数组间接查找 



/* 3 */
Array.prototype.forEach.call(arrObj, (value, index) => { // 不如在真实数组上效率高
  console.log(`${ index }: ${ value }`);
});

const arr = Array.prototype.slice.call(arrObj, 0); // 转换的代价比影响优化小
arr.forEach((value, index) => {
  console.log(`${ index }: ${ value }`);
});



/* 4 */
function foo(array) {
  for (let i = 0; i <= array.length; i++) { // 越界比较
    if(array[i] > 1000) { // 1.沿原型链的查找 2.造成undefined与数进行比较
        console.log(array[i]); // 业务上无效、出错
    }    
  }
}



/* 5 */
const array = [3, 2, 1]; // PACKED_SMI_ELEMENTS

array.push(4.4); // PACKED_DOUBLE_ELEMENTS

# html优化

  • 减小iframe(若真的需要,可以延迟加载)
  • 压缩空白符
  • 避免节点深层次嵌套(节点越多,遍历越慢)
  • 避免table布局
  • 删除注释 借助构建工具
  • css/JS外链
  • 删除元素默认属性
<iframe id="a"></iframe>
<script>
	document.getElementById("a").setAttribute("src","xxxx")
</script>

# css优化

  • 降低css对渲染的阻塞
  • 使用contain属性(兼容性)
  • 利用GPU进行完成动画
  • 使用font-dsiplay属性
以前的css要简单明了,如 .x{} 比y:nth-child(1)>div效率要高很多,现在随着浏览器的改进,这种差距越来越小,谷歌已把性能优化中使用高效的css选择器这条移除了

css contain 属性允许开发者声明当前元素和它的内容尽可能的独立于 DOM 树的其他部分。这使得浏览器在重新计算布局、样式、绘图或它们的组合的时候,只会影响到有限的 DOM 区域,而不是整个页面。

# 图片

  • imagemin插件
  • 图片懒加载:verlok/lazyload|yall.js|blazy
<!--原生某些已经支持懒加载了-->
<img src="xxx" loading="lazy"/>
  • srcset+Sizes 选择响应式图片
  • picture的使用

# font-display

优化字体闪烁问题 (IE和edge不支持),可以用@falce-face引入字体

@font-face{
	font-display:xxx;
	src:local('Long Cang Regular'),url(https://xxxx);
	......
}

参考链接 (opens new window)

# 长列表 虚拟列表优化项目

  1. 方案1:借助scrollTop + slice + 设置好高度 + startIndex + endIndex
<template>
  <div>
    <input type="text" v-model.number="dataLength"><div class="virtual-scroller" @scroll="onScroll" :style="{ height: 600 + 'px' }">
      <div class="phantom" :style="{ height: this.dataLength * itemHeight + 'px' }">
        <ul :style="{ 'margin-top': `${scrollTop}px` }">
          <li v-for="item in visibleList" :key="item.brandId"
            :style="{ height: `${itemHeight}px`, 'line-height': `${itemHeight}px` }">
            <div>
              <div>{{ item.name }}</div>
            </div>
          </li>
        </ul>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "vue-virtual-scroller",
  data() {
    return {
      itemHeight: 60,
      dataLength: 500000, // 总数量
      startIndex: 0,
      endIndex: 10,
      scrollTop: 0
    }
  },
  computed: {
    dataList() {
      const newDataList = [...Array(this.dataLength || 0).keys()].map((v, i) => ({
        brandId: i + 1,
        name: `${i + 1}`,
        height: this.itemHeight
      }));
      return newDataList
    },
    visibleList() {
      console.log(this.startIndex)
      return this.dataList.slice(this.startIndex, this.endIndex)
    }
  },
  methods: {
    onScroll(e) {
      const scrollTop = e.target.scrollTop
      this.scrollTop = scrollTop
      console.log('scrollTop', scrollTop)
      this.startIndex = Math.floor(scrollTop / this.itemHeight)
      this.endIndex = this.startIndex + 10
    }
  }
}
</script>

<style scoped>
.virtual-scroller {
  border: solid 1px #eee;
  margin-top: 10px;
  height: 600px;
  overflow: auto
}

.phantom {
  overflow: hidden
}

ul {
  background: #ccc;
  list-style: none;
  padding: 0;
  margin: 0;
}

li {
  outline: solid 1px #fff;
}
</style>
最后更新: 4/27/2024, 9:52:22 AM