数据类型的转换

强制转换

Number()

原始类型
// 数值:转换后还是原来的值
Number(324) // 324

// 字符串:如果可以被解析为数值,则转换为相应的数值
Number('324') // 324

// 字符串:如果不可以被解析为数值,返回 NaN
Number('324abc') // NaN

// 空字符串转为0
Number('') // 0

// 布尔值:true 转成 1,false 转成 0
Number(true) // 1
Number(false) // 0

// undefined:转成 NaN
Number(undefined) // NaN

// null:转成0
Number(null) // 0

(1)parseIntNumber函数都会自动过滤一个字符串前导和后缀的空格。
(2)Number函数将字符串转为数值,要比parseInt函数严格很多。基本上,只要有一个字符无法转成数值,整个字符串就会被转为NaN

对象

(2)简单的规则是,Number方法的参数是对象时,将返回NaN,除非是包含单个数值的数组。

Number({a: 1}) // NaN
Number([1, 2, 3]) // NaN
Number([5]) // 5

(2)Number背后的转换规则比较复杂。

第一步,调用对象自身的valueOf方法。如果返回原始类型的值,则直接对该值使用Number函数,不再进行后续步骤。

第二步,如果valueOf方法返回的还是对象,则改为调用对象自身的toString方法。如果toString方法返回原始类型的值,则对该值使用Number函数,不再进行后续步骤。

第三步,如果toString方法返回的是对象,就报错。

var obj = {x: 1};
Number(obj) // NaN

// 等同于
if (typeof obj.valueOf() === 'object') {
  Number(obj.toString());
} else {
  Number(obj.valueOf());
}

(3)默认情况下,对象的valueOf方法返回对象本身,所以一般总是会调用toString方法,而toString方法返回对象的类型字符串(比如[object Object])。所以,会有下面的结果。

Number({}) // NaN

(4)valueOftoString方法,都是可以自定义的。

Number({
  valueOf: function () {
    return 2;
  }
})
// 2

Number({
  toString: function () {
    return 3;
  }
})
// 3

Number({
  valueOf: function () {
    return 2;
  },
  toString: function () {
    return 3;
  }
})
// 2

String()

原始类型值
  • 数值:转为相应的字符串。
  • 字符串:转换后还是原来的值。
  • 布尔值true转为字符串"true"false转为字符串"false"
  • undefined:转为字符串"undefined"
  • null:转为字符串"null"
String(123) // "123"
String('abc') // "abc"
String(true) // "true"
String(undefined) // "undefined"
String(null) // "null"
对象

(1)String方法的参数如果是对象,返回一个类型字符串;如果是数组,返回该数组的字符串形式。

String({a: 1}) // "[object Object]"
String([1, 2, 3]) // "1,2,3"

(2)转换规则

  1. 先调用对象自身的toString方法。如果返回原始类型的值,则对该值使用String函数,不再进行以下步骤。
  2. 如果toString方法返回的是对象,再调用原对象的valueOf方法。如果valueOf方法返回原始类型的值,则对该值使用String函数,不再进行以下步骤。
  3. 如果valueOf方法返回的是对象,就报错。
String({a: 1})
// "[object Object]"

// 等同于
String({a: 1}.toString())
// "[object Object]"
var obj = {
  valueOf: function () {
    return {};
  },
  toString: function () {
    return {};
  }
};

String(obj)
// TypeError: Cannot convert object to primitive value
String({
  toString: function () {
    return 3;
  }
})
// "3"

String({
  valueOf: function () {
    return 2;
  }
})
// "[object Object]"

String({
  valueOf: function () {
    return 2;
  },
  toString: function () {
    return 3;
  }
})
// "3"

Boolean()

(1)它的转换规则相对简单:除了以下五个值的转换结果为false,其他的值全部为true

  • undefined
  • null
  • 0(包含-0+0
  • NaN
  • ''(空字符串)
Boolean(undefined) // false
Boolean(null) // false
Boolean(0) // false
Boolean(NaN) // false
Boolean('') // false
Boolean(true) // true
Boolean(false) // false

(2)所有对象(包括空对象)的转换结果都是true

Boolean({}) // true
Boolean([]) // true
Boolean(new Boolean(false)) // true

自动转换

(1)自动转换的规则是这样的:预期什么类型的值,就调用该类型的转换函数。
(2)由于自动转换具有不确定性,而且不易除错,建议在预期为布尔值、数值、字符串的地方,全部使用Boolean()Number()String()函数进行显式转换。
(3)加法运算符(+)有可能把运算子转为字符串

null + 1 // 1
undefined + 1 // NaN

(4)一元运算符也会把运算子转成数值。

+'abc' // NaN
-'abc' // NaN
+true // 1
-false // 0

错误处理机制

Error 实例对象

(1)JavaScript 原生提供Error构造函数,所有抛出的错误都是这个构造函数的实例。

var err = new Error('出错了');
err.message // "出错了"

(2)Error的属性

  • message:错误提示信息
  • name:错误名称(非标准属性)
  • stack:错误的堆栈(非标准属性)
if (error.name) {
  console.log(error.name + ': ' + error.message);
}
function throwit() {
  throw new Error('');
}

function catchit() {
  try {
    throwit();
  } catch(e) {
    console.log(e.stack); // print stack trace
  }
}

catchit()
// Error
//    at throwit (~/examples/throwcatch.js:9:11)
//    at catchit (~/examples/throwcatch.js:3:9)
//    at repl:1:5

原生错误类型

(1)SyntaxError`对象是解析代码时发生的语法错误。

// 变量名错误
var 1a;
// Uncaught SyntaxError: Invalid or unexpected token

// 缺少括号
console.log 'hello');
// Uncaught SyntaxError: Unexpected string

(2)ReferenceError对象是引用一个不存在的变量时发生的错误。

// 使用一个不存在的变量
unknownVariable
// Uncaught ReferenceError: unknownVariable is not defined

(3)将一个值分配给无法分配的对象,比如对函数的运行结果赋值。

// 等号左侧不是变量
console.log() = 1
// Uncaught ReferenceError: Invalid left-hand side in assignment

(4)RangeError对象是一个值超出有效范围时发生的错误。主要有几种情况,一是数组长度为负数,二是Number对象的方法参数超出范围,以及函数堆栈超过最大值。

// 数组长度不得为负数
new Array(-1)
// Uncaught RangeError: Invalid array length

(5)TypeError对象是变量或参数不是预期类型时发生的错误。比如,对字符串、布尔值、数值等原始类型的值使用new命令,就会抛出这种错误,因为new命令的参数应该是一个构造函数。

new 123
// Uncaught TypeError: number is not a func

var obj = {};
obj.unknownMethod() // 调用对象不存在的方法
// Uncaught TypeError: obj.unknownMethod is not a function

(6)URIError对象是 URI 相关函数的参数不正确时抛出的错误,主要涉及encodeURI()decodeURI()encodeURIComponent()decodeURIComponent()escape()unescape()这六个函数。

decodeURI('%2')
// URIError: URI malformed

(7)eval函数没有被正确执行时,会抛出EvalError错误。该错误类型已经不再使用了,只是为了保证与以前代码兼容,才继续保留。
(8)以上这6种派生错误,连同原始的Error对象,都是构造函数。开发者可以使用它们,手动生成错误对象的实例。这些构造函数都接受一个参数,代表错误提示信息(message)。

var err1 = new Error('出错了!');
var err2 = new RangeError('出错了,变量超出有效范围!');
var err3 = new TypeError('出错了,变量类型无效!');

err1.message // "出错了!"
err2.message // "出错了,变量超出有效范围!"
err3.message // "出错了,变量类型无效!"

自定义错误

function UserError(message) {
  this.message = message || '默认信息';
  this.name = 'UserError';
}

UserError.prototype = new Error();
UserError.prototype.constructor = UserError;
new UserError('这是自定义的错误!');

throw 语句

(1)throw语句的作用是手动中断程序执行,抛出一个错误。

if (x <= 0) {
  throw new Error('x 必须为正数');
}
// Uncaught ReferenceError: x is not defined

(2)throw也可以抛出自定义错误。

function UserError(message) {
  this.message = message || '默认信息';
  this.name = 'UserError';
}

throw new UserError('出错了!');
// Uncaught UserError {message: "出错了!", name: "UserError"}

(3)throw可以抛出任何类型的值。也就是说,它的参数可以是任何值。

// 抛出一个字符串
throw 'Error!';
// Uncaught Error!

// 抛出一个数值
throw 42;
// Uncaught 42

// 抛出一个布尔值
throw true;
// Uncaught true

// 抛出一个对象
throw {
  toString: function () {
    return 'Error!';
  }
};
// Uncaught {toString: ƒ}

try...catch 结构

(1)avaScript 提供了try...catch结构,允许对错误进行处理,选择是否往下执行。

try {
  throw new Error('出错了!');
} catch (e) {
  console.log(e.name + ": " + e.message);
  console.log(e.stack);
}
// Error: 出错了!
//   at <anonymous>:3:9
//   ...

(2)catch代码块捕获错误之后,程序不会中断,会按照正常流程继续执行下去。

try {
  throw "出错了";
} catch (e) {
  console.log(111);
}
console.log(222);
// 111
// 222

(3)catch代码块之中,还可以再抛出错误,甚至使用嵌套的try...catch结构。

var n = 100;

try {
  throw n;
} catch (e) {
  if (e <= 50) {
    // ...
  } else {
    throw e;
  }
}
// Uncaught 100

(4)为了捕捉不同类型的错误,catch代码块之中可以加入判断语句。

try {
  foo.bar();
} catch (e) {
  if (e instanceof EvalError) {
    console.log(e.name + ": " + e.message);
  } else if (e instanceof RangeError) {
    console.log(e.name + ": " + e.message);
  }
  // ...
}

finally 代码块

(1)表示不管是否出现错误,都必需在最后运行的语句。

function cleansUp() {
  try {
    throw new Error('出错了……');
    console.log('此行不会执行');
  } finally {
    console.log('完成清理工作');
  }
}

cleansUp()
// 完成清理工作
// Uncaught Error: 出错了……
//    at cleansUp (<anonymous>:3:11)
//    at <anonymous>:10:1

(2)return语句的执行是排在finally代码之前,只是等finally代码执行完毕后才返回。

var count = 0;
function countUp() {
  try {
    return count;
  } finally {
    count++;
  }
}

countUp()
// 0
count
// 1

(3)catch代码块结束执行之前,会先执行finally代码块。

function f() {
  try {
    console.log(0);
    throw 'bug';
  } catch(e) {
    console.log(1);
    return true; // 这句原本会延迟到 finally 代码块结束再执行
    console.log(2); // 不会运行
  } finally {
    console.log(3);
    return false; // 这句会覆盖掉前面那句 return
    console.log(4); // 不会运行
  }

  console.log(5); // 不会运行
}

var result = f();
// 0
// 1
// 3

result
// false

(4)进入catch代码块之后,一遇到throw语句,就会去执行finally代码块,其中有return false语句,因此就直接返回了,不再会回去执行catch代码块剩下的部分了。

function f() {
  try {
    throw '出错了!';
  } catch(e) {
    console.log('捕捉到内部错误');
    throw e; // 这句原本会等到finally结束再执行
  } finally {
    return false; // 直接返回
  }
}

try {
  f();
} catch(e) {
  // 此处不会执行
  console.log('caught outer "bogus"');
}

(5)try代码块内部,还可以再使用try代码块。

try {
  try {
    consle.log('Hello world!'); // 报错
  }
  finally {
    console.log('Finally');
  }
  console.log('Will I run?');
} catch(error) {
  console.error(error.message);
}
// Finally
// consle is not defined

编程风格

(1)缩进,可使用空格,也可以使用TAB
(2)总是使用大括号表示区块。
(3)JavaScript 会自动添加句末的分号,导致一些难以察觉的错误。

return
{
  key: value
};

// 相当于
return;
{
  key: value
};

(4)圆括号

  1. 表示函数调用时,函数名与左括号之间没有空格。
  2. 表示函数定义时,函数名与左括号之间没有空格。
  3. 其他情况时,前面位置的语法元素与左括号之间,都有一个空格。

(5)行尾不使用分号的情况

  • for 和 while 循环
  • 分支语句:if,switch,try
  • 函数的声明语句

(6)do...while循环是有分号的。
(7)函数表达式仍然要使用分号。

var f = function f() {
};

(8)如果没有使用分号,大多数情况下,JavaScript 会自动添加。

var a = 1
// 等同于
var a = 1;

(9)如果下一行的开始可以与本行的结尾连在一起解释,JavaScript 就不会自动添加分号。

// 等同于 var a = 3
var
a
=
3

// 等同于 'abc'.length
'abc'
.length

// 等同于 return a + b;
return a +
b;

// 等同于 obj.foo(arg1, arg2);
obj.foo(arg1,
arg2);

// 等同于 3 * 2 + 10 * (27 / 6)
3 * 2
+
10 * (27 / 6)
x = y
(function () {
  // ...
})();

// 等同于
x = y(function () {...})();
// 引擎解释为 c(d+e)
var a = b + c
(d+e).toString();

// 引擎解释为 a = b/hi/g.exec(c).map(d)
// 正则表达式的斜杠,会当作除法运算符
a = b
/hi/g.exec(c).map(d);

// 解释为'b'['red', 'green'],
// 即把字符串当作一个数组,按索引取值
var a = 'b'
['red', 'green'].forEach(function (c) {
  console.log(c);
})

// 解释为 function (x) { return x }(a++)
// 即调用匿名函数,结果f等于0
var a = 0;
var f = function (x) { return x }
(a++)

(10)只有下一行的开始与本行的结尾,无法放在一起解释,JavaScript 引擎才会自动添加分号。

if (a < 0) a = 0
console.log(a)

// 等同于下面的代码,
// 因为 0console 没有意义
if (a < 0) a = 0;
console.log(a)

(11)如果一行的起首是“自增”(++)或“自减”(--)运算符,则它们的前面会自动添加分号。

a = b = c = 1

a
++
b
--
c

console.log(a, b, c)
// 1 2 0
// 等同于
a = b = c = 1;
a;
++b;
--c;

(12)如果continuebreakreturnthrow这四个语句后面,直接跟换行符,则会自动添加分号。
(13)由于解释引擎自动添加分号的行为难以预测,因此编写代码的时候不应该省略行尾的分号。
(14)有的代码库在第一行语句开始前,会加上一个分号。可以避免与其他脚本合并时,排在前面的脚本最后一行语句没有分号,导致运行出错的问题。

;var a = 1;
// ... 

(15)建议避免使用全局变量。如果不得不使用,可以考虑用大写字母表示变量名,这样更容易看出这是全局变量,比如UPPER_CASE
(16)JavaScript 会自动将变量声明“提升”(hoist)到代码块(block)的头部。

if (!x) {
  var x = {};
}

// 等同于
var x;
if (!x) {
  x = {};
}

(17)所有函数都应该在使用之前定义。函数内部的变量声明,都应该放在函数的头部。
(18)with可以减少代码的书写,但是会造成混淆。

with (o) {
 foo = bar;
} 

上面的代码,可以有四种运行结果:

o.foo = bar;
o.foo = o.bar;
foo = bar;
foo = o.bar; 

这四种结果都可能发生,取决于不同的变量是否有定义。因此,不要使用with语句。
(19)相等运算符会自动转换变量类型,造成很多意想不到的情况。

0 == ''// true
1 == true // true
2 == true // false
0 == '0' // true
false == 'false' // false
false == '0' // true
' trn ' == 0 // true 

因此,建议不要使用相等运算符(==),只使用严格相等运算符(===)。
(20)建议不要将不同目的的语句,合并成一行。
(21)所有的++运算符都可以用+= 1代替。

++x
// 等同于
x += 1;

(22)switch...case不使用大括号,不利于代码形式的统一。此外,这种结构类似于goto语句,容易造成程序流程的混乱,使得代码结构混乱不堪,不符合面向对象编程的原则。建议switch...case结构可以用对象结构代替。

function doAction(action) {
  switch (action) {
    case 'hack':
      return 'hack';
    case 'slash':
      return 'slash';
    case 'run':
      return 'run';
    default:
      throw new Error('Invalid action.');
  }
}

上面的代码建议改写成对象结构。

function doAction(action) {
  var actions = {
    'hack': function () {
      return 'hack';
    },
    'slash': function () {
      return 'slash';
    },
    'run': function () {
      return 'run';
    }
  };

  if (typeof actions[action] !== 'function') {
    throw new Error('Invalid action.');
  }

  return actions[action]();
}

xiaopohair
68 声望26 粉丝

把这辈子活的热气腾腾!