文章目录

  • 语法专题
    • 数据类型的转换
      • 1 概述
      • 2 强制转换
      • 3 自动转换
  • 错误处理机制
    • 1 Error 实例对象
    • 2 原生错误类型
      • 2.1 SyntaxError 对象
      • 2.2 ReferenceError 对象
      • 2.3 RangeError 对象
      • 2.4 TypeError 对象
      • 2.5 URIError 对象
      • 2.6 EvalError 对象
      • 2.7 总结
    • 3 自定义错误
    • 4 throw 语句
    • 5 try...catch 结构
    • 6 finally 代码块
  • 编程风格
    • 1 概述
    • 2 缩进
    • 3 区块
    • 4 圆括号
    • 5 行尾的分号
      • 5.1 不使用分号的情况
      • 5.2 分号的自动添加
    • 6 全局变量
    • 7 变量声明
    • 8 with 语句
    • 9 相等和严格相等
    • 10 语句的合并
    • 11 自增和自减运算符
    • 12 switch...case结构
  • console 对象与控制台
    • 2 console 对象的静态方法
      • 2.1 console.log(),console.info(),console.debug()
      • 2.2 console.warn(),console.error()
      • 2.3 console.table()
      • 2.4 console.count()
      • 2.5 console.dir(),console.dirxml()
      • 2.6 console.assert()
      • 2.7 console.time(),console.timeEnd()
      • 2.8 console.group(),console.groupEnd(),console.groupCollapsed()
      • 2.9 console.trace(),console.clear()
    • 3 控制台命令行 API
    • 4 debugger 语句

语法专题

数据类型的转换

1 概述

JavaScript 是一种动态类型语言,变量没有类型限制,可以随时赋予任意值。

var x = y ? 1 : 'a';

x的类型没法在编译阶段就知道,必须等到运行时才能知道。

虽然变量的数据类型是不确定的,但是各种运算符对数据类型是有要求的。如果运算符发现,运算子的类型与预期不符,就会自动转换类型。比如,减法运算符预期左右两侧的运算子应该是数值,如果不是,就会自动将它们转为数值。

'4' - '3' // 1

本章讲解数据类型自动转换的规则。在此之前,先讲解如何手动强制转换数据类型。

2 强制转换
  • 2.1 Number()

使用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

Number函数将字符串转为数值,要比parseInt函数严格很多。基本上,只要有一个字符无法转成数值,整个字符串就会被转为NaNparseInt逐个解析字符,而Number函数整体转换字符串的类型。

parseInt('42 cats') // 42
Number('42 cats') // NaN

另外,parseIntNumber函数都会自动过滤一个字符串前导和后缀的空格。

parseInt('\t\v\r12.34\n') // 12
Number('\t\v\r12.34\n') // 12.34

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

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

之所以会这样,是因为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());
}

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

Number({}) // NaN

如果toString方法返回的不是原始类型的值,结果就会报错。

var obj = {valueOf: function () {return {};},toString: function () {return {};}
};Number(obj)
// TypeError: Cannot convert object to primitive value

从上例还可以看到,valueOftoString方法,都是可以自定义的。

  • 2.2 String()

String函数可以将任意类型的值转化成字符串,转换规则如下。

原始类型值:
数值:转为相应的字符串。
字符串:转换后还是原来的值。
布尔值:true转为字符串"true"false转为字符串"false"
undefined:转为字符串"undefined"
null:转为字符串"null"

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

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

疑问:如果是伪数组呢?

String方法背后的转换规则,与Number方法基本相同,只是互换了valueOf方法和toString方法的执行顺序。

1 先调用对象自身的toString方法。如果返回原始类型的值,则对该值使用String函数,不再进行以下步骤。

2 如果toString方法返回的是对象,再调用原对象的valueOf方法。如果valueOf方法返回原始类型的值,则对该值使用String函数,不再进行以下步骤。
3 如果valueOf方法返回的是对象,就报错。

String({a: 1})
// "[object Object]"// 等同于
String({a: 1}.toString())
// "[object Object]"

如果toString法和valueOf方法,返回的都是对象,就会报错。

var obj = {valueOf: function () {return {};},toString: function () {return {};}
};String(obj)
// TypeError: Cannot convert object to primitive value

下面是通过自定义toString方法,改变返回值的例子。

String({toString: function () {return 3;}
})
// "3"String({valueOf: function () {return 2;}
})
// "[object Object]"String({valueOf: function () {return 2;},toString: function () {return 3;}
})
// "3"
  • Boolean()

Boolean()函数可以将任意类型的值转为布尔值。
它的转换规则相对简单:除了以下五个值的转换结果为false,其他的值全部为true

undefined
null
0(包含-0和+0)
NaN
‘’(空字符串)

注意,所有对象(包括空对象)的转换结果都是true,甚至连false对应的布尔对象new Boolean(false)也是true(详见《原始类型值的包装对象》一章)。

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

所有对象的布尔值都是true,这是因为 JavaScript 语言设计的时候,出于性能的考虑,如果对象需要计算才能得到布尔值,对于obj1 && obj2这样的场景,可能会需要较多的计算。为了保证性能,就统一规定,对象的布尔值为true

3 自动转换

下面介绍自动转换,它是以强制转换为基础的。

遇到以下三种情况时,JavaScript 会自动转换数据类型,即转换是自动完成的,用户不可见。

第一种情况,不同类型的数据互相运算。

123 + 'abc' // "123abc"

第二种情况,对非布尔值类型的数据求布尔值。

if ('abc') {console.log('hello')
}  // "hello"

第三种情况,对非数值类型的值使用一元运算符(即+和-)。

+ {foo: 'bar'} // NaN
- [1, 2, 3] // NaN

自动转换的规则是这样的:预期什么类型的值,就调用该类型的转换函数。比如,某个位置预期为字符串,就调用String()函数进行转换。如果该位置既可以是字符串,也可能是数值,那么默认转为数值。

由于自动转换具有不确定性,而且不易除错,建议在预期为布尔值、数值、字符串的地方,全部使用Boolean()Number()String()函数进行显式转换。

  • 3.1 自动转换为布尔值

JavaScript 遇到预期为布尔值的地方(比如if语句的条件部分),就会将非布尔值的参数自动转换为布尔值。系统内部会自动调用Boolean()函数。

因此除了以下五个值,其他都是自动转为true。

  • undefined
  • null
  • +0或-0
  • NaN
  • ‘’(空字符串)

下面两种写法,有时也用于将一个表达式转为布尔值。

// 写法一
expression ? true : false// 写法二
!! expression
  • 3.2 自动转换为字符串
    JavaScript 遇到预期为字符串的地方,就会将非字符串的值自动转为字符串。具体规则是,先将复合类型的值转为原始类型的值,再将原始类型的值转为字符串。

字符串的自动转换,主要发生在字符串的加法运算(+)时。当一个值为字符串,另一个值为非字符串,则后者转为字符串。

'5' + 1 // '51'
'5' + true // "5true"
'5' + false // "5false"
'5' + {} // "5[object Object]"
'5' + [] // "5"
'5' + function (){} // "5function (){}"
'5' + undefined // "5undefined"
'5' + null // "5null"

这种自动转换很容易出错。

var obj = {width: '100'
};obj.width + 20 // "10020"

上面代码中,开发者可能期望返回120,但是由于自动转换,实际上返回了一个字符10020

  • 3.3 自动转换为数值

JavaScript 遇到预期为数值的地方,就会将参数值自动转换为数值。系统内部会自动调用Number()函数。

除了加法运算符(+)有可能把运算子转为字符串,其他运算符都会把运算子自动转成数值。

'5' - '2' // 3
'5' * '2' // 10
true - 1  // 0
false - 1 // -1
'1' - 1   // 0
'5' * []    // 0
false / '5' // 0
'abc' - 1   // NaN
null + 1 // 1
undefined + 1 // NaN

上面代码中,运算符两侧的运算子,都被转成了数值。

注意:null转为数值时为0,而undefined转为数值时为NaN

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

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

错误处理机制

1 Error 实例对象

JavaScript 解析或运行时,一旦发生错误,引擎就会抛出一个错误对象。JavaScript 原生提供Error构造函数,所有抛出的错误都是这个构造函数的实例。抛出Error实例对象以后,整个程序就中断在发生错误的地方,不再往下执行。

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

JavaScript 语言标准只提到,Error实例对象必须有message属性,表示出错时的提示信息,没有提到其他属性。大多数 JavaScript 引擎,对Error实例还提供namestack属性,分别表示错误的名称和错误的堆栈,但它们是非标准的,不是每种实现都有。

stack属性用来查看错误发生时的堆栈。

function throwit() {throw new Error('出错了');
}function catchit() {try {throwit();} catch(e) {console.log(e.stack); }
}catchit()
// Error 出错了
//    at throwit (~/examples/throwcatch.js:9:11)
//    at catchit (~/examples/throwcatch.js:3:9)
//    at repl:1:5

上面代码中,错误堆栈的最内层是throwit函数,然后是catchit函数,最后是函数的运行环境。

2 原生错误类型

2.1 SyntaxError 对象

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

// 变量名错误
var 1a;
// Uncaught SyntaxError: Invalid or unexpected token// 缺少括号
console.log 'hello');
// Uncaught SyntaxError: Unexpected string
2.2 ReferenceError 对象

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

unknownVariable // 使用一个不存在的变量
// Uncaught ReferenceError: unknownVariable is not defined// 另一种触发场景是,将一个值分配给无法分配的对象,比如对函数的运行结果赋值。
console.log() = 1 // 等号左侧不是变量
// Uncaught ReferenceError: Invalid left-hand side in assignment
2.3 RangeError 对象

RangeError对象是一个值超出有效范围时发生的错误。主要有几种情况:

  • 数组长度为负数;
  • Number对象的方法参数超出范围;
  • 函数堆栈超过最大值。
// 数组长度不得为负数
new Array(-1)
// Uncaught RangeError: Invalid array length
2.4 TypeError 对象

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

new 123
// Uncaught TypeError: number is not a funcvar obj = {};
obj.unknownMethod()
// Uncaught TypeError: obj.unknownMethod is not a function

上面代码的第二种情况,调用对象不存在的方法,也会抛出TypeError错误,因为obj.unknownMethod的值是undefined,而不是一个函数。

2.5 URIError 对象

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

decodeURI('%2')
// URIError: URI malformed
2.6 EvalError 对象

eval函数没有被正确执行时,会抛出EvalError错误。该错误类型已经不再使用了,只是为了保证与以前代码兼容,才继续保留。

2.7 总结

以上这6种派生错误,连同原始的Error对象,都是构造函数。开发者可以使用它们,手动生成错误对象的实例。这些构造函数都接受一个参数,代表错误提示信息(message)。

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

3 自定义错误

除了 JavaScript 原生提供的 7 种错误对象,还可以定义自己的错误对象。

function UserError(message) {this.message = message || '默认信息';this.name = 'UserError';
}UserError.prototype = new Error();
UserError.prototype.constructor = UserError;new UserError('这是自定义的错误!');

4 throw 语句

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

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

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

实际上,throw可以抛出任何类型的值。也就是说,它的参数可以是任何值。

// 抛出一个字符串
throw 'Error!';
// Uncaught Error!// 抛出一个数值
throw 42;
// Uncaught 42// 抛出一个布尔值
throw true;
// Uncaught true// 抛出一个对象
throw {toString: function () {return 'Error!';}
};
// Uncaught {toString: ƒ}

对于 JavaScript 引擎来说,遇到throw语句,程序就中止了。引擎会接收到throw抛出的信息,可能是一个错误实例,也可能是其他类型的值。

5 try…catch 结构

一旦发生错误,程序就中止执行了。JavaScript 提供了try...catch结构,允许对错误进行处理,选择是否往下执行。

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

上面代码中,try代码块抛出错误(上例用的是throw语句),JavaScript 引擎就立即把代码的执行,转到catch代码块,或者说错误被catch代码块捕获了。catch接受一个参数,表示try代码块抛出的值。

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

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

为了捕捉不同类型的错误,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);}// ...
}

6 finally 代码块

try...catch结构允许在最后添加一个finally代码块,表示不管是否出现错误,都必需在最后运行的语句。

function cleansUp() {try {throw new Error('出错了……');console.log('此行不会执行');} finally {console.log('完成清理工作');}
}cleansUp()
// 完成清理工作
// Uncaught Error: 出错了……
//    at cleansUp (<anonymous>:3:11)
//    at <anonymous>:10:1

上面代码中,由于没有catch语句块,一旦发生错误,代码就会中断执行。中断执行之前,会先执行finally代码块,然后再向用户提示报错信息。

function idle(x) {try {console.log(x);return 'result';} finally {console.log('FINALLY');}
}idle('hello')
// hello
// FINALLY

上面代码中,try代码块没有发生错误,而且里面还包括return语句,但是finally代码块依然会执行。而且,这个函数的返回值还是result

下面的例子说明,return语句的执行是排在finally代码之前,只是等finally代码执行完毕后才返回。

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

下面是finally代码块用法的典型场景。

openFile();try {writeFile(Data);
} catch(e) {handleError(e);
} finally {closeFile();
}

上面代码首先打开一个文件,然后在try代码块中写入文件,如果没有发生错误,则运行finally代码块关闭文件;一旦发生错误,则先使用catch代码块处理错误,再使用finally代码块关闭文件。

下面的例子充分反映了try...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; // 这句会覆盖掉前面那句 returnconsole.log(4); // 不会运行}console.log(5); // 不会运行
}var result = f();
// 0
// 1
// 3result
// false

catch代码块之中,触发转入finally代码块的标志,不仅有return语句,还有throw语句。

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

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 概述

“编程风格”(programming style)指的是编写代码的样式规则。不同的程序员,往往有不同的编程风格。

有人说,编译器的规范叫做“语法规则”(grammar),这是程序员必须遵守的;而编译器忽略的部分,就叫“编程风格”(programming style),这是程序员可以自由选择的。这种说法不完全正确,程序员固然可以自由选择编程风格,但是好的编程风格有助于写出质量更高、错误更少、更易于维护的程序。

所以,编程风格的选择不应该基于个人爱好、熟悉程度、打字量等因素,而要考虑如何尽量使代码清晰易读、减少出错。你选择的,不是你喜欢的风格,而是一种能够清晰表达你的意图的风格。这一点,对于 JavaScript 这种语法自由度很高的语言尤其重要。

必须牢记的一点是,如果你选定了一种“编程风格”,就应该坚持遵守,切忌多种风格混用。如果你加入他人的项目,就应该遵守现有的风格。

2 缩进

行首的空格和 Tab 键,都可以产生代码缩进效果(indent)。

Tab 键可以节省击键次数,但不同的文本编辑器对 Tab 的显示不尽相同,有的显示四个空格,有的显示两个空格,所以有人觉得,空格键可以使得显示效果更统一。

无论你选择哪一种方法,都是可以接受的,要做的就是始终坚持这一种选择。不要一会使用 Tab 键,一会使用空格键。

3 区块

如果循环和判断的代码体只有一行,JavaScript 允许该区块(block)省略大括号。

if (a)b();c();// 上面代码的原意可能是下面这样。
if (a) {b();c();
}// 但是,实际效果却是下面这样。
if (a) {b();
}c();

因此,建议总是使用大括号表示区块。

另外,区块起首的大括号的位置,有许多不同的写法。最流行的有两种,一种是起首的大括号另起一行。另一种是起首的大括号跟在关键字的后面。

block
{// ...
}block {// ...
}

一般来说,这两种写法都可以接受。但是,JavaScript 要使用后一种,因为 JavaScript 会自动添加句末的分号,导致一些难以察觉的错误。

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

上面的代码的原意,是要返回一个对象,但实际上返回的是undefined,因为 JavaScript 自动在return语句后面添加了分号。为了避免这一类错误,需要写成下面这样。因此,表示区块起首的大括号,不要另起一行。

return {key : value
};

4 圆括号

圆括号(parentheses)在 JavaScript 中有两种作用:

  1. 一种表示函数的调用;
  2. 另一种表示表达式的组合(grouping)。
// 圆括号表示函数的调用
console.log('abc');// 圆括号表示表达式的组合
(1 + 2) * 3

建议可以用空格,区分这两种不同的括号。

  1. 表示函数调用时,函数名与左括号之间没有空格。
  2. 表示函数定义时,函数名与左括号之间没有空格。
  3. 其他情况时,前面位置的语法元素与左括号之间,都有一个空格。
foo (bar)
return(a+b);
if(a === 0) {...}
function foo (b) {...}
function(x) {...}

按照上面的规则,上面的写法都是不规范的。上面代码的最后一行是一个匿名函数,function是语法关键字,不是函数名,所以与左括号之间应该要有一个空格。

5 行尾的分号

分号表示一条语句的结束。JavaScript 允许省略行尾的分号。事实上,确实有一些开发者行尾从来不写分号。但是,由于下面要讨论的原因,建议还是不要省略这个分号。

5.1 不使用分号的情况

首先,以下三种情况,语法规定本来就不需要在结尾添加分号。这三种情况,如果使用了分号,并不会出错。因为,解释引擎会把这个分号解释为空语句。

  1. for 和 while 循环
for ( ; ; ) {} // 没有分号while (true) {} // 没有分号

注意,do...while循环是有分号的。

do {a--;
} while(a > 0); // 分号不能省略
  1. 分支语句:if,switch,try
if (true) {} // 没有分号switch () {} // 没有分号try {} catch {} // 没有分号
  1. 函数的声明语句
function f() {} // 没有分号

注意,函数表达式仍然要使用分号。

var f = function f() {};
5.2 分号的自动添加

除了上一节的三种情况,所有语句都应该使用分号。但是,如果没有使用分号,大多数情况下,JavaScript 会自动添加。

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

这种语法特性被称为“分号的自动添加”(Automatic Semicolon Insertion,简称 ASI)。

因此,有人提倡省略句尾的分号。麻烦的是,如果下一行的开始可以与本行的结尾连在一起解释,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++)

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

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

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

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

如果continuebreakreturnthrow这四个语句后面,直接跟换行符,则会自动添加分号。这意味着,如果return语句返回的是一个对象的字面量,起首的大括号一定要写在同一行,否则得不到预期结果。

return
{ first: 'Jane' };// 解释成
return;
{ first: 'Jane' };

由于解释引擎自动添加分号的行为难以预测,因此编写代码的时候不应该省略行尾的分号。

不应该省略结尾的分号,还有一个原因。有些 JavaScript 代码压缩器(uglifier)不会自动添加分号,因此遇到没有分号的结尾,就会让代码保持原状,而不是压缩成一行,使得压缩无法得到最优的结果。

另外,不写结尾的分号,可能会导致脚本合并出错。所以,有的代码库在第一行语句开始前,会加上一个分号。

;var a = 1;
// ...

上面这种写法就可以避免与其他脚本合并时,排在前面的脚本最后一行语句没有分号,导致运行出错的问题。

6 全局变量

JavaScript 最大的语法缺点,可能就是全局变量对于任何一个代码块,都是可读可写。这对代码的模块化和重复使用,非常不利。

因此,建议避免使用全局变量。如果不得不使用,可以考虑用大写字母表示变量名,这样更容易看出这是全局变量,比如UPPER_CASE

7 变量声明

JavaScript 会自动将变量声明“提升”(hoist)到代码块(block)的头部。

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

为了避免可能出现的问题,最好把变量声明都放在代码块的头部。

for (var i = 0; i < 10; i++) {// ...
}// 写成
var i;
for (i = 0; i < 10; i++) {// ...
}

上面这样的写法,就容易看出存在一个全局的循环变量i

另外,所有函数都应该在使用之前定义。函数内部的变量声明,都应该放在函数的头部。

8 with 语句

with可以减少代码的书写,但是会造成混淆。

with (o) {foo = bar;
}

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

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

9 相等和严格相等

JavaScript 有两个表示相等的运算符:“相等”(==)和“严格相等”(===)。

相等运算符会自动转换变量类型,造成很多意想不到的情况。

0 == ''// true
1 == true // true
2 == true // false
0 == '0' // true
false == 'false' // false
false == '0' // true
' \t\r\n ' == 0 // true

因此,建议不要使用相等运算符(==),只使用严格相等运算符(===)。

10 语句的合并

有些程序员追求简洁,喜欢合并不同目的的语句。比如,原来的语句是

a = b;
if (a) {// ...
}

他喜欢写成下面这样。

if (a = b) {// ...
}

虽然语句少了一行,但是可读性大打折扣,而且会造成误读,让别人误解这行代码的意思是下面这样。

if (a === b){// ...
}

11 自增和自减运算符

自增(++)和自减(–)运算符,放在变量的前面或后面,返回的值不一样,很容易发生错误。事实上,所有的++运算符都可以用+= 1代替。

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

改用+= 1,代码变得更清晰了。
建议自增(++)和自减(--)运算符尽量使用+=-=代替。

12 switch…case结构

switch...case结构要求,在每一个case的最后一行必须是break语句,否则会接着运行下一个case。这样不仅容易忘记,还会造成代码的冗长。

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

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

因此,建议switch...case结构可以用对象结构代替。

console 对象与控制台

console对象是 JavaScript 的原生对象,它有点像 Unix 系统的标准输出stdout和标准错误stderr,可以输出各种信息到控制台,并且还提供了很多有用的辅助方法。

console的常见用途有两个:

  • 调试程序,显示网页代码运行时的错误信息。
  • 提供了一个命令行接口,用来与网页代码互动。

console对象的浏览器实现,包含在浏览器自带的开发工具之中。以 Chrome 浏览器的“开发者工具”(Developer Tools)为例,可以使用下面三种方法的打开它。

打开开发者工具以后,顶端有多个面板。

  • Elements:查看网页的 HTML 源码和 CSS 代码。
    -Resources:查看网页加载的各种资源文件(比如代码文件、字体文件 CSS 文件等),以及在硬盘上创建的各种内容(比如本地缓存、Cookie、Local Storage等)。
  • Network:查看网页的 HTTP 通信情况。
  • Sources:查看网页加载的脚本源码。
  • Timeline:查看各种网页行为随时间变化的情况。
  • Performance:查看网页的性能情况,比如 CPU 和内存消耗。
  • Console:用来运行 JavaScript 命令。

2 console 对象的静态方法

2.1 console.log(),console.info(),console.debug()

console.log方法用于在控制台输出信息。它可以接受一个或多个参数,将它们连接起来输出。
console.log方法会自动在每次输出的结尾,添加换行符。
如果第一个参数是格式字符串(使用了格式占位符),console.log方法将依次用后面的参数替换占位符,然后再进行输出。

console.log(' %s + %s = %s', 1, 1, 2)
//  1 + 1 = 2

console.log方法支持以下占位符,不同类型的数据必须使用对应的占位符。

  • %s 字符串
  • %d 整数
  • %i 整数
  • %f 浮点数
  • %o 对象的链接
  • %c CSS 格式字符串

使用%c占位符时,对应的参数必须是 CSS 代码,用来对输出内容进行 CSS 渲染。

console.log('%cThis text is styled!','color: red; background: yellow; font-size: 24px;'
)

console.infoconsole.log方法的别名,用法完全一样。只不过console.info方法会在输出信息的前面,加上一个蓝色图标。

console.debug方法与console.log方法类似,会在控制台输出调试信息。但是,默认情况下,console.debug输出的信息不会显示,只有在打开显示级别在verbose的情况下,才会显示。

console对象的所有方法,都可以被覆盖。因此,可以按照自己的需要,定义console.log方法。

['log', 'info', 'warn', 'error'].forEach(function(method) {console[method] = console[method].bind(console,new Date().toISOString());
});console.log("出错了!");
// 2014-05-18T09:00.000Z 出错了!

上面代码表示,使用自定义的console.log方法,可以在显示结果添加当前时间。

2.2 console.warn(),console.error()

warn方法和error方法也是在控制台输出信息,它们与log方法的不同之处在于,warn方法输出信息时,在最前面加一个黄色三角,表示警告;error方法输出信息时,在最前面加一个红色的叉,表示出错。同时,还会高亮显示输出文字和错误发生的堆栈。其他方面都一样。

console.error('Error: %s (%i)', 'Server is not responding', 500)
// Error: Server is not responding (500)
console.warn('Warning! Too few nodes (%d)', document.childNodes.length)
// Warning! Too few nodes (1)

可以这样理解,log方法是写入标准输出(stdout),warn方法和error方法是写入标准错误(stderr)。

2.3 console.table()

对于某些复合类型的数据,console.table方法可以将其转为表格显示。

var languages = [{ name: "JavaScript", fileExtension: ".js" },{ name: "TypeScript", fileExtension: ".ts" },{ name: "CoffeeScript", fileExtension: ".coffee" }
];console.table(languages);

自定义索引的例子

var languages = {csharp: { name: "C#", paradigm: "object-oriented" },fsharp: { name: "F#", paradigm: "functional" }
};console.table(languages);

2.4 console.count()

count方法用于计数,输出它被调用了多少次。

function greet(user) {console.count();return 'hi ' + user;
}greet('bob')
//  : 1
// "hi bob"greet('alice')
//  : 2
// "hi alice"greet('bob')
//  : 3
// "hi bob"

该方法可以接受一个字符串作为参数,作为标签,对执行次数进行分类。

function greet(user) {console.count(user);return "hi " + user;
}greet('bob')
// bob: 1
// "hi bob"greet('alice')
// alice: 1
// "hi alice"greet('bob')
// bob: 2
// "hi bob"
2.5 console.dir(),console.dirxml()

dir方法用来对一个对象进行检查(inspect),并以易于阅读和打印的格式显示。

console.log({f1: 'foo', f2: 'bar'})
// Object {f1: "foo", f2: "bar"}console.dir({f1: 'foo', f2: 'bar'})
// Object
//   f1: "foo"
//   f2: "bar"
//   __proto__: Object

该方法对于输出 DOM 对象非常有用,因为会显示 DOM 对象的所有属性。

console.dir(document.body)

Node 环境之中,还可以指定以代码高亮的形式输出。

console.dir(obj, {colors: true})

dirxml方法主要用于以目录树的形式,显示 DOM 节点。

console.dirxml(document.body)

如果参数不是 DOM 节点,而是普通的 JavaScript 对象,console.dirxml等同于console.dir

console.dirxml([1, 2, 3])
// 等同于
console.dir([1, 2, 3])
2.6 console.assert()

console.assert方法主要用于程序运行过程中,进行条件判断,如果不满足条件,就显示一个错误,但不会中断程序执行。这样就相当于提示用户,内部状态不正确。

它接受两个参数,第一个参数是表达式,第二个参数是字符串。只有当第一个参数为false,才会提示有错误,在控制台输出第二个参数,否则不会有任何结果。

console.assert(false, '判断条件不成立')
// Assertion failed: 判断条件不成立// 相当于
try {if (!false) {throw new Error('判断条件不成立');}
} catch(e) {console.error(e);
}
2.7 console.time(),console.timeEnd()

time方法表示计时开始,timeEnd方法表示计时结束。它们的参数是计时器的名称。调用timeEnd方法之后,控制台会显示“计时器名称: 所耗费的时间”。

console.time('Array initialize');var array= new Array(1000000);
for (var i = array.length - 1; i >= 0; i--) {array[i] = new Object();
};console.timeEnd('Array initialize');
// Array initialize: 1914.481ms
2.8 console.group(),console.groupEnd(),console.groupCollapsed()

console.groupconsole.groupEnd这两个方法用于将显示的信息分组。它只在输出大量信息时有用,分在一组的信息,可以用鼠标折叠/展开。

console.group('一级分组');
console.log('一级分组的内容');console.group('二级分组');
console.log('二级分组的内容');console.groupEnd(); // 二级分组结束
console.groupEnd(); // 一级分组结束
//
一级分组一级分组的内容二级分组二级分组的内容

console.groupCollapsed方法与console.group方法很类似,唯一的区别是该组的内容,在第一次显示时是收起的(collapsed),而不是展开的。

2.9 console.trace(),console.clear()

console.trace方法显示当前执行的代码在堆栈中的调用路径。

console.trace()
// console.trace()
//   (anonymous function)
//   InjectedScript._evaluateOn
//   InjectedScript._evaluateAndWrap
//   InjectedScript.evaluate

console.clear方法用于清除当前控制台的所有输出,将光标回置到第一行。如果用户选中了控制台的“Preserve log”选项,console.clear方法将不起作用。

3 控制台命令行 API

浏览器控制台中,除了使用console对象,还可以使用一些控制台自带的命令行方法。

  • $_ 属性返回上一个表达式的值。
2 + 2
// 4
$_
// 4
  • $0 - $4

控制台保存了最近5个在 Elements 面板选中的 DOM 元素,$0代表倒数第一个(最近一个),$1代表倒数第二个,以此类推直到$4

4 debugger 语句

debugger语句主要用于除错,作用是设置断点。如果有正在运行的除错工具,程序运行到debugger语句时会自动停下。如果没有除错工具,debugger语句不会产生任何结果,JavaScript 引擎自动跳过这一句。

「前端基础」阮一峰JavaScript教程笔记(二)相关推荐

  1. 「前端基础」阮一峰JavaScript教程笔记(一)

    文章目录 入门篇 6 条件语句 数据类型 概述 null, undefined 和布尔值 1. null 和 undefined 2. 布尔值 数值 1 概述 2 数值的表示法 3 数值的进制 4 特 ...

  2. 拜读阮一峰JavaScript教程笔记

    读了不知道几遍,每次都没总结合做笔记,所以很多内容都记不住,如果平时没应用到实际中,那知识跟像过眼云烟,于是还是谢谢博客记记笔记,好好学习天天向上. 原教程链接: http://javascript. ...

  3. 「前端基础」旺财记账Vue2版本

    文章目录 4[项目]旺财 Vue 项目搭建 4.1 课前准备 4.2 使用@vue/cli创建项目 4.3 目录结构说明 4.4 添加代码片段snippets 4.5 JS或TS里使用@ 4.6 CS ...

  4. dreamweaver 正则表达式为属性值加上双引号_「前端篇」不再为正则烦恼

    作者:李一二 转发链接:https://mp.weixin.qq.com/s/PmzEbyFQ8FynIlXuUL0H-g 前言 有不少朋友都为写正则而头疼,不过笔者早已不为正则而烦恼了.本文分享一些 ...

  5. 「前端工程化」该怎么理解?

    大家好,我是若川.今天分享一篇「前端工程化」的好文.非广告,请放心阅读.可点击下方卡片关注我,或者查看系列文章.今天发文比较晚,以往都是定时早上7:30发文,也不知道是不是有点早. 一.什么是前端工程 ...

  6. Serverless :让「前端开发者」走向「应用研发者」

    技术的成熟度源自大规模的实践,在 Java 领域,阿里将自身的实践源源不断的反哺给微服务技术体系:在 Node.js 领域,阿里正掀起了前所未有的前端革命浪潮,将实践反哺给  Serverless 技 ...

  7. 「前端开发者」如何把握住「微信小程序」这波红利?

    由于前两周一直在老家处理重要事情,虽然朋友圈被「微信小程序」刷爆了,但并没有时间深入了解. 昨天回广州之后,第一件事情就是把「微信小程序」相关的文章.开发文档.设计规范全部看了一遍,基本上明白了「微信 ...

  8. 「前端-HTML」 HTML-表格-表单-第二篇

    前端-HTML-第二篇 这是HTML第二篇,共三篇,如需看其他篇请点击跳转 目录 前端-HTML-第二篇 HTML 列表 1.无序列表 2.有序列表 3.自定义列表 其他 pre标签 HTML实体 H ...

  9. 「软件测试基础」理论篇之软件测试概论

    文章目录 1. 软件 1.1 软件发展史 1.2 软件生命周期 1.3 软件缺陷 1.4 三种纠错技术 2. 软件过程 2.1 RUP 2.1.1 RUP各个阶段 2.1.2 RUP核心工作流 2.2 ...

最新文章

  1. 详解 ARM64 内核中对 52 位虚拟地址的支持
  2. 《编译与反编译技术实战》——2.1节编译器、解释器及其工作方式
  3. 信号与系统2021春季课程小论文批改
  4. python消息队列celery_消息队列(kafka/nsq等)与任务队列(celery/ytask等)到底有什么不同?...
  5. 数字图像处理技术详解程序_安装地暖施工程序有哪些 安装地暖技术要求是什么【详解】...
  6. 洛谷 P1044 栈 [卡特兰数]
  7. postmessage与sendmessage的区别
  8. Spring boot变量的初始化顺序
  9. 您的第一个Lagom服务– Java Microservices入门
  10. matplot绘制图形入门
  11. java forkjoin 简书_ForkJoinPool in Java
  12. linux docker 分配资源,Docker 容器资源限制
  13. 计算机专业英语词汇1690个单词
  14. maven setting.xml详解
  15. 找到微信聊天记录占空间的真正原因了
  16. DoublyLinkedList
  17. Data Analysis - Day7 - Pandas
  18. 小白终是踏上了这条不归路----小文的mysql学习笔记(23)---------流程控制结构
  19. 在MS Word 中添加 Mathtype 插件(vbe6ext.olb不能被加载问题 已解决)
  20. 去除latex中cctbook里面二级章节标题中前面的双s符号

热门文章

  1. vi编辑器下出现E325:ATTENTION的解决办法
  2. QT treewidget 双击信号与自定义槽】
  3. 你了解System.out.println()的真正含义吗?
  4. SIR疾病传播模型的简单数学原理
  5. java allocatedirect_java – ByteBuffer.allocateDirect()和MappedByteBuffer.load()之间的区别
  6. 统计基础(六)卡方分布
  7. KingbaseES数据库概念(二)--数据访问
  8. 安科瑞配电室综合监控系统保障电气设备安全
  9. 计算机局域网访问诲康硬盘,鐖诲康浠€涔堬紝鎰忔€濓紵
  10. CSS white-space属性效果(指定元素内的空白怎样处理)