五 语法专题
1 数据类型的转换
https://wangdoc.com/javascript/features/conversion.html#number
1.1 概述
JS 是一种动态类型语言,变量没有类型限制,可以随时赋予任何值。
即:
let a = 1;
a = "hello";
这种操作,在静态语言中是不允许的。
虽然变量对变量类型没要求,但是运算符对数据类型有要求。如果运算符发现运算子的类型与预期不符合,就会进行类型转换。
1.2 强制转换
1.2.1 Number
(1)原始类型的值
// 数值:转化后还是原来的值
Number(324) // 324
// 字符串,如果可以被解析为数字,则转化为数字,否则为 NaN
Number('324') // 324
Number('324abc') // NaN
// 空字符串、false 和 null 会被转为 0
Number('') // 0
Number(null) //0
Number(false) // 0
// undefined 转为 NaN
Number(undefined) // NaN
// 布尔值 true
Number(true) // 1
Number 函数将字符串转化为数值,要比 parseInt
严格很多。基本上只要有一个数字无法转为数值,整个字符串就会变为 NaN。
let a = "34ca"
Number(a) // NaN
parseInt(a) // 34
上面的代码中,parseInt
逐个解析字符串,而Number
函数整体转化字符串的类型。
当然, parseInt 和 Number 都会自动过滤一个字符串的前后的空格。
parseInt(" 23 "); // 23
Number(" 34 ") // 34
(2)对象
Number 方法的参数就是对象时,将返回 NaN,除非是包含单个数值的数组。
Number({a:1}) // NaN
Number([1,2,3]) // NaN
Number([5]) // 5
Number 背后的转换规则比较复杂。
- 调用对象自身的 valueof 方法。如果返回的类型是原始类型的值,则直接对该值使用
Number
函数,不再进行后续方法。 - 如果
valueOf
返回的还是对象,则改为调用对象自身的toString
方法。如果toString
方法返回原始类型的值,则对改值使用 Number 函数。 - 如果 toString 返回的还是对象,则返回 NaN
Number({}) // NaN
Number([]) // 0
1.2.2 String
String 函数可以将任意类型的值转化为字符串,转化规则如下:
(1) 原始类型值
- 数值:转为相应的字符串
- 字符串:转化后还是原来的值
- 布尔值:true 转化为 “true”, false 转化为字符串 “false”。
- undefined: 转化为字符串 "undefined"
- null: 转化为字符串 "null"
- NaN: 转化为字符串 "NaN"
String(123) // "123"
String('abc') // "abc"
String(true) // "true"
String(undefined) // "undefined"
String(NaN) // "NaN"
(2) 对象
String
方法的参数如果是对象,则返回一个类型字符串;如果是数组,返回该数组的字符串形式。
String({a:1}) // "[object object]"
String([1,2,3]) // "1,2,3"
String 方法背后的转换规则,与 Number
方法基本相同,只是互换了valueOf
方法和toString
方法的执行顺序。
- 先调用对象的自身的
toString
方法。如果返回原始类型的值,则对该值使用String
函数,不再进行以下步骤。 - 如果
toString
方法返回的是对象,再调用原对象的valueOf
方法。如果valueOf
方法返回原始类型的值,则对该值使用String
函数,不再进行以下步骤。 - 如果 valueOf 方法返回的是对象,就报错。
String({a : 1}) // "[object object]"
1.2.3 Boolean
以下五个值的结果转化为 false
,其他值全部为 true
- undefined
- null
- -0 或 +0
- NaN
- ''
注意,以下几个值的布尔值为 true
Boolean({}) // true
Boolean([]) // true
Boolean(new Boolean(false)) // true
空的对象在 Python 中就是 false
1.3 自动转换
遇到以下三种情况,JS 会自动进行数据类型转化。
第一种,不同数据类型的数据互相运算。
123 + "abc" // "123abc"
d第二种,对非布尔值的数据求布尔值。(这种 Python也会发生数据类型转化,而GO就非常严格,要求类型转化必须是显示的)
if("a"){
}
第三种,对于非数值类型的值使用一元运算符
+ "1" // 1
+ {a:1} // NaN
1.3.1 自动转换为布尔值
JS 遇到预期为布尔值的地方就会将非布尔值的参数自动转化为布尔值。
以下五个值的布尔值都是 false
NaN
''
undefined
0 -0
null
1.3.2 自动转化为字符串
一般使用加法时,如果一个运算子是字符串,另外一个就会转化为字符串。具体规则是,先将复合类型的值转为原始类型的值,再将原始类型的值转为字符串。
1.3.3 自动转化为数值
除了加法运算符优先转化为字符串,其他运算符都会把运算子转化为数值。
2 错误处理机制
https://wangdoc.com/javascript/features/error.html
2.1 Error 实例对象
JS 解析或运行时,一旦发生错误,就会抛出错误对象。JS 原生提供 Error 构造函数,所有抛出的错误都是这个构造函数的实例。
var err = new Error("出错了");
err.message // 出错了
Error 是一个构造函数,会生成一个实例对象。Error
构造函数接受一个参数,表示错误提示,可以从实例的 message
属性读到这个参数。
除了message
属性,还提供 name
和 stack
属性,分别表示错误的名称和错误的堆栈,但它们是非标准的,不是每种实现都有。
-
message
: 错误提示信息 -
name
: 错误名称(非标准属性) -
stack
:错误的堆栈(非标准属性)
使用name
和message
这两个属性,可以对发生什么错误有一个大概的了解。
if (error.name){
console.log(error.name + ": " + error.message);
}
使用 stack
属性来查看错误发生时的堆栈。
function throwIt(){
throw new Error('I am Error');
}
function catchIt(){
try{
throwIt();
} catch(e){
console.log(e.stack);
}
}
catchIt();
// Error: I am Error
// at throwIt(~/examples/)
// at catchIt()
// at <anonymous>
上面的代码中,错误堆栈的最内部是 throwIt 函数,然后是 catchIt 函数,最后是函数的运行环境
2.2 原生错误类型
Error 实例对象是最一般的错误类型,在此基础上,还派生了 6 种其他的错误对象。
2.2.1 SyntaxError 对象
SyntaxError 对象是解析代码时发生的语法错误。
var 1a;
// Uncaught SyntaxError: Invalid or unexpected token
2.2.2 ReferenceError 对象
ReferenceError 对象是引用一个不存在的变量时发生的错误。
// 使用一个不存在的变量
a
// Uncaught ReferenceError: unknownVariable is not defined
另外一种触发场景是,将一个值分配给无法分配的对象,比如对函数的运行结果或者 this
赋值。
// this 对象不能手动赋值
this = 1
// ReferenceError: Invalid left-hand side in assignment
2.2.3 RangeError 对象
RangeError 对象是一个值超出有效范围时发生的错误。主要情况,一是数组长度为负数,二是 Number 对象的方法超出范围,以及函数堆栈超过最大值。
// 数组长度不能为负数
new Array(-1)
在 Python 中索引越界也会抛 一个 outOfRange 的错误,但是这种在 JS 中是不存在这种情况的。
2.2.4 TypeError 对象
TypeError 对象是变量或参数不是预期类型就会发生错误。比如对字符串、布尔值、数值等原始类型的值使用 new 命令,就会抛出这种错误,因为 new 命令的参数应该是一个构造函数。
new 123
// Uncaught TypeError: 123 is not a constructor
var obj = {};
obj.a();
// Uncaught TypeError: obj.a is not a function
2.2.5 URIError 对象
URIError
对象是 URI 相关函数的参数不正确时抛出的错误,encodeURI()
/ decodeURI
/ encodeURIComponent
/ decodeURICompoent()
/ escape()
和 unescape()
这六个函数。
decodeURI('%2')
// Uncaught URIError: URI malformed
2.2.6 EvalError 对象
eval
函数没有被正确执行时,会抛出 EvalError
错误。该错误类型已经不再使用。保留只是为了跟代码兼容。
2.2.7 总结
以上 6 种错误类型连同原始的 Error
对象都是构造函数,开发者可以自定义函数。
var err1 = new Error("自定义 Error");
var err2 = new RangeError("I am Error");
2.3 自定义错误
function UserError(message){
this.message = message || "默认信息";
this.name = "UserError";
}
UserError.prototype = new Error();
UserError.prototype.constructor = UserError;
上面代码自定义一个错误对象 UserError , 让它继承 Error
对象。然后,就可以生成这种自定义类型的错误了。
new UserError('这是自定义的错误');
2.4 throw 语句
throw 是 Python 中的 raise。
但是 throw 想扔啥就扔啥。
throw "Error"; // 字符串
throw 42; // 数值
throw true; // 布尔值
2.5 try ... catch 结构
python 和 JS 都支持这种写法,这是我以前用的比较少的:
try{
}catch (a){
}catch (b){
}catch (c){
}
2.6 finally 代码块
3 编程风格
https://wangdoc.com/javascript/features/style.html
3.1 概述
3.2 缩进
我都是用 tab
3.3 区块
如果循环和判断只有一行,可以省略大括号。这种臭毛病在 JAVA 中也有。我不喜欢
3.4 圆括号
圆括号的作用有两种:一种表示函数的调用;另外一种是表达式的组合。
// 圆括号表示函数的调用
console.log("abc");
// 圆括号表示表达式的组合
(1 + 2) * 3
表示表达式组合的时候,加空格比较好
3.5 行尾的分号
添加分号更好,我认为。
3.5.1 不使用分号的情况
- for 和 while 循环
- if switch try
- 函数的声明
但是注意,函数表达式是需要添加分号
var f = ()=>{};
3.5.2 分号的自动添加
除了以上三种情况,JS 会自动添加。
如果 continue / break / return 和 throw 这四个语句后面,直接跟换行符,则会自动添加分号。这意味着,如果return
语句返回的是一个对象的字面量,起首的大括号一定要写在同一行,否则达不到预期的效果。
return
{ first: 'jane'};
// 解释成
return;
{first : 'jane'}
// 实际案例
function a(){
return
{first : 'Jane'};
}
另外不写分号,有些 JS 代码压缩工具不会自动添加分号,遇到没有分号结尾的代码就会让他保护原装。
不写分号,可能会导致脚本合并除错。所以有的代码库,会在第一行语句开始前加上分号
;var a = 1;
3.6 全局变量
https://wangdoc.com/javascript/features/style.html#%E6%A6%82%E8%BF%B0
全局变量对于任何一个代码块都是可读可写的,应该避免使用全局变量。
3.7 变量的声明
JS 会自动将变量声明提升到代码块的头部。
if (!x){
var x = {};
}
// 等同于
var x;
if (!x){
x = {};
}
这意味着,变量 x 是 if 代码块之前就存在的。为了避免可能出现的问题,最好把变量声明都放在代码块的头部。
for (var i = 0; i < 10; i++){
//...
}
// 写成
var i;
for(i = 0;i<10;i++){
// ...
}
这样的写法,就容易看出 i 是一个全局的循环变量 i。
另外,所有函数都应该在使用之前定义。函数内部的变量声明,都应该放在函数的头部。(不然它也会提升到头部的。)
3.8 with 语句
最好不用 with
3.9 相等和严格相等
使用严格相等
3.10 语句的合并
没事合并语句就是有病
3.11 自增和自减运算符
不要用 ++ ,而是 += 1
3.12 switch .. case 结构
o ,朕知道了
4 console 对象与控制台
4.1 console 对象
Chrome 控制台上的各个板块:
- Elements: 查看网页的 HTML 源码和 CSS 代码。
- Sources: 查看网页加载各种资源(代码文件/字体/css 文件),以及在硬盘上创建的各种内容(本地内容、Cookie、Local Storage 等)
- Network: 查看网页的 HTTP 通信情况。
- Performance: 查看网页的性能,比如 CPU 和内存消耗。
- Console: 运行 JS 命令
4.2 console 对象的静态方法
4.2.1 console.log(), console.info(), console.debug()
console.log 可以接受一个或多个参数,将它们连接起来输出。
console.log(1,"2")
console.log 还支持字符串格式化:
console.log("%s + %s",1,2)
// 1 + 2
console.log 方法支持以下占位符,不同类型的数据必须使用对应的占位符。
- %s 字符串
- %d 整数
- %i 整数
- %f 浮点数
- %o 对象的连接
- %c CSS 格式字符串
console.log("%cThis text is styled", 'color: red; background: yellow; font-size: 24px;')
info / debug 都是不同的级别而已,debug 默认情况下在不显示,只有开启 All levels 才显示。
4.2.2 console.warn(), console.error()
warn 和 error 也是不同的日志级别而已, error 的出现会有一个小红叉,warn 的出现会有一个黄色的惊叹号
4.2.3 console.table()
可以把 对象/列表 以表格的形式打印出来。
var a = [{a:0,b:1,c:2},{a:00,b:11,c:22},{a:000,b:111,c:222}]
index | a | b | c |
---|---|---|---|
0 | 0 | 1 | 2 |
1 | 00 | 11 | 22 |
2 | 000 | 111 | 222 |
4.2.4 console.count()
count
方法用于计数,输出它被调用了多少次。
function greet(user){
console.count();
return "hi " + user;
}
greet('bob');
// default: 1
// hi bob
greet("jack");
// default: 2
// hi jack
该函数还可以接受一个字符串作为参数,作为标签,可以区分不同参数的调用次数,真是挺神奇的。
function greet(user){
console.count(user);
return "hi " + user;
}
greet('bob');
bob: 1
greet("jack");
jack: 1
4.2.5 console.dir(), console.dirxml()
console.dir({f1: "foo", "f2": "bar"})
// objct
// f1: "foo"
// f2: "bar"
该方法对于输出 DOM 对象非常有用,会显示 DOM 对象的所有属性。
console.dir(document.body);
在 Node 的环境下,还可以指定以代码高亮的形式输出。
console.dir(obj, {colors: true});
dirxml 方法主要以目录数的形式,显示 DOM 节点。
console.dirxml(document.body)
如果不是 DOM 节点,而是普通的 JS 对象,则输出与 console.dir
相同。
4.2.6 console.assert()
console.assert 会对条件进行断言,如果被断言的语句为假,不会终端程序,而是输出一条错误日志。
console.assert(false, "判断条件不成立");
// Assertion failed: 判断条件不成立
相当于:
try{
if (not false){
throw new Error("判断条件不成立");
}
} catch(e){
console.error(e);
}
下面一个例子,判断子节点的个数是不是大于等于 2。
console.assert(document.body.childNodes.length < 0, "节点大于等于 2");
4.2.7 console.time(), console.timeEnd()
这两个方法用于计时,可以算出一个操作可以花费的准确事件。这两个方法是一对,传入的参数是计时器的名称
console.time("Array");
var array = new Array(1000000);
for(var i = array.length - 1; i >= 0; i--){
array[i] = new Object();
}
console.timeEnd("Array");
// Array initialize: 1914.481 ms;
4.2.8 console.group(), console.groupEnd(), console.groupCollapsed()
console.group("g1");
console.log("一级分组的内容")
console.group("g2");
console.log("二级分组内容");
console.groupEnd(); // 二级分组结束
console.groupEnd(); // 一级分组结束
4.2.9 console.trace(), console.clear()
console.trace
方法显示当前执行的代码在堆栈中的调用路径。
console.trace()
// 代码在堆栈中的路径
console.clear
用于清除当前控制台的所有输出,将光标回置到第一行。
4.3 控制台命令 API
浏览器控制台中,除了使用 console
对象,还可以使用一些控制台自带的命令行。
(1) $_
$_
属性返回上一个表达式的值。
2 + 2
// 4
$_
// 4
(2) 0-4
....
感觉没啥用
4.4 debugger
语句
debugger
语句用于除错,设置断点。如果有正在运行的除错工具,程序运行到 debugger
语句时会自动停下。如果没有除错工具,debugger
语句不会产生任何结果,JS 引擎会自动跳过这一句。
Chrome 浏览器中,当代码运行到debugger
语句时,就会暂停运行,自动打开脚本源码界面。
for(var i = 0; i < 5; i++){
console.log(i);
if (i === 2) {
debugger;
}
}