第3章:语言基础 [一起学前端]
作者是位前端切图仔,本系列旨在从头开始学习前端,巩固前端基础。同时分享学习笔记供广大同学讨论,如有编写错误或者描述问题,还请大家多多指点。
3.1 语法
3.4.5 Number 类型
ES中最有意思的数据类型或许就是Number了。Number类型使用IEEE 754 格式表示整数和浮点值(在某些语言中也叫双精度值)。不同的数值类型相应地也有不同的数值字面量格式。
070 => 八进制 56
079 => 无效的八进制值,当成79处理
oxA => 十六进制10
注意:由于JS保存数值的方式,实际中可能存在正零(+0)和负零(-0)。正零和负零在所有情况下都被认为是等同的。
1、浮点值
因为存储浮点值使用的内存空间是存储整数值的两倍,所以ES总是想方设法把值转换为整数。在小数点后面没有数字的情况下,数值就会变成整数。类似地,如果数值本身就是整数,只是小数点后面跟着0(如1.0),那它也会被转为整数
对于非常大或非常小的数值,浮点值可以用科学计数法来表示。科学计数法用于表示一个应该乘以10的给定次幂的数值。ES中科学记数法的格式要求是一个数值(整数或浮点数)后跟一个大写或小写的字母e,再加上一个要乘的10的多少次幂。
let floatNum = 3.12e7
科学记数法也可以用于表示非常小的数值,例如 0.000 000 000 03 => 3e-11。默认情况下,ES会将小数点后至少包含6个零的浮点值转换为科学记数法
浮点值的精确度最高可达17位小数,但在算术计算中远不如整数精确。例如,0.1加0.2得到的不是0.3,而是0.300000000000000004。由于这种微小的舍入错误,导致很难测试特定的浮点值。比如下面的例子:
if(a+b == 0.3){
//
}
注意:之所以存在这种舍入错误,是因为使用了IEEE754数值,这种错误并非ES所独有。其他使用相同格式的语言也有这个问题。
解决方法:可以使用三方的库 big.js 或者 math.js 。当然要求不高也可以直接舍去,有些涉及金额计算的建议还是服务端进行处理
2、值的范围
由于内存的限制,ES并不支持表示这个世界上的所有数值。ES可以表示的最小数值保存在Number.MIN_VALUE 中;可以表示最大的数值保存在Number.MAX_VALUE中。如果某个计算得到的数值结果超出了JavaScript可以表示的范围,那么这个数值会被自动转换为一个特殊的infinity(无穷)值。任何无法表示的负数以-Infinity(负无穷大)表示,任何无法表示的正数以 Infinity(正无穷大)表示
注意:使用 Number.NEGATIVE_INFINITY 和 Number.POSITIVE_INFINITY 也可以获取正、负 Infinity。没错,这两个属性包含的值分别就是 -Infinity 和 Infinity
3、NaN
有一个特殊的数值叫做 NaN,意思是“不是数值”,用于表示本来要返回数值的操作失败了(而不是抛出错误)。比如
consolelog(0/0) // NaN
console.log(5/0) // Infinity 这个返回的是无穷大
NaN有几个独特的属性。首先,任何涉及NaN的操作始终返回NaN,在连续多步计算时这可能是个问题。其次,NaN不等于包括NaN在内的任何值,所以判断是否是NaN需要使用特别的方法 isNaN
console.log(isNaN(NaN)) // true
console.log(isNaN(10)) // false
console.log(isNaN("10")) // false
console.log(isNaN("blue")) // true // 不可以转换为数值,这个功能还是有点意思的,可以判断类型转换的时候是否能转为数值
console.log(isNaN(true)) // false
注意:虽然不常见,但isNAN() 可以用于测试对象。此时,首先会调用对象的valueOf()方法,然后再确定返回的值是否可以转换为数值。如果不能,再调用toString()方法,并测试其返回值。这通常是ES内置函数和操作符的工作方式。
4、数值转换
有3个函数可以将非数值转换为数值:Number、parseInt、parseFloat。Number是转型函数,可以用于任何数据类型。后两个函数主要用于将字符串转换为数值。对于同样的参数,这3个函数执行的操作也不同。
Number函数基于如下规则执行转换
- 布尔值,true转换为1,false转换为0
- 数值,直接返回
- null,返回0
- undefined,返回 NaN
- 字符串,应用以下规则
- 如果字符串包含数值字符,包含数值字符前面带加、减号的情况,则转换为一个十进制数值。因此,Number("1") 返回 1,Number("123")返回123,Number("011")返回11(忽略前面的零)
- 如果字符串包含有效的浮点值格式如“1.1”,则会转换为相应的浮点值(同样,忽略前面的零)
- 如果字符串包含有效的十六进制格式如“oxf”,则会转换为与该十六进制值对应的十进制整数值
- 如果是空字符串(不包含字符),则返回0
- 如果字符串包含除上述情况之外的其他字符,则返回NaN
- 对象,调用valueOf()方法,并按照上述规则转换返回的值。如果转换结果是NaN,则调用toString()方法,再按照转换字符串的规则转换
下面是一些具体的例子
Number('hello word') // NaN
Number('') // 0
Number('00001111') // 1111
Number(true) // 1
考虑到用Number函数转换字符串时相对复杂且有点反常规,通常在需要取得整数时可以优先使用parseInt函数。parseInt函数更专注于字符串是否包含数值模式。字符串最前面的空格会被忽略,从第一个非空格字符开始转换。如果第一个字符不是数值字符、加号或减号,parseInt立即返回NaN。这意味着空字符串也会返回NaN(这一点和Number不一样,它返回0)。如果第一个字符是数值字符、加号或减号,则继续依次检测每个字符,直到字符串末尾,或碰到非数值字符。比如,“1234blue”会被转换为1234,因为“blue”会被完全忽略。类似地,“22.5”会被转换为22,因为小数点不是有效的整数字符。
假设字符串中的第一个字符是数值字符,parseInt函数也能识别不同的整数格式(十进制、八进制、十六进制)。换句话说,如果字符串以“0x”开头,就会被解释为十六进制整数。如果字符串以“0”开头,且紧跟着数值字符,在非严格模式下会被某些实现解释为八进制整数。
parseInt("1234blue") // 1234
parseInt("") // NaN
parseInt("oxA") // 10 解释为十六进制整数
parseInt("22.5") // 22
parseInt("70") // 10 解释为十进制整数
parseInt("oxf") // 15,解释为十六进制整数
当然如果进制不同的话,建议传入第二个参数,表明进制位数,防止错误解析
parseInt("AF",16)// 175 这个时候字符串的前缀 ox是不需要传递的
parseFloat 函数的工作方式跟parseInt函数类似,都是从位置0开始检测每个字符。同样,它也是解析到字符串末尾或者解析到一个无效的浮点数值字符为止。这意味着第一次出现的小数点是有效的,但第二次出现的小数点就无效了,此时字符串的剩余字符就会被忽略。因此,“22.34.5”将转换成 22.34。
parseFloat 函数的另一个不同之处在于,它始终忽略字符串开头的零。这个函数能识别前面讨论的所有浮点格式,以及十进制格式。十六进制数值始终会返回0。因为parseFloat只解析十进制值,因此不能指定底数。最后,如果字符串表示整数(没有小数点或者小数点后面只有一个零),则parseFloat返回整数。
let num1 = parseFloat('1234blue') // 1234 按整数解析
let num2 = parseFloat(0xA) // 0
3.4.5 Number 类型
String(字符串)数据类型表示零或多个16位Unicode字符序列。字符串可以使用双引号("")、单引号('')、或反引号(``)标识
跟某些语言中使用不同的引号会改变对字符串的解释方式不同,ES语法中表示字符串的引号没有区别
1、字符字面量
字符串数据类型包含一些字符字面量,用于表示非打印字符或有其他用途的字符,如下表所示:
| 字面量 | 含义 |
|---|---|
| \n | 换行 |
| \t | 制表 |
| \r | 回车 |
| \f | 换页 |
\\ | 反斜杠(\) |
\' | 单引号('),在字符串以单引号标识时使用,例如 'hello \'nihao\' ' |
\" | 含义同上 |
| \` | 含义同上 |
| \xnn | 以十六进制编码nn表示的字符(其中n是十六进制数字0-F),例如 \x41 等于 "A" |
| \unnn | 以十六进制编码nnnn表示的Unicode字符(其中n是十六进制数字0-F),例如\u03a3等于希腊字符“∑” |
这些字符字面量可以出现在字符串中的任意位置,且可以作为单个字符被解释
注意:如果字符串中包含双字节字符,那么length属性返回的值可能不是准确的字符数。
2、字符串的特点
ES中的字符串是不可变的,意思是一旦创建,它们的值就不能变了。要修改某个变量中的字符串值,必须先销毁原始的字符串,然后将包含新值的另一个字符串保存到该变量,如下所示:
let lang = "Java"
lang = lang + "Script"
这里,变量lang一开始包含字符串“JAVA”。紧接着,lang被重新定义为包含“Java”和“Script”的组合,也就是“JavaScript”。整个过程首先会分配一个足够容纳10个字符的空间,然后填充上“Java”和“Script”。最后销毁原始的字符串”Java“和字符串”Script“,因为这两个字符串都没有用了。所有处理都是在后台发生的,而这也是一些早期的浏览器在拼接字符串时非常慢的原因。这些浏览器在后台的版本中都有针对性地解决了这个问题。
3、转换为字符串
有两种方式把一个值转换为字符串。首先是使用几乎所有值都有的toString()方法。这个方法唯一的用途就是返回当前值的字符串等价物。
toString方法可用于数值、布尔值、对象和字符串值,null和undefined是没有toString方法的。
默认情况下,toString返回数值的十进制字符串表示,而通过传递参数,可以得到数值的二进制、八进制、十六进制,或者其他任何有效基数的字符串
let num = 10
num.toString(2) // 1010
num.toString(16) // a
如果你不确定一个值是不是null 或者 undefined 可以使用 String转型函数,它始终会返回表示响应类型值的字符串
- 如果值有toString方法,则调用该方法并返回结果
- 如果值是null 返回 ”null“
- 如果值是undefined,返回”undefined“
注意:用加号操作符给一个值加上一个空字符串”“也可以将其转换为字符串
4、模版字符串
ES6新增特性,模版字面量保留换行字符,可以跨行定义字符串。由于模版字面量会保持反引号内部的空格,因此在使用时要格外注意。格式正确的模版字符串看起来可能会缩进不当
5、字符串插值
模版字面量最常用的一个特性是支持字符串插值,也就是可以在一个连续定义中插入一个或多个值。技术上讲,模版字面量不是字符串,而是一种特殊的JavaScript句法表达式,只不过求值后得到的是字符串。模版字面量在定义时立即求值并转换为字符串实例,任何插入的变量也会从它们最接近的作用域中取值。
字符串插值通过在 ${} 中使用一个JavaScript表达式实现
所有插入的值都会使用 toString 强制转型为字符串,而且任何JavaScript表达式都可以用于插值。嵌套的模版字符串无须转义。
6、模版字面量标签函数
模板字面量也支持定义标签函数(tag function),而通过标签函数可以自定义插值行为。标签函数会接收被插值记号分割后的模版和对每个表达式求值的结果
上面的话比较拗口,大致的意思,就是模版字面量可以当成函数的值传入,第一个是字符串集合,后面的是模板变量字符串
let a = 6
let b = 3
function simpleTag(strings,aValExpression,bValExpression,sumExpression){
console.log(strings)
console.log(aValExpression)
console.log(bValExpression)
console.log(sumExpression)
}
let untaggedResult = `${a} + ${b} = ${a+b}`
let taggedResult = simpleTag`${a} + ${b} = ${a+b}`
VM170:4 ['', ' + ', ' = ', '', raw: Array(4)]
VM170:5 6
VM170:6 3
VM170:7 9
// 上述代码可以复制到控制台执行看看,结果就很明显了
7、原始字符串
使用模板字面量也可以直接获取原始的模版字面量内容,而不是被转换后的字符表示。为此,可以使用默认的String.raw标签函数
console.log('\u00A9') // ©️
console.log(String.raw`\u00A9`) // \u00A9 会直接返回字符串
书上说的更加详细,感兴趣的话看看书本吧
3.4.7 Symbol 类型
提示:本章特性较多,Symbol 主要用在各个特性类型中,后面单独拿出来说一说
3.4.8 Object类型
ES中的对象其实就是一组数据和功能的集合。对象通过new操作符后跟对象类型的名称来创建。开发者可以通过创建Object类型的实例来创建自己的对象,然后再给对象添加属性和方法
Object的实例本身并不是很有用,但理解与它相关的概念非常重要。类似Java中的Java.lang.Object,ES中的Object也是派生其他对象的基类。Object类型的所有属性和方法在派生的对象上同样存在。
每个Object实例都有如下属性和方法
- constructor:用于创建当前对象的函数
- hasOwnProperty:用于判断当前对象实例上是否存在给定的属性。要检查的属性名必须是字符串或符号
- isPrototypeOf:用于判断当前对象是否为两一个对象的原型
- propertyIsEnumerable:用于判断给定的属性是否可以使用 for-in语句枚举。与hasOwnProperty一样,属性名必须是字符串
- toLocaleString:返回独享的字符串表示,该字符串反应对象所在的本地化执行环境
- toString:返回对象的字符串表示
- valueOf:返回对象对应的字符串、数值或布尔值表示。通常与toString的返回值相同
因为在ES中Object是所有对象的基类,所以任何对象都有这些属性和方法。
注意:严格来说,ECMA-262中对象的行为不一定适合JavaScript中的其他对象。比如浏览器环境中的BOM和DOM对象,都是由宿主环境定义和提供的宿主对象。而宿主对象不受ESMA-262约束,所以它们可能会也可能不会继承Object。
3.5 操作符
ECMA-262 描述了一组可用于操作数据的操作符,包括数学操作符、位操作符、关系操作符和相等操作符等。ESMAScript中的操作符是独特的,因为它们可用于各种值,包括字符串、数值、布尔值,甚至还有对象。在应用给对象时,操作符通常会调用valueOf()和 或 toString()方法来取得可以计算的值。
3.5.1 一元操作符
只操作一个值的操作符叫一元操作符。一元操作符是ECMAScript中最简单的操作符。
1、递增、递减操作符
递增和递减操作符直接照搬自C语言,但有两个版本:前缀版和后缀版。顾名思义,前缀版就是位于要操作的变量前头,后缀版就是位于要操作的变量后头。前缀递增操作符会给数值加1,把两个加号(++)放到变量前头即可。
递增和递减操作符遵循如下规则。
- 对于字符串,如果是有效的数值形式,则转换为数值再应用改变。变量类型从字符串变成数值。
- 对于字符串,如果不是有效的数值形式,则将变量的值设置为NaN。变量类型从字符串变成数值
- 对于布尔值,如果是false,则转换为1再应用改变。变量类型从布尔值变成数值。
- 对于浮点值,加1或减1
- 如果是对象,则调用其valueOf()方法取得可以操作的值。对得到的值应用上述规则。如果是NaN,则调用toString()并再次应用其他规则。变量类型从对象变成数值。
下面的例子演示了这些规则:
let s1 = "2"
let s2 = "z"
let b = false;
let f = 1.1;
let o = {
valueOf(){
return -1
}
}
s1++ // 值变成数值3
s2++ // 值变成NaN
b++ // 值变成数值1
f-- // 值变成0.100000000000009(因为浮点数不精确)
o-- // 值变成 -2
2、一元加和减
一元加和减操作符对于大多数开发者来说并不陌生,它们在ECMAScript中跟在高中数学中的用途一样。一元加由一个加号(+)标识,放在变量前头,对数值没有任何影响。
如果将一元加应用到非数值,则会执行与使用Number()转型函数一样的类型转换:布尔值false和true转换为0和1,字符串根据特殊规则进行解析,对象会调用它们的valueOf()和 或 toString() 方法以得到可以转换的值。
一元减由一个减号(-)表示,放在变量前头,主要用于把数值变成负值,如把1转换成-1。
对数值使用一元减会将其变成相应的负值。在应用到非数值时,一元减会遵循与一元加同样的规则,先对它们进行转换,然后再取负值
3.5.2 位操作符
接下来要介绍的操作符用于数值的底层操作,也就是操作内存中表示数据的比特位。ES中的所有数值都以IEEE 754 64位格式存储,但位操作并不直接应用到64位表示,而是先把值转换为32位整数,再进行位操作,之后再把结果转换为64位。对开发者而言,就好像只有32位整数一样,因为64位整数存储格式是不可见的。既然知道了这些,就只需要考虑32位整数即可。
有符号整数使用32位的前31位表示整数值。第32位表示数值的符号,如0表示正,1表示负。这一位称为符号位,它的值决定了数值其余部分的格式。正值以真正的二进制格式存储,即31位中的每一位都代表2的幂。第一位表示 2^0,第二位表示2^1,依此类推。如果一个位是空的,则以0填充,相当于忽略不计。比如,数值18的二进制格式为 10010 (精简)。
负值以一种称为二补数的二进制编码存储。一个数值的二补数通过如下3个步骤计算得到:
-
确定绝对值的二进制表示(如,对于-18,先确定18的二进制表示)
-
找到数值的一补数(或反码),换句话说,就是每个0都变成1,每个1都变成0
-
给结果加 1
1、按位非
按位非操作符用波浪符(~)表示,它的作用是返回数值的一补数。按位非是ES中为数不多的几个二进制数学操作符之一。
let num1 = 25
let num2 = ~num1
console.log(num2) // -26
这里,按位非操作符作用到了数值25,得到的结果是-26。由此可以看出,按位非得最终效果是对数值取反并减1,就像执行如下操作的结果一样
let num = 25
let num2 = -num -1
实际上,尽管两者返回的结果一样,但位操作的速度快得多。这是因为位操作是在数值的底层表示上完成的。
2、按位与
按位与操作符用和号(&)表示,有两个操作数。本质上,按位与就是将两个数的每一个位对齐,然后基于真值表中的规则,对每一位执行相应的与操作。
3、按位或
按位或操作符用管道符(|)表示,同样有两个操作数,按位或遵循如下真值表
| 第一个数值的位 | 第二个数值的位 | 结果 |
|---|---|---|
| 1 | 1 | 1 |
| 1 | 0 | 1 |
| 0 | 1 | 1 |
| 0 | 0 | 0 |
4、按位异或
按位异或用脱字符(^)表示,同样有两个操作数。下面是按位异或的真值表
| 第一个数值的位 | 第二个数值的位 | 结果 |
|---|---|---|
| 1 | 1 | 0 |
| 1 | 0 | 1 |
| 0 | 1 | 1 |
| 0 | 0 | 0 |
5、左移
左移操作符用两个小于号(<<)表示,会按照指定的位数将数值的所有位向左移动。比如,如果数值2向左移5位,就会得到64
6、有符号右移
有符号右移由两个大于号(>>)表示,会将数值的所有32位都向右移,同时保留符号。同样移位后就会出现空位。不过,右移后空位会出现在左侧,且在符号位之后。ES会用符号位的值来填充这些空位,以得到完整的数值。
7、无符号右移
无符号右移用3个大于号表示(>>>),会将数值的所有32位都向右移。对于正数,无符号右移与有符号右移结果相同。
对于负数,有时候差异会非常大。与有符号右移不同,无符号右移会给空位补0,而不管符号位是什么。
因为负数是其绝对值的二补数,所以右移之后结果变得非常之大。
let oldValue = -64
let newValue = oldValue >>> 5 // 等于十进制 134217726
3.5.3 布尔操作符
1、逻辑非(!)
- 如果操作数是对象,则返回 false
- 如果操作数是空字符串,则返回 true
- 如果操作数是非空字符串,则返回 false
- 如果操作数是数值0,则返回 true
- 如果操作数是非0数值(包括Infinity),则返回 false
- 如果操作数是null,则返回 true
- 如果操作数是NaN,则返回 true
- 如果操作数是undefined,则返回 true
2、逻辑与(&&)
逻辑与操作符可用于任何类型的操作数,不限于布尔值。如果操作数不是布尔值,则逻辑与并不一定会返回布尔值,而是遵循如下规则。
- 如果第一个操作数是对象,则返回第二个操作数
- 如果第二个操作数是对象,则只有第一个操作数求值为true才会返回该对象
- 如果两个操作数都是对象,则返回第二个操作数
- 如果有一个操作数是null,则返回null
- 如果有一个操作数是NaN,则返回NaN
- 如果有一个操作数是undefined,则返回undefined
3、逻辑或(||)
与逻辑与类似,如果有一个操作数不是布尔值,那么逻辑或操作符也不一定返回布尔值。它遵循如下规则。
- 如果第一个操作数是对象,则返回第一个操作数
- 如果第一个操作数求值为false,则返回第二个操作数
- 如果两个操作数都是对象,则返回第一个操作数
- 如果两个操作数都是null,则返回null
- 如果两个操作数都是NaN,则返回NaN
- 如果两个操作数都是undefined,则返回undefined
3.5.4 乘性操作符
ES定义了3个乘性操作符:乘法、除法和取模。这些操作符跟它们在Java、C语言中对应的操作符作用一样,但在处理非数值时,他们也会包含一些自动的类型转换。如果乘性操作符有不是数值的操作数,则该操作数会在后台被使用Number()转型函数转换为数值。这意味着空字符串会被当成0,而布尔值true 会被当成1
1、乘法操作符
乘法操作符由一个星号(*)表示,可以用于计算两个数值的乘积。其语法类型于C语言。
不过,乘法操作符在处理特殊值时也有一些特殊行为
- 如果操作数都是数值,则执行常规的乘法运算,即两个正值相乘是正值,两个负值相乘也是正值,正负符号不同的值相乘得到负值。如果ES不能表示乘积,则返回Infinity 或 -Infinity
- 如果有任一操作数是NaN,则返回NaN
- 如果Infinity 乘以0 ,则返回 NaN
- 如果Infinity乘以非0的有限数值,则根据第二个操作数的符号返回 Infinity 或 - Infinity
- 如果Infinity乘以 Infinity,则返回 Infinity
- 如果有不是数值的操作数,则先在后台用Number将其转换为数值,然后再应用上述规则
2、除法操作符
- 如果操作数都是数值,则执行常规的除法运算,即两个正值相除是正值,两个负值相除也是正值,符号不同的值相除得到负值。如果ES不能表示商,则返回 Infinity 或 - Infinity
- 如果有任一操作数是NaN,则返回NaN
- 如果是0除以0,则返回NaN
- 如果是非0的有限制除以0,则根据第一个操作数的符号返回 Infinity 或 -Infinity
- 如果是Infinity除以任何数值,则根据第二个操作数的符号返回 Infinity 或 -Infinity
- 如果有不是数值的操作数,则先在后台用Number函数将其转换为数值,然后再应用上述规则
3、取模操作符
取模(余数)操作符由一个百分比符号(%)表示,比如:
let result = 26 % 5 // 等于 1
- 如果操作数是数值,则执行常规除法运算,返回余数
- 如果被除数是无限值,除数是有限值,则返回NaN
- 如果被除数是邮箱至,除数是0,则返回NaN
- 如果是Infinity 除以 Infinity,则返回NaN
- 如果被除数是有限值,除数是无限值,则返回被除数
- 如果被除数是0,除数不是0,则返回0
- 如果有不是数值的操作数,则先在后台用Number函数将其转换为数值,然后再应用上述规则
3.5.5 指数操作符
ES7新增了指数操作符,Math.pow现在有了自己的操作符 **,结果是一样的
不仅如此,指数操作符也有自己的指数赋值操作符**=,该操作符执行指数运算和结果的赋值操作
3.5.6 加性操作符
3.5.7 关系操作符
在使用关系操作符比较两个字符串时,会发生一个有趣的现象。很多人认为小于意味着“字母顺序靠前”,而大于意味着“字母顺序靠后”,实际上不是这么回事。对字符串而言,关系操作符会比较字符串中对应字符的编码,而这些编码是数值。比较完之后,会返回布尔值。问题的关键在于,大写字母的编码都小于小写字母的编码,因此以下这种情况就会发生:
let result = "Brick" < "alphabet" // true
另外在比较字符串“23”和“3”时返回 true。因为两个操作数都是字符串,所以会逐个比较它们的字符编码(字符“2”的编码是 50 ,而字符“3”的编码是 51)。不过,如果有一个操作数是数值,那么比较的结果就对了
"23" < "3" // true
"23" < 3 // false
只要是数值和字符串比较,字符串就会先被转换成数值,然后进行数值比较。对于数值字符串而言,这样能保证结果正确。
但是当字符串不能转换为数值,那么只能转换为NaN。这里有一个规则,即任何关系操作符在涉及比较NaN时都返回false。
在大多数比较的场景中,如果一个值不小于另一个值,那就一定大于或等于它。但是在比较NaN时候,无论是小于还是大于等于,比较的结果都会返回false
3.5.8 相等操作符
判断两个变量是否相等是编程中最重要的操作之一。在比较字符串、数值和布尔值是否相等时,过程都很直观。但是在比较两个对象是否相等时,情形就比较复杂了。ES中的相等和不相等操作,原本在比较之前会执行类型转换,但很快就有人质疑这种转换是否应该发生。最终ES提供了两组操作符,第一组是等于 和不等于,他们在比较之前执行转换。第二组是全等和不全等,他们在比较之前不执行转换。
1、等于和不等于
ES中的等于操作符用两个等于号(==)表示,如果操作数相等,则会返回true。不等于操作符用叹号和等于号(!=)表示,如果两个操作数不相等,则会返回true。这两个操作符都会先进行类型转换(通常称为强制类型转换)再确定操作数是否相等。
在转换操作数的类型时,相等和不相等操作符遵循如下规则。
- 如果任一操作数是布尔值,则将其转换为数值再比较是否相等。false转换为0,true转换为1
- 如果一个操作数是字符串,另一个操作数是数值,则尝试将字符串转换为数值,再比较是否相等
- 如果一个操作数是对象,另一个操作数不是,则调用对象的valueOf方法取得其原始值,再根据前面的规则进行比较
在进行比较时,这两个操作符会遵循如下规则
- Null 和 undefined 相等
- null 和 undefined 不能转换为其他类型的值再进行比较
- 如果有任一操作数是NaN,则相等操作符返回 false,不相等操作符返回 true。记住:即使两个操作数都是NaN,相等操作符也返回false,因为按照规则,NaN不等于NaN
- 如果两个操作数都是对象,则比较它们是不是同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回true。否则,两则不相等。
| 表达式 | 结果 |
|---|---|
| null == undefined | true |
| false == 0 | True |
| true == 1 | true |
| true == 2 | False |
| Undefined == 0 | False |
| null == 0 | False |
| "5" == 5 | true |
2、全等和不全等
全等和不全等操作符与相等和不相等操作符类似,只不过他们在比较相等时不转换操作符。全等操作符由3个等于号(===)表示,只有两个操作数在不转换的前提下相等才返回true。
不全等操作符用一个叹号和两个等于号(!==)表示,只有两个操作数在不转换的前提下不相等才返回true。
注意:由于相等和不相等操作符存在类型转换问题,因此推荐使用全等和不全等操作符。这样有助于在代码中保持数据类型的完整性。
3.5.9 条件操作符
条件操作符是ES中用途最为广泛的操作符之一,语法跟Java中一样。
variable = boolean_expression ? true_value : false_value
3.5.10 赋值操作符
简单的赋值用等于号(=)表示,将右手边的值赋给左手边的变量
- 乘后赋值(*=)
- 除后赋值(/=)
- 取模后赋值(%=)
- 加后赋值(+=)
- 减后赋值(-=)
- 左移后赋值(<<=)
- 右移后赋值(>>=)
- 无符号右移后赋值(>>>=)
这些操作符仅仅是简单语法,使用它们不会提升性能。
3.5.11 逗号操作符
逗号操作符可以用来在一条语句中执行多个操作,如下所示:
let num=1,num2=2,num3=3;
在一条语句中同时声明多个变量是逗号操作符最常用的场景。不过,也可以使用逗号操作符来辅助赋值。在赋值时使用逗号操作符分隔值,最终会返回表达式中最后一个值:
let num = (5,1,4,8,0) // num 的值为0
在这个例子中,num将被赋值为0,因为0是表达式中最后一项。逗号操作符的这种使用场景并不多见,但这种行为的确存在。
3.6 语句
ES描述了一些语句(也称为流控语句),而ES中的大部分语法都体现在语句中。语句通常使用一或多个关键字完成既定的任务。语句可以简单,也可以复杂。简单的如高速函数退出,复杂的如列出一堆药重复执行的指令。
3.6.1 if语句
3.6.2 do-while 语句
3.6.3 while 语句
3.6.4 for 语句
3.6.5 for-in 语句
For-in 语句是一种严格的迭代语句,用于枚举对象中的非符号键属性,语法如下:
for (property in expression) statement
ES 中对象的属性是无序的,因此for-in语句不能保证返回对象属性的顺序。换句话说,所有可枚举的属性都会返回一次,但返回的顺序可能会因浏览器而异。如果for-in循环要迭代的变量是null或undefined,则不执行循环体。
3.6.6 for-of 语句
for-of 语句是一种严格的迭代语句,用于便利可迭代对象的元素
for(const el of [2,4,5,6]){
console.log(el)
}
for-of 循环会按照可迭代对象的next()方法产生值的顺序迭代元素。
注意:ES2018对for-of 语句进行了扩展,增加了for-await-of 循环,以支持生成期约(promise)的异步可迭代对象。
3.6.7 标签语句
标签语句用于给语句加标签,语法如下:
label:statement
3.6.8 break 和 continue 语句
break和continue语句为执行循环代码提供了更严格的控制手段。其中,break语句用于立即退出循环,强制执行循环后的下一条语句。而continue语句也用于立即退出循环,但会再次从循环顶部开始执行。
let num = 0;
outermost:
for(let i =0;i<10;i++){
for(let j=0;j<10;j++){
if(i===5 && j===5){
break outermost;// 添加标签不仅让break退出内部循环,也会退出外部循环
}
num++;
}
}
3.6.9 with语句
with语句的用途是将代码作用域设置为特定的对象,其语法是:
with (expression) statement;
使用with语句的主要场景是针对一个对象反复操作,这时候将代码作用域设置为该对象能提供便利,如下面的例子所示:
let qs = locaiton.search.substring(1);
let hostName = location.hostname;
let url = location.href
// 上面代码中的每一行都用到了location对象。如果使用witch语句,就可以少写一些代码
with(location){
let qs = search.substring(1)
let hostName = hostname
let url = href;
}
这里,with语句用于连接location对象。这意味着在这个语句内部,每个变量首先会被认为是一个局部变量。如果没有找到该局部变量,则会搜索location对象,看它是否有一个同名的属性。如果有,则该变量会被求值为location对象的属性。
严格模式不允许使用with语句,否则会抛出错误
警告:由于with语句影响性能且难于调试其中的代码,通常不推荐在产品代码中使用with语句。
3.6.10 switch 语句
Switch语句是与if语句紧密相关的一种流控制语句,从其他语言借鉴而来。ES中的Switch语句跟C语言中Switch语句的语法非常相似
为了避免不必要的条件判断,最好给每个条件后面都加上break语句。如果确实需要连续匹配几个条件,那么推荐写个注释表明是故意忽略了break。
虽然switch语句是从其他语言借鉴过来的,但是ES为它赋予了一些独有的特性。首先,switch语句可以用于所有数据类型(在很多语言中,它只能用于数值),因此可以使用字符串甚至对象,其次,条件的值不需要是常量,也可以是变量或表达式。
注意:switch语句在比较每个条件的值时会使用全等操作符,因此不会强制转换数据类型(比如,字符串“10”不等于数值10)
3.7 函数
这章简单,就不说了