确定哪些 JavaScript 函数没有被调用

Posted

技术标签:

【中文标题】确定哪些 JavaScript 函数没有被调用【英文标题】:Determine which JavaScript functions were NOT called 【发布时间】:2020-10-06 07:36:25 【问题描述】:

假设您有一个使用一些 javascript 库的网站,无论是您自己的还是第三方创作的。除了最小化和混淆之外,我想知道是否可以通过删除未使用的函数来进一步减小脚本有效负载的大小。一旦达到一定程度的复杂性,就很难/不可能知道在所有可能的执行路径下最终调用了哪些函数。这让我产生了一个疑问:有没有什么工具或方法可以确定,在网页使用一段时间后,哪些用户定义的函数(不是内置函数)没有被调用?

一个明显的可能性是将所有函数定义包装在对注册函数的调用中,这 1) 将函数添加到注册器,以及 2) 注入一些代码以将函数标记为已被调用。然后就可以查询那些没有被调用的函数的注册器。然而,这种方法极其复杂。最好的办法是在 Web 服务器上编写一个 JavaScript 代码解析器,它由运行时标志(“处于 JS 诊断模式”)启用,它捕获所有 JavaScript 响应并相应地修改代码。但并不需要太多的想象力就能意识到这将是多么容易出错和困难。

更新:澄清一下,我不是在寻找一个自动删除未使用函数的解决方案,我不会对此感到满意,因为害怕引入不稳定。相反,功能使用的开发时间分析将让我选择要删除的包含/功能,以便最终解决方案可以在发布之前进行适当的测试。

【问题讨论】:

请参考How to create a Minimal, Reproducible Example。 这是一个被称为树抖动的问题。 Webpack 包含一些实用程序来促进它。这是一个很大的主题,有很多资源可以帮助解决它。 当函数被调用时,可以用一些记录来覆盖 Function.prototype.call。一个简短的实验表明这是可能的。显然,这与最佳实践背道而驰,但无论如何 W3C 都不是我真正的父亲。如果您编译代码库中所有函数的列表,则可以减去被调用函数的列表。 @CharlesBamford Monkeypatching Function.prototype.call 无济于事,因为通常直接调用函数。您必须使用其原型 call 函数调用每个方法。 包装所有函数的最简单方法是编写一个 babel-plugin 来转换你的代码,包装每个函数。这应该是相当简单的,你不必接触你的代码库,你可以简单地生成你的代码的两个版本,一个有转换,一个没有。 【参考方案1】:

一旦达到一定程度的复杂性,就很难/不可能知道在所有可能的执行路径下最终调用了哪些函数。

“困难/不可能”中不需要斜线。 死代码消除相当于解决停机问题,因此是不可能的。

有没有什么工具或者方法可以确定网页使用一段时间后,哪些用户自定义函数(不是内置函数)没有被调用?

这介于 profiling(确定代码的哪些部分已被执行的频率)和 覆盖分析(确定代码的哪些部分已被执行)之间的某个位置全部)。更准确地说,听起来您正在寻找功能覆盖率

请注意,代码覆盖率通常是在测试的上下文中讨论的,以至于对于某些人来说,术语“代码覆盖率”和“测试覆盖率”是同义词,但代码覆盖率与测试无关。它只是指“当我运行特定的工作负载时,代码的哪些部分会被执行?”的问题

上述问题中“部分”一词的解释方式不同,它们产生了不同的覆盖范围:

行覆盖率:执行了哪些行? 函数覆盖率:执行了哪些函数? (这是您感兴趣的。) 语句覆盖率:执行了哪些语句? 表达式覆盖率:执行了哪些表达式? 分支覆盖率:条件的哪些分支被执行? 路径覆盖:通过块的哪些路径被执行?

行覆盖率通常没有用,因为您只需重新格式化代码即可更改数字。事实上,在 ECMAScript 中,我总是可以很简单地做到这一点,只需将所有内容写在一行上即可执行所有行。

分支覆盖和路径覆盖的区别可以用这段代码来举例说明:

function foo(bar, baz) 
  if (bar) 
    console.log("bar");
   else 
    console.log("no bar");
  

  if (baz) 
    console.log("baz");
   else 
    console.log("no baz");
  

为了覆盖所有分支,我需要两个调用:

foo(true,  false);
foo(false, true);

为了通过函数覆盖所有可能的路径,我需要四个调用:

foo(true,  true);
foo(true,  false);
foo(false, true);
foo(false, false);

如果您根据函数覆盖率进行启发式死代码消除,您将能够删除未使用的函数。如果您根据分支覆盖率执行此操作,您甚至可以从 ifswitch 语句中删除未使用的分支。

如果您正在寻找代码覆盖工具,您可能需要查看测试空间,因为这是它们最常使用的地方。而且您可能必须编写自己的死代码消除工具,或者可能修补现有的压缩程序,以便它可以从代码覆盖运行中读取日志文件并据此做出决策。

据我所知,没有现成的工具可以做到这一点,尽管我可能错了。

【讨论】:

删除 all 死代码相当于停机问题。删除大部分死代码很困难,而且肯定有容易实现的目标。 好吧,OP 正在询问在所有静态可识别的死代码已被删除后该怎么做。 是的,我认为他们想找到在典型会话中不使用的实时代码(可以使用)。 (我认为这是一个坏主意,因为对于非典型会话,页面只会中断,例如,当删除通常未使用的错误处理程序时)。 定义:启发式——你认为你知道的,但在最坏的时候发现的,你不知道。有一个很棒的故事,我不知道它有多少真实性,90% 的用户只使用 10% 的 MS Word。因此,如果您使用覆盖率分析,您将删除 90% 的代码,但实际上 每一行代码 都是根据需要它的人的功能请求放在那里的,所以如果你删除只删除一个那些“未使用”的行,你已经破坏了某人的话。

以上是关于确定哪些 JavaScript 函数没有被调用的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript This -笔记

确定 GC 释放了哪些 JavaScript 对象

如何在Javascript中找出从某个文件调用的函数

为啥我的 JavaScript 函数显然没有被调用?

有没有办法确定用户加入了哪些多用户会议 (MUCH)?

javascript的this问题