严格模式
在文件第一行,或函数体(该函数有效)当中加入 "use strict";,可以让javascript运行在“严格模式”中。
function canYouSpotTheProblem() {
"use strict"; //此函数有效
for (counter = 0; counter < 10; counter++)
console.log("Happy happy");
}
canYouSpotTheProblem();
// → ReferenceError: counter is not defined
在普通模式中,会自动初始化变量,比如上面代码中的counter会自动创建一个全局的counter变量。严格模式中,必须手动声明变量。
在严格模式中,this所在的函数,如果没有被当作对象的方法调用,this的值是undefined。一般模式中,this指向全局对象。
function Person(name) { this.name = name; }
var ferdinand = Person("Ferdinand"); // oops
console.log(name);
// → Ferdinand
构造函数没有使用new,this不会指向新创建的对象。
"use strict";
function Person(name) { this.name = name; }
// Oops, forgot 'new'
var ferdinand = Person("Ferdinand");
// → TypeError: Cannot set property 'name' of undefined
严格模式中,结果不同。
"use strict";
function Person(name) { this.name = name; }
// Oops, forgot 'new'
var ferdinand = Person("Ferdinand");
// → TypeError: Cannot set property 'name' of undefined
严格模式不准有重名的参数或者属性,移除了某些有严重问题的特性(比如with)。
禁止在函数内部遍历调用栈
function f1(){
"use strict";
f1.caller; // 报错
f1.arguments; // 报错
}
f1();
arguments不再追踪参数变化。
function f(a) {
a = 2;
return [a, arguments[0]];
}
f(1); // 正常模式为[2,2]
function f(a) {
"use strict";
a = 2;
return [a, arguments[0]];
}
f(1); // 严格模式为[2,1]
无法使用arguments.callee
"use strict";
var f = function() { return arguments.callee; };
f(); // 报错
测试
开发人员会用另一个程序来测试使用的程序,如。
function Vector(x, y) {
this.x = x;
this.y = y;
}
Vector.prototype.plus = function(other) {
return new Vector(this.x + other.x, this.y + other.y);
};
用作测试的程序。
function testVector() {
var p1 = new Vector(10, 20);
var p2 = new Vector(-10, 5);
var p3 = p1.plus(p2);
if (p1.x !== 10) return "fail: x property";
if (p1.y !== 20) return "fail: y property";
if (p2.x !== -10) return "fail: negative x property";
if (p3.x !== 0) return "fail: x from plus";
if (p3.y !== 25) return "fail: y from plus";
return "everything ok";
}
console.log(testVector());
// → everything ok
实际上,不用自己亲自动手,有许多语言专用的测试框架来测试程序。
Debugging
调试时不要随意改代码碰运气,经思考,分析然后得出理论原因,改动代码验证理论是否正确。如果暂时想不出原因,可以多加几行console.log,观察代码运行状态。浏览器开发工具的调试功能是一个强大的工具,需要加以善用。
Error propagation
当程序出错时,一种处理方式是返回一个特殊的值,通常是null和undefined。
function promptNumber(question) {
var result = Number(prompt(question, ""));
if (isNaN(result)) return null;
else return result;
}
这种方法也有些缺点,一是有时程序可以返回任何可能的值,这时很难找到一个特殊的值。二是会增加许多额外的精力。比如promptNumber被调用10次,则要检查10次是否返回null,如果函数本身不检查只是返回null,则调用它的代码就要去检查……
异常(Exceptions)
如果程序无法正常继续,我们倾向于立刻跳到一个指定的地方来处理问题,这种机制称为 exception handling。
异常就像是函数的一种高优先级的返回:不但跳出当前函数,还跳出调用他的代码,直到最开始的调用为止。这叫做 unwinding the stack,异常“缩小”调用栈,将它碰到的所有的调用上下文“抛弃”。
如果异常只是一味的“缩小”调用栈,那他们就没什么用处了,只是一种“文艺”的摧毁程序的方式而已。他们的功能在于,我们可以设置“障碍”,来捕获异常,处理它。
function promptDirection(question) {
var result = prompt(question, "");
if (result.toLowerCase() == "left") return "L";
if (result.toLowerCase() == "right") return "R";
throw new Error("Invalid direction: " + result);
}
function look() {
if (promptDirection("Which way?") == "L")
return "a house";
else
return "two angry bears";
}
try {
console.log("You see", look());
} catch (error) {
console.log("Something went wrong: " + error);
}
throw抛出异常,try和catch的代码捕获异常。当try中的代码被抛出后,catch中的代码处理异常。catch的参数是异常的值,本例中使用Error构造函数创建异常。这个对象有一个message属性,包含了错误的信息,同时包含了当前的函数调用栈,所谓的stack trace,存储在stack中。对于调试时很有用,里面包含了抛出异常的函数以及引起异常的其他的函数。
异常之后的处理
看这个例子
var context = null;
function withContext(newContext, body) {
var oldContext = context;
context = newContext;
var result = body();
context = oldContext;
return result;
}
注意context的值最后会恢复成最初的值,然而如果程序出现异常,context就无法恢复原来的值。
try之后可以跟一个finally,表示无论发生什么,都运行finally代码。
function withContext(newContext, body) {
var oldContext = context;
context = newContext;
try {
return body();
} finally {
context = oldContext;
}
}
无须再把body的结果保存到result中,直接返回它。无论body是否有异常,finally中的代码都会被运行。于是我们可以运行下面的代码:
try {
withContext(5, function() {
if (context < 10)
throw new Error("Not enough context!");
});
} catch (e) {
console.log("Ignoring: " + e);
}
// → Ignoring: Error: Not enough context!
console.log(context);
// → null
Selective catching
如果异常没有被捕获,最终会由环境对象来处理,这意味着不同的运行环境处理方式不同。在浏览器中,错误信息会被写到console中。
对于程序员的错误或者程序的错误,由环境最终来处理异常是可以接受的,但是对于在程序运行时本来就可能出现的异常,导致程序崩溃,则需要更细致的处理。
当程序进入到catch中,我们知道try中代码出现了问题,但是我们无法知道是什么异常,也不知道是哪个异常导致的。
javascript不提供可选的异常捕获,你或者捕获全部,或者一个都不捕获。
看这个例子:
for (;;) {
try {
var dir = promtDirection("Where?"); // ← typo!
console.log("You chose ", dir);
break;
} catch (e) {
console.log("Not a valid direction. Try again.");
}
}
for (;;)创建一个无限循环,稍后会手动退出,prompt的拼写出现错误,但是catch语句忽视了所有的错误,这就导致了死循环,而且最终使有用的拼写错误的信息被“覆盖”掉了。
一般来说,不要地毯式捕获所有异常,除非程序要运行在其他地方,比如网络上,告诉其他系统我们的程序崩溃了。即便如此,仍然要仔细对待可能有有用的信息被隐藏了。
我们想捕获特定类型的异常,先要识别异常,然后抛出。我们不使用message,使用instanceof。
function InputError(message) {
this.message = message;
this.stack = (new Error()).stack;
}
InputError.prototype = Object.create(Error.prototype);
InputError.prototype.name = "InputError";
Error.prototype 的对象赋值给InputError.prototype,这就导致 instanceof Error 作用效果与 InputError相同。同时增加了一个name属性,因为Error也有这个属性(Error, SyntaxError, ReferenceError, and so on)。
stack属性在某些环境下也会提供更多的信息。
现在代码可以抛出想要的异常:
function promptDirection(question) {
var result = prompt(question, "");
if (result.toLowerCase() == "left") return "L";
if (result.toLowerCase() == "right") return "R";
throw new InputError("Invalid direction: " + result);
}
并且可以被正确的捕获:
for (;;) {
try {
var dir = promptDirection("Where?");
console.log("You chose ", dir);
break;
} catch (e) {
if (e instanceof InputError)
console.log("Not a valid direction. Try again.");
else
throw e;
}
}
只有InputError的实例会被抛出,其他的错误仍然被报出来。
断言
断言是一种检查程序错误的基本工具。
function AssertionFailed(message) {
this.message = message;
}
AssertionFailed.prototype = Object.create(Error.prototype);
function assert(test, message) {
if (!test)
throw new AssertionFailed(message);
}
function lastElement(array) {
assert(array.length > 0, "empty array in lastElement");
return array[array.length - 1];
}
如果没有assert函数,数组又是空,lastElement返回的最后一个元素将是undefined,这通常是没有意义的。上面的代码可以确保结果是我们想要的。
断言可以保证在程序出错的地方产生错误,否则无意义的值可能悄悄的传递下去,直到在一个不相关的系统中引起错误。
总结
调试程序可以使用自动化测试工具和断言。
对于可以本地解决的bug,特殊的返回值比较好,否则抛出一个异常是更好的选择。