八、调试与评测

调试是编程中一个棘手的部分。开发过程中的 bug 是不可避免的。不管我们有什么经验,我们都得花相当多的时间去猎杀它们。这是常有的事。从代码的外观来看,您可能找不到 bug,应用可能没有问题,但是开发人员会奋战数小时,直到遇到愚蠢的原因,例如打印错误的属性名。通过更好地使用浏览器开发工具,可以节省大量时间。因此,我们将在本章中讨论以下主题:

  • 如何发现 bug
  • 从控制台 API 中获得最佳效果
  • 如何调整性能

猎虫

调试是关于发现并解决阻止预期应用行为的缺陷。在这里,关键是找到导致问题的代码。当我们遇到 bug 时,我们通常做什么?比方说,我们有一个表单,假设它在提交事件上运行验证,但它没有。首先,我们需要满足一些假设。例如,如果对表单元素的引用有效,如果在注册侦听器期间事件和方法名称拼写正确,如果对象上下文没有在侦听器主体中丢失,等等。

通过验证方法入口和出口点上的输入和输出,可以自动发现一些错误(参见合同设计,网址:https://en.wikipedia.org/wiki/Design_by_contract 。然而,我们必须手动发现其他 bug,这里我们可以使用两个选项。从代码肯定是正确的一步一步地开始到问题点(自底向上调试),或者相反,从中断点后退以找到中断源。在这里,浏览器开发工具可以派上用场。

最先进的是 Chrome 开发工具。我们可以打开其中的面板,在代码中设置断点。浏览器在到达断点时停止执行,并显示带有实际变量范围和调用堆栈的窗格。它还提供控制,用户可以使用它一次一行地逐步通过代码。以下屏幕截图显示了借助断点进行调试:

Hunting bugs

然而,要在 DevTools 中浏览代码库,这个可能会很棘手。幸运的是,您可以直接在 IDE 中设置浏览器的断点。您只需要将调试器语句放在希望浏览器中断的行上。

有时候,很难弄清楚 DOM 到底发生了什么。我们可以使用 DevTools 中断 DOM 事件,例如节点删除、节点修改和子树更改。只需在面板中导航到 HTML 元素,右键单击,然后选择中断。。。选项。

此外,在面板中有一个名为XHR 断点的选项卡,我们可以在其中设置 URL 列表。当请求任何 URL 时,浏览器将中断。

您也可以在面板侧栏顶部找到一个停止标志形式的图标。如果单击此按钮,DevTools 将在捕获到的任何异常上中断,并在源代码中为您提供抛出位置。以下屏幕截图显示了如何使用捕获异常时暂停工具:

Hunting bugs

更多信息,请参见https://developer.chrome.com/devtools/docs/javascript-debugging

从控制台 API 中获得最佳效果

尽管它不是 JavaScript 的一部分,但我们都广泛使用控制台 API 来了解应用生命周期中到底发生了什么。该 API 曾经由 Firebug 工具引入,现在可以在每个主要的 JavaScript 代理中使用。大多数开发人员只是使用诸如 error、trace、log 之类的方法以及诸如 info 和 warn 之类的 decorator 来完成简单的日志记录。好的,当我们将任何值传递给console.log时,它们会显示在JavaScript控制台面板上。通常,我们传递一个描述案例的字符串和要检查的各种对象的列表。但是,您知道吗,我们可以通过 PHPsprintf的方式直接从字符串引用这些对象?因此,作为第一个参数给出的字符串可以是包含其余参数格式说明符的模板:

var node = document.body;
console.log( "Element %s has %d child nodes; JavaScript object %O, DOM element %o",
  node.tagName,
  node.childNodes.length,
  node,
  node );

Getting the best from a console API

可用的说明符是字符串的%s、数字的%d、DOM 元素的%o和 JavaScript 对象的%O(与console.dir相同)。此外,还有一个特定的说明符,允许我们设计console.log报告的样式。这可能非常有用。实际上,应用控制台接收的日志记录太多。在一百条类似的消息中,很难分辨出想要的消息。我们可以做的是对消息进行分类并相应地设置样式:

console.log.user = function(){
  var args = [].slice.call( arguments );
  args.splice( 0, 0, "%c USER ",
    "background-color: #7DB4B5; border-radius: 3px; color: #fff; font-weight: bold; " );
  console.log.apply( console, args );
};

console.log.event = function(){
  var args = [].slice.call( arguments );
  args.splice( 0, 0, "%c EVENT ",
    "background-color: #f72; border-radius: 3px; color: #fff; font-weight: bold; " );
  console.log.apply( console, args );
};
console.log( "Generic log record" );
console.log.user( "User click button Foo" );
console.log.event( "Bar triggers `Baz` event on Qux" );

在本例中,我们定义了两个扩展console.log的方法。其中一个在控制台消息前面加上青色的USER,用于用户操作事件。第二个在报告前面加上事件,用于突出显示中介事件。以下屏幕截图解释了 console.log 的彩色输出:

Getting the best from a console API

另一个鲜为人知的技巧是在代码逻辑中使用console.assert进行断言。所以,我们假设一个条件是真的,直到它是真的,一切都很好,我们没有得到任何消息。但一旦失败,我们就会在控制台中获得一条记录:

console.assert( sessionId > 0, "Session is created" );

以下屏幕截图显示了如何使用控制台断言:

Getting the best from a console API

有时我们需要知道一件事发生的频率。这里我们可以使用console.count方法:

function factory( constr ){
  console.count( "Factory is called for " + constr );
  // return new window[ constr ]();
}
factory( "Foo" );
factory( "Bar" );
factory( "Foo" );

这将在控制台中显示指定的消息及其旁边的自动更新计数器。以下屏幕截图显示了如何使用 console.count:

Getting the best from a console API

您可以在上了解有关使用控制台的更多信息 https://developer.chrome.com/devtools/docs/console

调谐性能

性能提升用户体验。如果加载页面或 UI 的响应时间过长,用户很可能会离开应用,再也不会回来。网络应用尤其如此。在第 3 章DOM 脚本和 AJAX中,我们比较了不同的 DOM 操作方法。为了了解一种方法的速度,我们使用了一个性能内置对象:

"use strict";
var cpuExpensiveOperation = function(){
      var i = 100000;
      while( --i ) {
        document.body.appendChild( document.createElement( "div" ) );
      }
    },
    // Start test time
    s = performance.now();

cpuExpensiveOperation();
console.log( "Process took", performance.now() - s, "ms" );

performance.now()返回一个高分辨率时间戳,表示精确到微秒的时间(以毫秒为单位)。这是为基准测试而设计和广泛使用的。但是,time/timeEnd控制台对象也提供了测量时间的方法:

console.time( "cpuExpensiveOperation took" );
cpuExpensiveOperation();
console.timeEnd( "cpuExpensiveOperation took" );

以下屏幕截图显示了使用控制台测量时间:

Tuning performance

如果我们需要知道操作执行期间的具体情况,我们可以请求该期间的配置文件:

console.profile( "cpuExpensiveOperation" );
cpuExpensiveOperation();
console.profileEnd( "cpuExpensiveOperation" );

以下屏幕截图显示了如何使用控制台 API 进行分析:

Tuning performance

此外,我们可以在 DevTools 的时间线面板中标记事件的确切时间:

cpuExpensiveOperation(); 
console.timeStamp( "cpuExpensiveOperation finished" );

以下屏幕截图显示了如何在录制会话期间在时间线上标记事件:

Tuning performance

在调整性能时,我们必须特别注意响应时间。有许多技术可以用来改善引导过程中的用户体验(非阻塞 JavaScript 和 CSS 加载、关键 CSS、移动静态文件 CDN 等)。假设您决定异步加载 CSS(https://www.npmjs.com/package/asynccss 和缓存到本地存储中。但你如何测试你是否从中获得了什么?幸运的是,DevTools 有一个电影长片功能。我们只需要打开网络面板,启用截图并重新加载页面。

当用户在加载过程中看到页面时,DevTools 会一帧一帧地向我们显示页面加载的进度。此外,我们可以为测试手动设置连接速度(节流),并了解它如何影响电影带。下面的屏幕截图显示了如何获取页面加载的胶片带:

Tuning performance

总结

调试是 web 开发不可或缺的一部分。这也可能是一个相当缓慢和乏味的任务。有了浏览器开发工具,我们可以减少寻找 bug 的时间。我们可以在代码中设置断点,并以与程序相同的方式逐步移动到问题的根源。当使用 ChromeDevTools 时,我们可以监视 DOM 修改事件和特定的 URL 请求。在调整性能时,我们可以使用time/timeEnd测量时间,并使用profile/profileEnd请求流程概要文件。使用 filmstrip 和 throttling 等功能,我们可以查看不同连接上的页面负载。

我们从回顾 JavaScript 的核心特性开始这本书。我们学习了如何通过语法糖使代码更具表现力,练习了对象迭代和集合规范化,比较了包括 ES6 类在内的各种对象声明方法,并了解了如何使用 JavaScript 的神奇方法。然后,我们进入模块化编程。我们大体上讨论了模块模式和模块,并回顾了 JavaScript AMD、CommonJS 和 ES6 模块中的三种主要模块化方法。下一个主题是关于保持高性能 DOM 操作。我们还检查了 fetchapi。我们还考虑了一些最激动人心的 HTML5API,如存储、IndexedDB、workers、SSE 和 WebSocket,以及 Web 组件下的技术。我们考虑了利用 JavaScript 事件循环和构建非阻塞应用的技术。我们在 JavaScript 中练习设计模式,并讨论了关注点分离。我们在三个框架中编写了一个简单的应用:主干、角度和 React。我们通过创建一个命令行实用程序并公开一个 web 服务器来尝试 Node.js。我们还使用 NW.js 创建了一个演示桌面应用,并使用 PhoneGap 创建了其移动版。最后,我们谈到了寻找虫子。