作者|若冰你不能管理你不能衡量的东西。如果你不能衡量一件事,你就无法管理它。——管理大师彼得德鲁克前言JavaScript是前端应用的主要语言。与其他平台编程语言相比,JS资源大多通过网络加载,因此代码的大小直接影响页面加载执行时间。“无效代码”的多??少直接影响到我们的代码质量,因此衡量代码执行覆盖率是一项重要的预优化工作。什么是代码覆盖率1、死代码死代码也叫无用代码。这个概念应该是编译时静态分析的代码,对执行没有影响。例如://a.jsconsta=1;常量b=2;/*死代码*/exportdefaulta;//index.jssimportafrom'./a.js';exportdefaultfunction(){console.log(a);}通常我们使用TreeShaking来移除这些死代码减少代码大小。2。冗余代码代码覆盖率中提到的冗余代码与死代码略有不同。简单来说,Deadcode适用于compilation,Codecoverage适用于runtime。死代码是在任何情况下都不会执行的代码,因此可以在编译期间将其删除。冗余代码是指某些特定的业务逻辑不会执行这些代码逻辑(例如:加载首屏时,根本不会加载某个前端组件,所以对于“首屏”的业务逻辑用例,前端代码是多余的)。3。代码覆盖率代码覆盖率(Codecoverage)是软件测试的一种度量。也就是说,它描述了测试期间(运行时)执行的源代码占源代码总数的比例。如何衡量代码覆盖率1、Chrome浏览器的DevToolsChrome浏览器的DevTools为我们提供了一个工具Coverage来衡量页面代码(JS、CSS)的覆盖率。使用方法:Devtools——Moretools——Coverage可测代码类型:JSCSS统计可视化形式:使用率以字节计算;当我们选择一个脚本资源时,我们可以在Source栏中看到加载页面上,当前资源运行的代码(蓝色)和还没有运行的代码(红色);缺点:很明显,目前网页上的绝大部分JS脚本,基本上都是经过混淆、压缩、打包后的产物。对于开发者来说,这种覆盖可读性和参考价值不大。TIPS:当然,如果你有源图的话,也可以用浏览器查看源码的覆盖率:(1)在source选项卡中找到当前页面的js资源文件(当然已经混淆了面目全非)。(2)输入sourcemapURL(以def发布平台为例,可以在构建结果中找到)。(3)在webpack://目录下,可以查看对应源码的大概覆盖率(但是没有消耗值)。那么问题来了,有没有办法让开发者了解源代码的代码覆盖率的价值呢?2.Istanbul(NYC)这个软件以土耳其最大的城市伊斯坦布尔命名,因为土耳其的地毯世界闻名,地毯就是用来盖的。Istanbul或NYC(纽约市,基于istanbul实现)是一个衡量JavaScript程序代码覆盖率的工具。目前,大多数节点代码测试框架都使用该工具来获取测试报告。它有四个衡量维度:linecoverage(线路覆盖)Coverage-每行是否执行)【一般我们关注的是这个信息】functioncoverage(函数覆盖-是否调用每个函数)branchcoverage(分支覆盖-是否每一个if代码blockisexecuted)语句覆盖率(statementcoverage-每条语句是否执行)可测代码类型:JSTS统计可视化形式:HTMLterminal缺点:目前还没有非侵入式的方案可以使用istanbul,就是在编译构建时修改构建结果的方法,嵌入到统计代码中,然后在运行时显示统计信息。我们可以使用babel-plugin-istanbul插件对源代码进行AST级别的打包和重写。这种编译方式也称为代码检测/检测。3.stake构建如果我们要衡量这段代码中哪些代码被执行了,哪些代码没有被执行,我们会怎么做呢?//add.jsfunctionadd(a,b){returna+b}module.exports={add}我们很容易想到在我们的源代码中添加一些“装饰”代码,然后当代码逐行执行到某处,那么我们在全局环境变量中记录一下://全局对象记录__coverage__记录了上面代码中语句和函数的执行次数constc=(window.__coverage__={//"f"表示次数oftimeseachfunctionisexecuted//当前代码只有一个函数,所以f数组只有一个并且记录值为0f:[0],//"s"表示每条语句执行的次数//3个语句都赋0s:[0,0,0],})//函数定义是A语句(statement),那么我们+1c.s[0]++functionadd(a,b){//如果add函数(function)被调用,f+1,调用语句s+1c.f[0]++c.s[1]++returna+b}//add调用语句s+1c.s[2]++module.exports={add}istabul做同样的事情,babel-plugin-istanbul在构建过程中解析AST并添加相应的统计单元(语句、函数、分支等)作为装饰代码,代码运行后最终输出一个json格式的数据:{"/Users/bairuobing/test/istanbul.js":{"path":"/Users/bairuobing/test/istanbul.js","s":{"1":1,"2":0,"3":1},"b":{},"f":{"1":0},"fnMap":{//function"1"的起止位置信息:{"name":"add","line":1,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":19}}}},"statementMap":{//语句的起止位置信息"1":{"start":{"line":1,“列”:0},“结束”:{“行”:3,“列”:1}},“2”:{“开始”:{“行”:2,“列”:4},“结束”:{“行”:2,“列”:16}},“3”:{“开始”:{“line":4,"column":0},"end":{"line":4,"column":24}}},"branchMap":{//分支起止位置信息}}}当我们运行代码后,得到上面的json后就可以消费了#terminalformoutputnycreport--reporter=text#HTMLformoutputnycreport--reporter=lcov--exclude-after-remap=falseterminalHTML代码覆盖在Application中iHomeRax开发包Tbox中的tips:tbox是一个消费者一户一户的本地开发包,既然我们知道了源码的代码覆盖率,那我们可以用它来为性能优化做些什么贡献呢?当主工程bundle比较大的时候,那么通过解包大型/无用的前端组件来瘦身首屏的主JS包是一个可行的选择,此时可以根据代码覆盖率来决定优化哪些代码。1.代码拆分React.lazy为我们提供了一个很好的思路,就是利用动态加载模块规范import()的能力(webpackparsesimport()intocodesegmentation)实现前端组件代码的懒加载/动态加载。受此启发,为什么不动态加载一些组件来换取精简主页包呢?//动态导入组件//ThisIsBigModimport{createElement,useState,useEffect}from'rax';exportdefault(props)=>{const[AsyncMod,setAsyncMod]=useState(null);useEffect(()=>{constload=async()=>{constModule=awaitimport('./ThisIsBigMod');//keytry{setAsyncMod(Module);}catch(e){console.log(e);}};加载();},[]);如果(!AsyncMod||!AsyncMod.default){返回null;}return
