《Eloquent JavaScript》笔记--错误处理

严格模式

在文件第一行,或函数体(该函数有效)当中加入 "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,特殊的返回值比较好,否则抛出一个异常是更好的选择。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值