Eloquent JavaScript 笔记 八: Bugs and Error Handling

原书:http://eloquentjavascript.net/08_error.html

1. Strict Mode

"use strict";

    1.function内,放在第一行;

    2.js文件,放在第一行;

    3. <script> 标签,放在第一行;

    4. 如果不放在第一行,没用。

未定义的变量

function canYouSpotTheProblem() {
  "use strict";
  for (counter = 0; counter < 10; counter++)
    console.log("Happy happy");
}

canYouSpotTheProblem();
// → ReferenceError: counter is not defined

如果在正常情况下,使用未定义的变量时,系统会自动定义一个全局变量。而strict模式下,不会定义全局变量,直接报错。


this

如果一个函数是object的方法,而调用它时,没有相应的object,会怎样?

正常模式下,this指向全局变量。strict模式下,this等于undefined。

function Person(name) { this.name = name; }
var ferdinand = Person("Ferdinand"); // oops
console.log(name);
// → Ferdinand

在这段代码中,调用Person构造函数时,忘了写new。那么,它就没有创建一个新的Person对象,而只是把Person(name)作为一个普通的函数来执行。这个时候,this是全局变量。也就是说,this.name = name; 会给全局变量变量创建一个属性name,并赋值。仔细看 console.log(name); 这里面的name变量是哪儿来的?


"use strict";
function Person(name) { this.name = name; }
// Oops, forgot 'new'
var ferdinand = Person("Ferdinand");
// → TypeError: Cannot set property 'name' of undefined

这段代码中,this是undefined,给一个undefined添加name属性,当然就报错了。


1. Testing

以上一章的Vector类为例

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

已经有好些现成的Test Suite可以用了,功能与上例类似。会简化测试代码的书写,提供更好的输出格式。


1. Debugging

看一个例子,找出问题

给定radix(10进制、16进制等),把一个整数按这个基数打印出来。

function numberToString(n, base) {
  var result = "", sign = "";
  if (n < 0) {
    sign = "-";
    n = -n;
  }
  do {
    result = String(n % base) + result;
    n /= base;
  } while (n > 0);
  return sign + result;
}
console.log(numberToString(13, 10));
// → 1.5e-3231.3e-3221.3e-3211.3e-3201.3e-3191.3e-3181.3…

用console.log 调试

在do .. while循环内部,加上 console.log(n);

可以看到,n /= base 之后,不是整数。所以,这一行应该写做:n = Math.floor(n/base);


用浏览器自带的调试功能,加断点、单步跟踪

这是chrome



或者,在代码中加入一行 debugger; 如果浏览器的Develop功能打开了,它会自动变成一个breakpoint



1. Error Propagation

并不是所有的代码和数据都是可控的,例如:用户的输入、使用第三方代码库、从网路上获取数据等。

看一个例子

这个函数返回用户输入数字:

function promptNumber(question) {
  var result = Number(prompt(question, ""));
  if (isNaN(result)) return null;
  else return result;
}

console.log(promptNumber("How many trees do you see?"));

不幸的是,用户可以输入任何字符,我们并不能每次都得到正确的输入。所以,这里做了错误处理,当不能得到合格的数字时,返回null。

在函数中返回一个特殊的值,用以代表出错/异常,是一种常用的方法。

但有时,这种方法也不太实用,因为:

   1. 有时找不到合适的 “特殊数值” ,也许 null 也是一个合法的返回值呢?

   2. 在调用该函数时,每次都要判断返回值是否合法,是否出错。也是一件麻烦的事情。尤其是,当函数调用的层次比较深,那么每一层都要写一些防御性代码。


1. Exceptions

回顾第三章的函数堆栈,每一次函数调用,都会压一层堆栈,每return一次,就会弹出一层堆栈。

exception类似于一个超级return,它可以一次弹出多层堆栈,一直弹到有catch它的那一层。

基本用法

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);
}

  1. throw

     抛出异常

  2. Error

     预定义的类,主要有两个属性:

        message :错误描述

        stack: 当前的堆栈
  3. try
     可能抛出异常的代码块
  4. catch

     异常发生后,跳到这里。之后的代码还会正常执行。

1. Cleaning Up After Exceptions

 有这么一种情况

var context = null;

function withContext(newContext, body) {
  var oldContext = context;
  context = newContext;
  var result = body();
  context = oldContext;
  return result;
}

注意,在执行 body() 之前,我们保存了context,执行完body()之后,恢复原来的context。如果在body()中有exception抛出,怎么办? 后面两行代码不会执行呀。


finally

function withContext(newContext, body) {
  var oldContext = context;
  context = newContext;
  try {
    return body();
  } finally {
    context = oldContext;
  }
}

不论是否抛出异常,finnally 代码块都会被执行。注意,即使是在 return 语句之后,也会执行。

看一个完整的例子:

var context = null;
function withContext(newContext, body) {
    var oldContext = context;
    context = newContext;
    try {
        return body();
    } finally {
        context = oldContext;
    }
}

try {
    withContext(5, function () {
        if (context < 10)
            throw new Error("Not enough context!");
    });
}
catch(e) {
    console.log("Ignoring: " + e);
}

console.log(context);

1. Selective Catching

如果一个exception没有被catch,它会一直弹到栈底,此时,系统(浏览器)会捕获它,通常会在console中打印出错误描述。

有时,我们希望捕获某些种类的exception,而让其他的由系统处理。如果按照上个例子的写法,会捕获所有body()内部抛出的异常,这可能不是我们所希望的。

看一个例子:

for (;;) {
  try {
    var dir = promtDirection("Where?"); // ← typo!
    console.log("You chose ", dir);
    break;
  } catch (e) {
    console.log("Not a valid direction. Try again.");
  }
}

该代码的本意是得到合法的用户输入就break,跳出for循环。可是,promtDirection 这个函数名拼错了,这个异常本来应该由系统处理,而下面的catch把它强行捕获,导致了死循环。

Error 由个message属性,也许我们可以通过不同的message来区分不同类型的exception。但这样不好,因为message是给人看的,不能严谨的定义一种类型,随意性比较强(被翻译成不同语言、被修改了,等等)。


定义不同类型的Error

就像系统预定义的:Error, SyntaxError, ReferenceError 等。

function InputError(message) {
  this.message = message;
  this.stack = (new Error()).stack;
}
InputError.prototype = Object.create(Error.prototype);
InputError.prototype.name = "InputError";
定义一个InputError,继承 Error.prototype,并且,通过Error的构造,得到当前的堆栈。

系统提供的Error类型都有name属性。我们也给它加上。

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;
  }
}

仔细看catch代码块:

  1. 用instanceof 识别不同的Error类型;

  2. 其他类型的继续throw 。


1. Assertations


js本身没有提供assert,可以自己定义一个

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];
}

本章的两个练习题很简单,就是练习一下自定义Error类型和使用finnally。










  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值