深入了解JavaScript代码覆盖

它为什么是有用的?

作为一名JavaScript开发者,你可能经常发现自己处于代码覆盖可能有用的情景。例如:

  • 对测试套件的质量感兴趣? 重构一个大型的遗留项目? 代码覆盖可以准确显示代码库中已覆盖了哪些部分。
  • 想快速了解是否覆盖了代码库的特定部分? 代码覆盖可以显示有关应用程序的哪些部分已被执行的实时信息,而不是使用console.log进行printf-风格的调试或手动执行代码。
  • 或者你可能正在优化速度,并想知道要关注哪些点? 执行次数可以指出关键函数和循环。

JavaScript在V8中的代码覆盖

今年早些时候,我们在V8上添加了对JavaScript代码覆盖的原生支持。5.9版本中的初始发布提供了函数粒度(显示已执行的函数)的覆盖范围,后来扩展为支持在v6.2中的块粒度覆盖(同样的,仅对于单独表达式有效)。

函数粒度(左侧)和块粒度(右侧)

对JavaScript开发者

目前访问覆盖信息有两种主要的方式。对于JavaScript开发者,Chrome DevTools的Coverage tab给出了JS (和CSS)覆盖率并在源码面板中指出了无用代码。

块覆盖coverage 在DevTools Coverage 面板中的块覆盖。覆盖的行使用绿色标注,未覆盖的行则使用红色。

基于V8覆盖数据的Istanbul.js报告

给嵌入式

嵌入式及框架作者可以通过直接hook到Inspector API上获得更大的灵活性。V8提供两种不同的覆盖模式:

1.尽力覆盖模式下收集覆盖信息,确保在运行时对性能的影响最小,但可能会丢失已被垃圾回收(GC)函数的数据。

2.精确覆盖确保不会因为GC而丢失任何数据,用户可以选择接收执行计数而不是二进制覆盖信息;但性能可能会受此额外开销的影响(有关详细信息,请参阅下一节)。精准覆盖可以按函数或块粒度收集信息。

精准覆盖的Inspector API如下:

  • Profiler.startPreciseCoverage(callCount, detailed) 使能覆盖信息收集,可选调用次数(vs.二进制覆盖)以及块粒度(vs. 函数粒度);
  • Profiler.takePreciseCoverage() 返回已收集的覆盖信息,其中包含源码范围列表以及相关的执行次数;
  • Profiler.stopPreciseCoverage() 禁用收集并释放相关数据结构。

Inspector协议间的通信可能如下所示:

// The embedder directs V8 to begin collecting precise coverage.
{ "id": 26, "method": "Profiler.startPreciseCoverage",
"params": { "callCount": false, "detailed": true }}
// Embedder requests coverage data (delta since last request).
{ "id": 32, "method":"Profiler.takePreciseCoverage" }
// The reply contains collection of nested source ranges.
{ "id": 32, "result": { "result": [{
"functions": [
{
"functionName": "fib",
"isBlockCoverage": true, // Block granularity.
"ranges": [ // An array of nested ranges.
{
"startOffset": 50, // Byte offset, inclusive.
"endOffset": 224, // Byte offset, exclusive.
"count": 1
}, {
"startOffset": 97,
"endOffset": 107,
"count": 0
}, {
"startOffset": 134,
"endOffset": 144,
"count": 0
}, {
"startOffset": 192,
"endOffset": 223,
"count": 0
},
]},
"scriptId": "199",
"url": "file:///coverage-fib.html"
}
]
}}
// Finally, the embedder directs V8 to end collection and
// free related data structures.
{"id":37,"method":"Profiler.stopPreciseCoverage"}

同理,尽力覆盖可以使用 Profiler.getBestEffortCoverage() 。

幕后细节

如上一节所述,V8支持两种主要的代码覆盖模式:尽力和精确覆盖。欲了解他们实现概述,请继续阅读。

尽力覆盖

尽力和精确覆盖模式都大量重用其它的V8机制,其中首数被称为调用计数器的机制。每次通过V8的Ignition解释器调用函数时,我们都会在函数的反馈向量上增加其调用计数器。随着函数后来变得愈加频繁并通过优化编译器做了提升,这个计数器用于帮助辅助关于内联函数的内联决策;现在,我们也依靠它报告代码覆盖情况。

第二种重用机制确立了函数的源码范围。报告代码覆盖时,调用计数需要与源文件中的相关范围作关联。例如,在下面的示例中,我们不仅需要报告函数f已经执行了一次,还包含f的源码范围从第1行开始到第3行结束。

function f() {
console.log('Hello World');
}
f();

又一次我们是幸运的,我们能够重用 V8 中的现有信息。由于 Function.prototype.toString 需要知道函数在原文件中的位置以提取适当的子字符串,函数已经知道它们在源代码中的起始位置和结束位置。

在收集到最优的覆盖范围时,这两种机制简单地结合在一起:首先,我们通过遍历整个堆来找到所有存活的函数。对于每个可见的函数,我们报告调用次数(存储在反馈向量中,我们可以从函数中访问)和源范围(方便存储在函数本身)。

请注意,由于无论是否启用 coverage,都会维护调用计数,因此尽力服务的覆盖不会引入任何运行时开销。它也不使用专用的数据结构,因此既不需要显式启用也无需显式禁用。

那么为什么这种模式称为尽力服务(best-effort)呢,它的局限性是什么? 超出范围的函数可能会被垃圾回收器释放掉。这意味着相关的调用计数将会丢失,事实上我们完全忘记了这些函数曾经存在过。 因此“尽力服务”:即使我们尽力了,所收集的覆盖信息也可能不完整。

精准覆盖 (函数粒度)

与尽力服务模式相比,精确覆盖可确保所提供的覆盖信息是完整的。为实现这一目标,我们会在启用精准覆盖后将所有反馈向量添加到V8的根参考集中,从而阻止GC对其进行回收。虽然这确保了信息无丢失,但它通过人为地保持对象存活增加内存开销。

精准覆盖模式还可以提供执行计数。这为精准覆盖实施增加了另一个窍门。回想一下,每次通过V的解释器调用函数时,调用计数器都会递增,并且一旦函数访问频率过高,这些函数就可以升级并进行优化。 但优化的函数不再增加其调用计数器,因此必须禁用优化编译器,以使其报告的执行次数保持准确。

精准覆盖(块粒度)

块粒度覆盖必须报告准确到独立表达式层级的覆盖范围。例如,在下面的一段代码中,块覆盖可以检测到条件表达式的else分支: c从不执行,而函数粒度覆盖只会知道函数 f(作为一个整体)被覆盖了。

function f(a) {
return a ? b : c;
}
f(true);

你可能从前面的部分想起我们已经在 V8 中提供了函数调用次数和源码范围。不幸的是,这不适合块覆盖的场景,我们必须实现新的机制来收集执行次数和它们相应的源码范围。

第一个方面是源码范围:假设我们拥有一个特定块的执行计数,我们如何将它们映射到源代码的一部分呢? 为此,我们需要在解析源文件时收集相关位置信息。在块覆盖之前,V8已经在某种程度上做到了这一点。一个示例是由如上所述的Function.prototype.toString而触发的函数范围的收集。

另一个例子是用于构造Error对象的回溯的源码位置。但这些都不足以支持块覆盖; 前者仅适用于函数,而后者仅保存位置信息(例如if-else语句的if标记的位置),而不是源码范围。

因此,我们必须扩展解析器以收集源码范围。为了演示,假设我们正在使用if-else语句:

if (cond) {
/* Then branch. */
} else {
/* Else branch. */
}

当启用块覆盖时,我们收集 then 和 else 分支的源码范围,并将它们与已解析的 IfStatement AST 节点相关联。其他相关语言结构也是如此处理。

在解析过程中收集完源码范围集之后,第二个方面是在运行时跟踪执行计数。 这是通过在生成的字节码数组的关键位置插入新的专用 IncBlockCounter 字节码来完成的。在运行时,IncBlockCounter 字节码处理程序只是增加对应的计数器接口(可通过函数对象访问)。

在 if-else 语句的上述示例中,这样的字节码将被插入在三个位置:紧接在 then 分支的主体之前,在 else 分支的主体之前,紧接在 if-else 语句之后(由于分支内可能存在非本地控制,因此需要连续的计数器)。

最后,报告块粒度覆盖与函数粒度报告类似。但除了调用计数(来自反馈向量)之外,我们现在还报告了感兴趣的源范围的集合以及它们的块计数(存储在挂起该函数的辅助数据结构中)。

如果您想了解V8中代码覆盖之后相关技术细节的更多信息,请参阅coverage和block coverage设计文档。

总结

我们希望你喜欢本文中对V8原生代码覆盖支持的简要介绍。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • JS失效 提示HTML1114: (UNICODE 字节顺序标记)的代码页 utf-8 覆盖(META 标记)的冲突的代码页 utf-8

    经过分析发现,这个是因为页面中html与js编码不一致导致的,一般情况最好统一标准的utf8的 上网找了找,木有找到相关的解决办法,索性自己试了试. 原页面是这样写的: <html> <head> <meta http-equiv="Content-Type" content="text/html charset=UTF-8" /> <script type="text/javascript" src=&

  • JavaScript编写棋盘覆盖代码详解

    一.前言 之前做了一个算法作业,叫做棋盘覆盖,本来需要用c语言来编写的,但是因为我的c语言是半桶水(哈哈),所以索性就把网上的c语言写法改成JavaScript写法,并且把它的覆盖效果显示出来 二.关键代码 <!DOCTYPE html> <html> <head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <titl

  • JS点击图片弹出文件选择框并覆盖原图功能的实现代码

    简单说下原理,把显示的图片的<img>标签 和上传文件的 <input> 标签放在同一个div下,设置<img>的大小和<input>的大小一样,<input> 设置透明度为0,用定位和设置优先级把input浮动在<img>上方,这样点击图片就能选择上传图片,选择完图片后获取图片地址,替换掉原来的默认图片就能实现覆盖原图功能. js代码: <script type="text/javascript" src=

  • js原生方法被覆盖,从新赋值原生的方法

    实现原理 js的原生方法被覆盖掉以后,如果你还没让原生方法又从新指向一个新的变量名,那就gg了.所以,关键就是怎么再获取到原生的方法.实现的原理呢就是创建一个新的window对象,然后从新的window对象里面获取原生的方法,来重新赋值. 使用iframe实现 首先创建一个iframe对象,使用document.createElement方法创建 var iframe = document.createElement("iframe"); document.body.appendChi

  • 深入了解JavaScript代码覆盖

    它为什么是有用的? 作为一名JavaScript开发者,你可能经常发现自己处于代码覆盖可能有用的情景.例如: 对测试套件的质量感兴趣? 重构一个大型的遗留项目? 代码覆盖可以准确显示代码库中已覆盖了哪些部分. 想快速了解是否覆盖了代码库的特定部分? 代码覆盖可以显示有关应用程序的哪些部分已被执行的实时信息,而不是使用console.log进行printf-风格的调试或手动执行代码. 或者你可能正在优化速度,并想知道要关注哪些点? 执行次数可以指出关键函数和循环. JavaScript在V8中的代

  • 深入理解JavaScript系列(1) 编写高质量JavaScript代码的基本要点

    具体一点就是编写高质量JavaScript的一些要素,例如避免全局变量,使用单变量声明,在循环中预缓存length(长度),遵循代码阅读,以及更多. 此摘要也包括一些与代码不太相关的习惯,但对整体代码的创建息息相关,包括撰写API文档.执行同行评审以及运行JSLint.这些习惯和最佳做法可以帮助你写出更好的,更易于理解和维护的代码,这些代码在几个月或是几年之后再回过头看看也是会觉得很自豪的. 书写可维护的代码(Writing Maintainable Code ) 软件bug的修复是昂贵的,并且

  • 编写高质量JavaScript代码的基本要点

    才华横溢的Stoyan Stefanov,在他写的由O'Reilly初版的新书<JavaScript Patterns>(JavaScript模式)中,我想要是为我们的读者贡献其摘要,那会是件很美妙的事情.具体一点就是编写高质量JavaScript的一些要素,例如避免全局变量,使用单变量声明,在循环中预缓存length(长度),遵循代码阅读,以及更多. 此摘要也包括一些与代码不太相关的习惯,但对整体代码的创建息息相关,包括撰写API文档.执行同行评审以及运行JSLint.这些习惯和最佳做法可以

  • 利用函数的惰性载入提高javascript代码执行效率

    在 javascript 代码中,因为各浏览器之间的行为的差异,我们经常会在函数中包含了大量的 if 语句,以检查浏览器特性,解决不同浏览器的兼容问题. 例如,我们最常见的为 dom 节点添加事件的函数: 复制代码 代码如下: function addEvent (type, element, fun) { if (element.addEventListener) { element.addEventListener(type, fun, false); } else if(element.a

  • 不到200行 JavaScript 代码实现富文本编辑器的方法

    前段时间在寻找一些关于富文本编辑器的资料,然后发现了这个名为 Pell 的项目,它是一个所见即所得(WYSIWYG)的文本编辑器,虽然它的功能很简单,但是令人吃惊的是它只有 1kb 大小.而项目最核心的文件 pell.js 只有130行,即使加上其它部分,总的 js 数量也不到200行.这引起了我的兴趣,决定看看它的源码是如何做到这一点的. 项目的主要代码在 pell.js文件中,其结构很简单,主要功能的实现依赖于以下的几个部分 actions 对象 exec() 函数 init() 函数 Do

  • 浅谈JavaScript 代码整洁之道

    概述 一张幽默的图片:软件质量通过你在阅读代码的时候有多少报怨来进行评估 Robert C. Martin 在 <代码整洁之道> 中提到的软件工程原则,同样适用于 JavaScript.这不是一个风格参考.它指导如何用 JavaScript 编写可读.可复用.可重构的软件. 并不是每一个原则都必须严格遵循,甚至很少得到大家的认同.它们仅用于参考,不过要知道这些原则都是<代码整洁之道>的作者们累积多年的集体经验. 我们在软件工程方面的技术发展刚刚超过 50 年,我们仍然在学习很多东西

  • 验证用户必选CheckBox控件与自定义验证javascript代码

    CheckBox控件,由于它的值是选择与非选择.因此在提交数据时,想让用户必须选择CheckBox,普通情况之下,不好做验证. 但我们可以使用asp:CustomValidator来验证,不过还得写自定义验证Javascript代码,可参考如下: 复制代码 代码如下: function ValidateCheckBox(sender, args) { var checkbox = document.getElementById("<%=CheckBox1.ClientID %>&qu

  • 一个即时表单验证的javascript代码

    可爱吧--教你做可爱女人--化妆 美容 美化 瘦身 护肤--深圳可爱吧 // 此脚本由刘海民编写 // 网站:http://www.szrgb.net // 邮箱:lovelium@gmail.com var num=5; var mon1=0; var mon2=0; var mon3=0; var mon4=0; var mon5=0; //封装得到对像ID涵数 function getObj(objName){return(document.getElementById(objName))

  • 纯JavaScript代码实现文本比较工具

    之前项目需求需要写一个纯js文本比较工具,在此小编把代码分享在我们平台供大家参考,算法有待优化,本文写的不好还请见谅. 先上效果图: 代码如下所示: 把源码保存为html格式的文件就可以直接运行了 <!doctype html> <html> <head> <title>文本比较工具</title> <style type="text/css"> *{padding:px;margin:px;} html,body

  • 不使用浏览器运行javascript代码的方法

    有时候我们想用js写一段小程序,但是又觉得使用浏览器去运行挺麻烦的,那么现在我们来看一下如何使用java程序调用javascript程序,这样就可以不借助浏览器就可执行js代码了. 之所以有这个需求是因为这几天在做的一个项目中碰到了这样的问题,我有一个javascript脚本,但是这个项目的其他代码都是用C\C++写的,不想将js代码转成C,感觉太麻烦了,所以就想如果可以在C下面直接调用javascript代码就好了,或者在shell中有一个可以不借助浏览器就可以直接运行js代码的工具也行.现在

随机推荐