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