现代JavaScript 之代码质量 第一部分 第三章 易错总结
3.1 在 Chrome 中调试
断点(Breakpoints)
- 快速跳转至代码中的断点(通过点击右侧面板中的对应的断点)。
- 通过取消选中断点来临时禁用对应的断点。
- 通过右键单击并选择移除来删除一个断点。
- ……等等。
Debugger 命令
function hello(name) {
let phrase = `Hello, ${name}!`;
debugger; // <-- 调试器会在这停止
say(phrase);
}
当我们在一个代码编辑器中并且不想切换到浏览器在开发者工具中查找脚本来设置断点时,这真的是非常方便。
暂停并查看
- 察看(Watch) —— 显示任意表达式的当前值。
你可以点击加号 + 然后输入一个表达式。调试器将随时显示它的值,并在执行过程中自动重新计算该表达式。 - 调用栈(Call Stack) —— 显示嵌套的调用链。
此时,调试器正在 hello() 的调用链中,被 index.html 中的一个脚本调用(这里没有函数,因此显示 “anonymous”)
如果你点击了一个堆栈项,调试器将跳到对应的代码处,并且还可以查看其所有变量。 - 作用域(Scope) —— 显示当前的变量。
Local 显示当前函数中的变量,你还可以在源代码中看到它们的值高亮显示了出来。
Global 显示全局变量(不在任何函数中)。
跟踪执行
“恢复(Resume)”:继续执行,快捷键 F8。
继续执行。如果没有其他的断点,那么程序就会继续执行,并且调试器不会再控制程序。
“跨步(Step over)”:运行下一条指令,但 不会进入到一个函数中,快捷键 F10。
跟上一条命令“下一步(Step)”类似,但如果下一条语句是函数调用则表现不同。这里的函数指的是:不是内置的如 alert 函数等,而是我们自己写的函数。
“下一步(Step)”命令进入函数内部并在第一行暂停执行,而“跨步(Step over)”在无形中执行函数调用,跳过了函数的内部。
执行会在该函数执行后立即暂停。
“步入(Step into)”,快捷键 F11。
和“下一步(Step)”类似,但在异步函数调用情况下表现不同。如果你刚刚才开始学 JavaScript,那么你可以先忽略此差异,因为我们还没有用到异步调用。
至于之后,只需要记住“下一步(Step)”命令会忽略异步行为,例如 setTimeout(计划的函数调用),它会过一段时间再执行。而“步入(Step into)”会进入到代码中并等待(如果需要)。
“步出(Step out)”:继续执行到当前函数的末尾,快捷键 Shift+F11。
继续执行代码并停止在当前函数的最后一行。当我们使用步出偶然地进入到一个嵌套调用,但是我们又对这个函数不感兴趣时,我们想要尽可能的继续执行到最后的时候是非常方便的。
“下一步(Step)”:运行下一条指令,快捷键 F9
运行下一条语句。如果我们现在点击它,alert 会被显示出来。
一次接一次地点击此按钮,整个脚本的所有语句会被逐个执行。
启用/禁用所有的断点。
这个按钮不会影响程序的执行。只是一个批量操作断点的开/关。
启用/禁用出现错误时自动暂停脚本执行。
当启动此功能并且开发者工具是打开着的时候,任何一个脚本的错误都会导致该脚本执行自动暂停。然后我们可以分析变量来看一下什么出错了。因此如果我们的脚本因为错误挂掉的时候,我们可以打开调试器,启用这个选项然后重载页面,查看一下哪里导致它挂掉了和当时的上下文是什么。
3.2 代码风格
语法
缩进
垂直方向上的缩进:用于将代码拆分成逻辑块的空行。
function pow(x, n) {
let result = 1;
// <--
for (let i = 0; i < n; i++) {
result *= x;
}
// <--
return result;
}
插入一个额外的空行有助于使代码更具可读性。写代码时,不应该出现连续超过 9 行都没有被垂直分割的代码。
嵌套的层级
尽量避免代码嵌套层级过深。
例如,在循环中,有时候使用 continue
指令以避免额外的嵌套是一个好主意。
使用 if/else 和 return 也可以做类似的事情。
例如,下面的两个结构是相同的。
第一个:
function pow(x, n) {
if (n < 0) {
alert("Negative 'n' not supported");
} else {
let result = 1;
for (let i = 0; i < n; i++) {
result *= x;
}
return result;
}
}
第二个:
function pow(x, n) {
if (n < 0) {
alert("Negative 'n' not supported");
return;
}
let result = 1;
for (let i = 0; i < n; i++) {
result *= x;
}
return result;
}
自动检查器
下面是一些最出名的代码检查工具:
- JSLint — 第一批检查器之一。
- JSHint — 比 JSLint 多了更多设置。
- ESLint — 应该是最新的一个。
3.3 注释
好的注释
记录函数的参数和用法
有一个专门用于记录函数的语法 JSDoc:用法、参数和返回值。
例如:
/**
* 返回 x 的 n 次幂的值。
*
* @param {number} x 要改变的值。
* @param {number} n 幂数,必须是一个自然数。
* @return {number} x 的 n 次幂的值。
*/
function pow(x, n) {
...
}
3.5 使用 Mocha 进行自动化测试
行为驱动开发(BDD)
BDD 包含了三部分内容:测试、文档和示例。
describe("pow", function() {
it("raises to n-th power", function() {
assert.equal(pow(2, 3), 8);
});
});
<!DOCTYPE html>
<html>
<head>
<!-- add mocha css, to show results -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/3.2.0/mocha.css">
<!-- add mocha framework code -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/3.2.0/mocha.js"></script>
<script>
mocha.setup('bdd'); // minimal setup
</script>
<!-- add chai -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/chai/3.5.0/chai.js"></script>
<script>
// chai has a lot of stuff, let's make assert global
let assert = chai.assert;
</script>
</head>
<body>
<script>
function pow(x, n) {
/* function code is to be written, empty now */
}
</script>
<!-- the script with tests (describe, it...) -->
<script src="test.js"></script>
<!-- the element with id="mocha" will contain test results -->
<div id="mocha"></div>
<!-- run tests! -->
<script>
mocha.run();
</script>
</body>
</html>
改进规范
- 第一种 —— 在同一个 it 中再添加一个 assert:
describe("pow", function() {
it("raises to n-th power", function() {
assert.equal(pow(2, 3), 8);
assert.equal(pow(3, 4), 81);
});
});
- 第二种 —— 写两个测试:
describe("pow", function() {
it("2 raised to power 3 is 8", function() {
assert.equal(pow(2, 3), 8);
});
it("3 raised to power 4 is 81", function() {
assert.equal(pow(3, 4), 81);
});
});
主要的区别是,当 assert
触发一个错误时,it
代码块会立即终止。因此,在第一种方式中,如果第一个 assert
失败了,我们将永远不会看到第二个 assert
的结果。
改进实现
describe("pow", function() {
function makeTest(x) {
let expected = x * x * x;
it(`${x} in the power 3 is ${expected}`, function() {
assert.equal(pow(x, 3), expected);
});
}
for (let x = 1; x <= 5; x++) {
makeTest(x);
}
});
嵌套描述
describe("pow", function() {
describe("raises x to power 3", function() {
function makeTest(x) {
let expected = x * x * x;
it(`${x} in the power 3 is ${expected}`, function() {
assert.equal(pow(x, 3), expected);
});
}
for (let x = 1; x <= 5; x++) {
makeTest(x);
}
});
// ……可以在这里写更多的测试代码,describe 和 it 都可以添加在这。
});
延伸规范
describe("pow", function() {
// ...
it("for negative n the result is NaN", function() {
assert.isNaN(pow(2, -1));
});
it("for non-integer n the result is NaN", function() {
assert.isNaN(pow(2, 1.5));
});
});
3.6 Polyfill
Babel 是一个 transpiler。它将现代的 JavaScript 代码转化为以前的标准形式.
实际上,Babel 包含了两部分:
-
第一,用于重写代码的 transpiler 程序。开发者在自己的电脑上运行它。它以之前的语言标准对代码进行重写。然后将代码传到面向用户的网站。像 webpack 这样的现代项目构建系统,提供了在每次代码改变时自动运行 transpiler 的方法,因此很容易集成在开发过程中。
-
第二,polyfill。
新的语言特性可能不仅包括语法结构,还包括新的内建函数。 Transpiler 会重写代码,将语法结构转换为旧的结构。但是对于新的内建函数,需要我们去实现。JavaScript 是一个高度动态化的语言。脚本可以添加/修改任何函数,从而使它们的行为符合现代标准。
更新/添加新函数的脚本称为 “polyfill”。它“填补”了缺口,并添加了缺少的实现。
所以,如果我们要使用现代语言功能,transpiler 和 polyfill 是必要的。