Eloquent JavaScript 笔记 八: Bugs and Error Handling

来源:互联网 发布:安卓去广告软件 编辑:程序博客网 时间:2024/06/03 23:44

原书: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"); // oopsconsole.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。










原创粉丝点击