分时函数
requestIdleCallback 浏览器空闲时执行
<div class="w_680 d_f jc_sb">
<div class="d_f fd_c ai_c">
<button onclick="handleInsert()">插入1万个元素(原始写法)</button>
<div id="idOld"></div>
</div>
<div class="d_f fd_c ai_c">
<button onclick="handleOptimize()">插入1万个元素(优化后的写法)</button>
<div id="idOptimize"></div>
</div>
</div>
<script>
function handleInsert() {
console.time();
let idOld = document.querySelector("#idOld"),
datas = Array.from({ length: 100000 }, (_, i) => i + 1);
for (const item of datas) {
const div = document.createElement("div");
div.textContent = item;
idOld.appendChild(div);
}
console.timeEnd();
}
// 优化方式一(参数归一化)
function handleOptimize() {
console.time();
let datas = Array.from({ length: 100000 }, (_, i) => i + 1),
idOptimize = document.querySelector("#idOptimize"),
taskHandler = (item, i) => {
const div = document.createElement("div");
div.textContent = item;
idOptimize.appendChild(div);
};
performChunk(datas, taskHandler);
console.timeEnd();
// performChunk(100000, taskHandler);
}
function performChunk(datas, taskHandler) {
// 参数归一化
if (typeof datas === "number") datas = Array.from({ length: datas }, (_, i) => i + 1);
if (datas.length === 0) return false;
let i = 0;
function _run() {
if (i >= datas.length) return false;
requestIdleCallback((idle) => {
while (idle.timeRemaining() > 0 && i < datas.length) {
taskHandler(datas[i], i);
i++;
}
_run();
});
}
_run();
}
// 优化方式二(当浏览器不支持requestIdleCallback方法的时候)
function handleOptimize1() {
console.time();
let datas = Array.from({ length: 100000 }, (_, i) => i + 1),
idOptimize = document.querySelector("#idOptimize"),
taskHandler = (item, i) => {
const div = document.createElement("div");
div.textContent = item;
idOptimize.appendChild(div);
},
scheduler = (task) => {
setTimeout(() => {
const start = Date.now();
task(() => Date.now() - start < 50);
}, 100);
};
performChunk1(datas, taskHandler, scheduler);
console.timeEnd();
}
function performChunk1(datas, taskHandler, scheduler) {
if (typeof datas === "number") datas = Array.from({ length: datas }, (_, i) => i + 1);
if (datas.length === 0) return false;
let i = 0;
function _run() {
if (i >= datas.length) return false;
scheduler((isGoOn) => {
while (isGoOn() > 0 && i < datas.length) {
taskHandler(datas[i], i);
i++;
}
_run();
});
}
_run();
}
// 优化方式三(判断环境)
function handleOptimize2() {
console.time();
let datas = Array.from({ length: 100000 }, (_, i) => i + 1),
idOptimize = document.querySelector("#idOptimize"),
taskHandler = (item, i) => {
const div = document.createElement("div");
div.textContent = item;
idOptimize.appendChild(div);
};
browserPerformChunk2(datas, taskHandler);
console.timeEnd();
}
function browserPerformChunk2(datas, taskHandler) {
const scheduler = (task) => {
requestIdleCallback((idle) => {
task(() => idle.timeRemaining() > 0);
});
};
performChunk2(datas, taskHandler, scheduler);
}
function performChunk2(datas, taskHandler, scheduler) {
if (typeof datas === "number") datas = Array.from({ length: datas }, (_, i) => i + 1);
if (datas.length === 0) return false;
let i = 0;
function _run() {
if (i >= datas.length) return false;
scheduler((isGoOn) => {
while (isGoOn() > 0 && i < datas.length) {
taskHandler(datas[i], i);
i++;
}
_run();
});
}
_run();
}
</script>分片
(可以通过 DocumentFragment 的使用,减少 DOM 操作次数,降低回流对性能的影响。并且可以通过 requestAniminationFrame 保证插入新节点操作在页面重绘前执行,二者结合可以实现数据渲染优化。)
关于requestAnimationFrame
requestAnimationFrame会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率。
在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的CPU、GPU和内存使用量。
requestAnimationFrame是由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销
第一种
<ul id="list-with-big-data">1000个好友</ul>
<script>
// 全局变量,先扔这儿
let [ary, ul] = [[], document.querySelector("#list-with-big-data")];
// 生成好友数据,暂拟1000个
for (i = 1; i < 1000; i++) {
ary.push(i);
}
// step1.
console.time();
function renderList() {
for (let i = 0; i < ary.length; i++) {
let li = document.createElement("li");
li.innerHTML = ary[i];
ul.appendChild(li);
}
}
renderList();
console.timeEnd();第二种
console.time();
function renderData(fn, count) {
// renderData方法接受两个参数fn和count,fn为创建节点逻辑方法,count参数表示每批创建节点数量。
// 每次遍历count个数据,传入节点创建函数,同时删除此数据。
let dataChunk = () => {
for (i = 0; i < (Math.min(ary.length, count || 0)); i++) {
let item = ary.shift();
// 将每条数据作为参数传入节点创建函数中。
fn(item);
}
};
// 此处计时器设为10毫秒,当然也改为参数传入。
return () => {
let timeOut = setInterval(function(params) {
// dataChunk方法遍历数据时,逐一将数据删除,当数据为空时,结束计时器。
if (ary.length === 0) {
return clearInterval(timeOut);
}
dataChunk();
}, 10);
};
}
const renderList1 = renderData((n) => {
// 再简单不过的创建节点并插入的过程。 默认第二个参数即count为8,表一次渲染8条数据。
let li = document.createElement("li");
li.innerHTML = n;
ul.appendChild(li);
}, 8);
renderList1();
console.timeEnd();第三种
console.time();
function renderList2(count = 8) {
let dataChunk = function() {
// 创建fragment,fragment详细可移步度娘。 vue双向绑定的实现大多也有用到。
let fragment = document.createDocumentFragment();
for (i = 0; i < (Math.min(ary.length, count || 0)); i++) {
// 与step2大同小异,将节点创建移步此处,并逐条删除数据。
let li = document.createElement("li");
li.innerHTML = ary[0];
// 私以为就是将动态生成的list节点挂载到fragment生成的虚拟dom上,之后再一并插入到ul底下。
fragment.appendChild(li);
ary.shift();
}
;
ul.appendChild(fragment);
renderData();
};
var renderData = () => {
if (ary.length > 0) {
// 此处调用requestAnimationFrame方法,将dataChunk作为参数传入,实现递归,反复的一进一出渲染数据。
window.requestAnimationFrame(dataChunk);
}
};
renderData();
}
renderList2();
console.timeEnd();- setTimeout 延迟加载 (使用不当可能适得其反)
- web worker 多线程 (不能操作dom,主要计算密集型的任务)
- requestIdleCallback 帧空闲时运行 (react18带🔥的,低优先级任务)
- requestAnimationFrame 每帧都会运行 (主要做动画效果调优)
Navigator.sendBeaconNavigator.sendBeacon 是一个 Web API 方法,用于在浏览器后台异步发送数据,通常用于在页面卸载或关闭时发送数据,以确保数据能够被成功传递给服务器,即使页面正在关闭。这对于需要在用户离开页面时进行一些数据收集、日志记录等操作非常有用。
sendBeacon 方法可以接受两个参数:
url:一个字符串,表示要发送数据的目标 URL。
data:一个 ArrayBufferView、Blob、DOMString 或 FormData 对象,包含要发送的数据。
Performance
Performance 是一个与性能相关的API,可以获取到当前页面中与性能相关的信息,它提供了许多用于测量和分析网页性能的关键 API。以下是一些重要的 Performance 接口的 API:
performance.timing: 这个属性提供了一个包含了各种关键时间点的 PerformanceTiming 对象,可以用来测量网页加载的不同阶段,如导航开始、资源加载、DOM 解析等。
performance.now() : 这个方法返回一个高精度的时间戳,用于测量性能。它适用于执行时间间隔的测量,比如代码执行时间。
performance.mark() 和 performance.measure() : 这些方法用于在代码中插入标记点,以便测量不同代码块之间的性能差异。可以使用 performance.mark() 创建标记,然后使用 performance.measure() 来测量两个标记之间的时间间隔。
performance.getEntries() 和 performance.getEntriesByType() : 这些方法用于获取各种性能条目(如资源、导航等)的列表。您可以使用这些方法来分析特定类型的性能数据。
performance.navigation: 这个属性提供了导航类型的信息,帮助您确定是新导航还是页面刷新。
performance.memory: 这个属性提供了有关内存使用情况的信息。它返回一个 PerformanceMemory 对象,包含了内存使用的统计数据。
Performance.timing Performance.timing对象包含了延迟相关的性能信息,window.performance.timing 对象提供了许多属性,用于访问有关网页加载和性能的时间信息。以下是这些属性的列表:
navigationStart: 导航开始的时间戳,即浏览器开始获取当前文档的时间。
unloadEventStart: 前一个文档的 unload 事件开始时间。
unloadEventEnd: 前一个文档的 unload 事件结束时间。
redirectStart: 重定向开始的时间戳,即从前一个文档到当前文档的重定向开始时间。
redirectEnd: 重定向结束的时间戳,即从前一个文档到当前文档的重定向结束时间。
fetchStart: 开始获取文档的时间戳,通常是发起请求的时间。
domainLookupStart: 域名解析开始的时间戳,即浏览器开始解析域名的时间。
domainLookupEnd: 域名解析结束的时间戳,即浏览器完成域名解析的时间。
connectStart: 开始建立连接的时间戳,即浏览器开始建立网络连接的时间。
connectEnd: 完成网络连接的时间戳,即浏览器完成网络连接的时间。
secureConnectionStart: 安全连接开始的时间戳,用于HTTPS连接。
requestStart: 开始发送请求的时间戳,即浏览器开始向服务器发送请求的时间。
responseStart: 开始接收响应的时间戳,即浏览器开始接收服务器响应的时间。
responseEnd: 响应结束的时间戳,即浏览器完成接收服务器响应的时间。
domLoading: 开始解析文档对象模型(DOM)的时间戳。
domInteractive: DOM 变为可交互的时间戳,即用户可以与页面进行交互的时间。
domContentLoadedEventStart: DOMContentLoaded 事件开始的时间戳。
domContentLoadedEventEnd: DOMContentLoaded 事件结束的时间戳。
domComplete: DOM 解析完成的时间戳。
loadEventStart: 加载事件开始的时间戳,即文档加载事件开始的时间。
loadEventEnd: 加载事件结束的时间戳,即文档加载事件结束的时间。
通过这些属性,我们了解页面加载和渲染的各个阶段,从而优化页面性能和用户体验。而这些属性值都是时间戳,以毫秒为单位。
performance.getEntries() Performance.getEntries(FilterOptions),对于给定的filter,此方法返回 PerformanceEntry对象数组,PerformanceEntry对象代表了 performance 时间列表中的单个 metric 数据,metric(MaiTreeKe)可以理解为基本单元,一条记录。手动构建 mark或者measure生成Performance entries, 在资源加载的时候,也会被动生成(例如图片、script、css等资源加载)
通过 performance.getEntries() 获取各个资源请求的 PerformanceEntry 对象,统计耗时 performanceEntries 是指浏览器性能API中的 PerformanceEntry 对象数组。这个数组包含了在页面生命周期中收集的不同类型的性能数据,比如资源加载时间、导航信息、用户交互延迟等等。
这些数据是通过 Performance API 提供的接口,如 performance.getEntries() 或 performance.getEntriesByType(type) 来获取的。在这个返回的数组中,每个 PerformanceEntry 对象都代表了特定的性能指标或事件。不同类型的性能指标(如资源加载、用户交互等)会有不同的属性。
常见的 PerformanceEntry 对象属性包括:
name: 标识资源或事件的名称。
entryType: 表示性能条目的类型,如 "resource"、"navigation" 等。
startTime: 开始时间,表示性能事件的开始时间。
duration: 持续时间,表示性能事件的时长。
initiatorType: 发起者类型,用于资源加载事件,如 "script"、"img" 等。
其他类型特定的属性,如 decodedBodySize、transferSize 等,用于描述资源加载事件。
通过遍历 performanceEntries 数组,可以获取页面中不同类型的性能数据,进而进行分析、优化和监控。这对于了解页面性能并针对性地进行改进非常有帮助。
const ptiming = performance.timing
// 白屏时间
result.blankTime = fix(ptiming.responseStart - ptiming.navigationStart)
// 浏览器发起请求的时间
result.fStartTime = fix(ptiming.fetchStart - ptiming.navigationStart)
// connect开始
result.cStartTime = fix(ptiming.connectStart - ptiming.navigationStart)
// connect结束
result.cEndTime = fix(ptiming.connectEnd - ptiming.navigationStart)
// dns开始时间
result.dnsStartTime = fix(ptiming.domainLookupStart - ptiming.navigationStart)
// dns结束时间
result.dnsEndTime = fix(ptiming.domainLookupEnd - ptiming.navigationStart)