代码区项目交易流程

利用 JavaScript Profiler 分析 Vue 性能问题


在实现 InputNumber 组件的时候,有一个功能是按住 + 或 - 按钮时,组件的值在不断的自增或者自减,具体如下图


利用 JavaScript Profiler 分析 Vue 性能问题

当组件的值自增到一定数量之后,组件会开始卡顿,并且页面上下滚动也会有明显的延迟。

问题体验

相关代码

2. 定位问题 <script> export default { name: "input-number", ... methods: { handleClick(type) { const { step } = this; const period = 10; const timerHandle = () => { const { addDisabled, decDisabled } = this; if (!addDisabled && type === "add") this.inputNumberValue += step; if (!decDisabled && type === "dec") this.inputNumberValue -= step; }; const timer = setInterval(timerHandle, period); const startTime = new Date(); const handler = () => { const endTime = new Date(); if (endTime - startTime < period) timerHandle(); clearInterval(timer); document.removeEventListener("mouseup", handler, false); }; document.addEventListener("mouseup", handler, false); } ... }; </script> 复制代码

首先定位问题发生的位置,直观上感受应该是点击之后不无端自增发生的卡顿,对应代码中的 handleClick 函数,它将 click 事件分为 mousedown 以及 mouseup ,当触发 mousedown 事件时候,调用一个 setInterval 定时执行组件值变化的函数。

初步定位问题应该就发生在 timerHandle 之后,当 inputNumberValue 发生变化之后,它会按照一定的规则来改变 inputValue 的值,从而触发 $emit(input, this.inputValue) 来完成 v-model 。

computed: { inputNumberValue: { get() { return this.inputValue; }, set(value) { // ...一定规则 this.inputValue = limits.find(limit => limit.need(value)).value; } } }, watch: { value: { handler(newVal) { console.timeEnd() this.inputNumberValue = newVal; }, immediate: true }, inputValue(newVal) { this.$emit("input", newVal); } } 复制代码

利用 console.time 以及 console.timeEnd 来排查,那一步发生的卡顿,检测整个 v-model 变化的流程。

也就是在 timerHandle 以及 watch value handler 内添加 console.time 以及 console.timeEnd ,具体如下

const timerHandle = () => { const { addDisabled, decDisabled } = this; if (!addDisabled && type === "add") this.inputNumberValue += step; if (!decDisabled && type === "dec") this.inputNumberValue -= step; console.time(); }; watch: { value: { handler(newVal) { console.timeEnd() this.inputNumberValue = newVal; }, immediate: true } } 复制代码

然后运行,发现运行时间是在不断地增加的,这时候问题的可以归类为,inputNumber 组件的值在不断地变动,导致的 update 的时间会不断地增长。


利用 JavaScript Profiler 分析 Vue 性能问题

接下来要判断具体是哪一句js导致整个页面的 update 时间不断地变长,利用 Chrome 的 javascript Profiler 来完成该工作。打开开发者工具


利用 JavaScript Profiler 分析 Vue 性能问题

利用这个面板你可以追踪网页程序的内存泄漏问题,进一步提升程序的JavaScript执行性能,点击Start 按钮,然后去复现刚才的操作,得到结果如下


利用 JavaScript Profiler 分析 Vue 性能问题

图中标识处有三个模式:

Chart 按时间先后顺序显示的火焰图; Heavy(Bottom Up) 根据对性能的消耗影响列出所有的函数,并可以查看该函数的调用路径; Tree(Top Down) 从调用栈的顶端(最初调用的位置)开始,显示调用结构的总体的树状图情况。

选择 Tree(Top Down) 模式,得到结果如下


利用 JavaScript Profiler 分析 Vue 性能问题

可以看出 flushCallbacksvue 函数占用了74.66%的 Total Time,所以需要对它进行分析


利用 JavaScript Profiler 分析 Vue 性能问题

在它的调用栈中,关键的一步是 Vue._update ,它的主要功能是将 Vnode 渲染成真实DOM,所以上述的卡顿问题果然出现在渲染这一步。

继续分析,发现主要问题在与 updateDirctives 这个函数内,看来问题和指令的更新相关。

最后,发现原来是 highlightBlock 的锅,因为要完成页面中代码高亮的需求,开发了一个指令

import hljs from 'highlight.js/lib/highlight'; Vue.directive ('highlight', function (el) { let blocks = el.querySelectorAll ('code'); Array.prototype.forEach.call (blocks, block => { hljs.highlightBlock (block); }); }); 复制代码

当 InputNumber 组件 v-model 所绑定的父组件 data 变动时候,会导致 v-highlight 指令不断地更新,使得页面卡顿。

3. 解决问题

只需要将该指令的高亮代码的函数写在 bind 里面,这样就只调用一次,指令第一次绑定到元素时调用。

Vue.directive ('highlight', { bind (el) { let blocks = el.querySelectorAll ('code'); Array.prototype.forEach.call (blocks, block => { hljs.highlightBlock (block); }); } }); 复制代码

原创声明: 该文章为原创文章,转载请注明出处。

本文前端(javascript)相关术语:javascript是什么意思 javascript下载 javascript权威指南 javascript基础教程 javascript 正则表达式 javascript设计模式 javascript高级程序设计 精通javascript javascript教程

点击收藏

LAST Epoc.js: JavaScript Brain Sensor Controller Software - HelpDev 从零实现Vue的组件库(九)- InputNumber 实现 NEXT