第三章 语言基础
数据类型及转换
为什么let声明的变量会出现暂时性死区
问题
使用
var
声明变量时,由于声明会被提升,javascript 引擎会自动将多余的声明在作用域顶部合并为一个声明。因为let
的作用域是块,所以不可能检查前面是否已经使用let
声明过同名变量,同时也就不能在没有声明的情况下使用它
typeof
typeof
是一个操作符而不是函数,所以不需要传参(但可以使用参数)
1 | let msg='hello world' |
即使变量未声明,也可以执行typeof操作,且只能执行这个有用的操作,返回结果是
'undefined'
null
任何时候,只要变量要保存对象,而当时又没有那个对象可保存,就要用null来填充该变量。这样就可以保持null是空对象指针的语义,并进一步将其与undefined区分开来 —–高程4 57页
Number
Number类型使用了 IEEE754 格式表示整数和浮点数(在某些语言中也叫双精度值)
ECMAScript2015或ES6中的八进制值通过前缀0o来表示。严格模式下,如果使用前缀0来表示八进制的话,会被视为语法错误
存储浮点值使用的内存空间是存储整数值的两倍
在浏览器控制台,二进制以 0B 开头,八进制以 0 或者 0O 开头,十六进制以 0x 开头
数值转换(Number()
)
- 布尔值,true 转换为1,false 转换为0
- 数值,直接返回
- null,返回0
- undefined,返回NaN
- 字符串,应用以下规则:
- 如果字符串包含数值字符,包括数值字符前面带加、减号的情况,则转换为一个十进制值
- 如果字符串包含有效的浮点值格式如 “1.1”,则会转换为相应的浮点值
- 如果字符串包含有效的十六进制格式如 “0xf”,则会转换为与该十六进制值对应的十进制整数值
- 如果是空字符串(不包含字符),则返回 0
- 如果字符串包含除上述情况之外的其他字符,则返回 NaN
- 对象,调用 valueOf() 方法,并按照上述规则转换返回的值。如果转换结果不是数字 ,则调用toString() 方法,再按照转换字符串的规则转换
String
字符字面量
字面量 | 含义 | 英语单词 |
---|---|---|
\n | 换行 | newline |
\t | 制表 | tab |
\b | 退格 | backspace |
\r | 回车 | Enter(windows) return(Mac) |
\f | 换页 | |
\ | 反斜杠() | |
' | 单引号(‘),在字符串以单引号标示时使用,例如’He said,'hey.'‘ | |
" | 双引号(“),在字符串以双引号标示时使用,例如”He said,"hey."“ | |
` | 反引号(`),在字符串以反引号标示时使用,例如He said,\ hey.`` |
|
\xnn | 以十六进制编码nn表示的字符(其中n是十六进制数字0~F),例如\x41 等于“A” | |
\unnnn | 以十六进制编码nnnn表示的unicode字符(其中n是十六进制数字0~F),例如\u03a3 等于希腊字符“∑” |
字符字面量可以出现在字符串中的任意位置,且可以作为单个字符被解释
转换为字符串
- toString():该方法可见于数值、布尔值、对象和字符串值
- 字符串值使用该方法是只是简单的返回自身的一个副本
- null 和 undefined 值没有 toString() 方法,转换需要使用 String() 方法
- 多数情况下,toString() 不接受任何参数。但是在对数值调用这个方法时,toString() 可以接收一个底数参数,即以什么底数参数来输出数值的字符串值。默认情况下,toString() 返回数值的十进制字符串表示,通过传入参数,可以得到数值的二进制、八进制、十六进制或其他任何有效基数的字符串表示
1 | let num = 10; |
- String():函数遵循以下规则:
- 如果值有 toString() 方法,则调用该方法(不传参数)
- 如果值是 null,返回 ‘null’
- 如果值是 undefined,返回 ‘undefined’
扩展
String.raw:获取原始的模板字面量内容(如换行符或 Unicode 字符)
1
2 console.log(`\u00A9`) //-> ©
console.log(String.raw`\u00A9`) //-> \u00A9
Symbol
基本用法
Symbol(符号)是 ES6 新增的数据类型。符号是原始值
,且符号实例是唯一、不可变的。符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险
调用 Symbol() 函数时,也可以传入一个字符串参数作为对符号的描述,但是这个字符串参数与符号定义或标识完全无关,仅是方便调试代码
Symbol() 函数不能与 new 关键字一起作为构造函数使用。这样做是为了避免创建符号包装对象
全局符号注册表
Symbol.for()
对每个字符串键都执行幂等操作。第一次使用某个字符串调用时,他会检查全局运行时注册表,发现不存在对应的符号,就会生成一个新符号实例并添加到注册表中。后续使用相同字符串的调用同样会检查注册表,发现存在与该字符串对应的符号,就会返回该符号的实例
1 | let foo = Symbol.for('foo') |
全局注册表中的符号必须使用字符串键来创建,作为参数传给 Symbol.for() 的任何值都会被转换为字符串
Symbol.keyFor()
用来查询
全局注册表
。该方法接收符号,返回该全局符号对应的字符串键。如果查询的不是全局符号,则返回 undefined
1 | // 创建全局符号 |
Object
Object 是派生其他对象的基类。Object 类型的所有属性和方法在派生的对象上同样存在。每个Object 实例都有如下属性和方法:
- constructor: 用于创建当前对象的函数。
- hasOwnProperty(属性名):用于判断当前对象实例(非原型)上是否存在给定的属性。要检查的属性名必须是字符串或符号
- isPrototypeOf(object):用于判断当前对象是否存在于另一个对象的原型链上
- propertyIsEnumerable(属性名):用于判断给定的属性是否可以使用 for-in 语句枚举。属性名必须是字符串
- toLocalString():返回对象的字符串表示,该字符串反映对象所在的本地化执行环境
- toString():返回对象的字符串表示
- valueOf():返回对象对应的字符串、数值或布尔值表示。通常与 toString() 的返回值相同
操作符
一元操作符
只操作一个值的操作符叫
一元操作符
递增/递减操作符
前缀版:变量的值会在语句被求值之前改变(在计算机科学中,这通常被称为具有副作用)
1 | let age = 29 |
前缀递增和递减在语句中的优先级是相等的,因此会从左到右一次求值
1 | let num1 = 2 |
后缀版:递增和递减在语句被求值后才发生
1 | let num1 = 2 |
递增和递减操作符可以作用于任何值,包括字符串、布尔值、浮点值,甚至对象。操作符遵循如下规则:
- 对于字符串,如果是有效的数值形式,则转换为数值再应用改变。变量类型从字符串变成数值;如果不是有效的数值形式,则将变量的值设置为 NaN。变量类型从字符串变成数值
- 对于布尔值,如果是 false,则转换为0再应用改变,变量类型从布尔值变成数值;如果是 true,则转换为1再应用改变,变量类型从布尔值变成数值
- 对于浮点数,加1或减1
- 对于对象,则调用 valueOf() 方法取得可以操作的值,对得到的值应用上述规则。如果是NaN,则调用 toString() 并再次应用其他规则。变量类型从对象变成数值
位操作符
JS中所有的数值都以 IEEE754 64位格式存储,操作时是先把值转换为32位数值,再进行操作,然后再把结果转换为64位。有符号整数
使用32位的前31位表示整数值,第32位表示数值的符号,如0表示正,1表示负,这一位称为符号位(sign bit)
,它的值决定了数值其余部分的格式。正值以真正的二进制格式存储,负值以一种称为二补数(或补码)
的二进制编码存储。
一个数值的二补数通过如下3个步骤计算得到:
1 | /* |
如果将位操作符应用到非数值,那么首先会使用 Number() 函数将该值转换为数值,然后再应用位操作。最终结果是数值
按位非
按位非操作符用波浪符 ~ 表示,它的作用是返回数值的一补数。即二补数(复数)不执行最后加1那一步,最终结果是对数值取反并减1,就像执行如下操作:
1 | let num1 = 25 |
尽管两者的返回结果一样,但位操作的速度快得多。这是因为位操作是在数值的底层表示上完成的
按位与
按位与操作符用 & 表示,有两个操作数。本质上,按位与就是将两个数的每一个位对齐,然后基于真值表中的规则,对每一位执行相应的与操作
1 | let result = 25 & 3 |
按位或
用符号 | 表示。按位或操作至少一位是 1 时返回 1,两位都是 0 时返回 0
1 | let result = 25 | 3 |
按位异或
用符号 ^ 表示。按位异或只在一位上是 1 的时候返回 1,两位都是 1 或 0,返回 0
1 | let result = 25 ^ 3 |
左移
用符号 << 表示。会按照指定的位数将数值的所有位向左移动。高位左移溢出则舍弃该高位
1 | let old = 2 // 等于二进制 10 |
注意:左移会保留它所操作数值的符号,比如 -2 左移 5 位,将得到 -64,而不是正 64
有符号右移
用符号 >> 表示,会将数值的所有32位都向右移,同时保留符号(正或负)
右移后空位会出现在左侧,且在符号位之后。ECMAScript会用符号位的值来填充这些空位,以得到完成的数值
无符号右移
用符号 >>> 表示,会将数值的所有 32 位都向右移。对于正数,无符号右移与有符号右移结果相同。
与有符号右移不同,无符号右移会给空位补0,而不管符号位是什么。
- 对正数来说,跟有符号右移效果相同
- 对负数来说,结果相差太多
1 | let old = -64 // 二进制 1111 1111 1111 1111 1111 1111 1100 0000 |
布尔操作符
逻辑非
用符号 ! 表示。首先将操作数转换为布尔值,然后再对其取反
同时使用两个 !! ,相当于调用了转型函数 Boolean()
逻辑与
用符号 && 表示。只有两个操作符都为 true 时才返回 true
如果有操作符不是布尔值,则逻辑与并不一定会返回布尔值,而是遵循如下规则:
- 如果第一个操作数为true,则返回第二个操作数
- 如果第一个操作数为false,则返回第一个操作数
短路操作符/短路特性
:如果第一个操作符决定了结果,那么永远不会对第二个操作数求值
对逻辑与操作符来说,如果第一个操作符是 false , 那么无论第二个操作数是什么值,结果都不可能为 true
1 | let font = true |
逻辑或
用符号 || 表示。只要有一个为 true 就返回 true
如果有操作符不是布尔值,则逻辑或并不一定会返回布尔值,而是遵循如下规则:
- 如果第一个操作数为true,则返回第一个操作数
- 如果第一个操作数为false,则返回第二个操作数
逻辑或操作符也有短路特性:当第一个操作数求值为 true 时,第二个操作数就不会再被求值了
乘性操作符
乘法操作符
用符号 * 表示
- 如果操作数都是数值,则执行正常的乘法运算。正正得正,负负得正,正负得负。如果乘积超出最大数,则返回 Infinity 或 -Infinity
- 如果有不是数值的操作数,则先在后台用 Number() 将其转换为数值,然后再进行运算
- Infinity 乘以 0,返回 NaN
- Infinity 乘以非 0 的有限数值,则根据第二个操作数的符号返回 Infinity 或 -Infinity
- Infinity 乘以 Infinity,返回 Infinity
除法操作符
用符号 / 表示
- 如果操作数都是数值,则执行常规的除法运算。正数相除是正数,负数相除是正数,正负相除或者负正相除是负数。如果 ECMAScript不能表示商,则返回 Infinity 或 -Infinity
- Infinity 除以 Infinity,返回 NaN
- 0 除以 0,返回 NaN
- 非 0 的有限值除以 0 ,则根据第一个操作数的符号返回 Infinity 或 -Infinity
- 如果是 Infinity 除以任何数值,则根据第二个操作数的符号返回 Infinity 或 -Infinity
- 如果有不是数值的操作数,则先在后台用 Number() 函数将其转换为数值,然后再按上述规则进行运算
取模操作符
用符号 % 表示。用于获取余数
- 如果操作数是数值,则执行常规除法运算,返回余数
- 如果被除数是无限值,除数是有限值,则返回 NaN
- 如果被除数是有限值,除数是0,则返回 NaN
- 如果是 Infinity 除以 Infinity,返回 NaN
- 如果被除数是有限值,除数是无限值,则返回被除数
- 如果被除数是0,除数不是0,则返回0
- 如果有不是数值的操作数,则先在后台用 Number() 函数将其转换为数值,然后再按上述规则进行运算
指数操作符
ES7新增,用符号 ** 表示,与 Math.pow() 结果一样
1 | console.log(Math.pow(3,2)) // 9 |
指数操作符也支持指数赋值操作符 **=
1 | let a = 3 |
加性操作符
加法操作符
两个数值相加
- Infinity + Infinity = Infinity
- -Infinity + -Infinity = -Infinity
- Infinity + -Infinity = NaN
- +0 + +0 = +0
- -0 + +0 = +0
- -0 + -0 = -0
如果有一个是字符串,则将另一个操作数转换为字符串,然后进行拼接
如果有一个操作数是对象、数值或者布尔值,会调用 toString() 方法以获取字符串,然后执行上面的关于字符串的规则;undefined 和 null 会调用 String() 函数,分别获取 “undefined” 和 “null”
1 | let a = 5 + 5 |
减法操作符
如下规则:
- Infinity - Infinity = NaN
- -Infinity - -Infinity = NaN
- Infinity - -Infinity = Infinity
- -Infinity - Infinity = -Infinity
- +0 - +0 = +0
- +0 - -0 = -0
- -0 - -0 = +0
- 如果有任一操作数是 NaN,返回 NaN
- 如果任一操作符是字符串、布尔值、null 或 undefined,则先在后台使用 Number() 将其转换为数值,然后再根据前面的规则执行数学运算
- 如果有任一操作数是对象,则调用其 valueOf() 方法取得表示它的数值,如果该值是 NaN,则减法计算的结果是 NaN。如果对象没有 valueOf() 方法,则调用 toString() 方法,然后再将得到的字符串转换为数值
关系操作符
执行比较两个值的操作,包括 < 、> 、<= 、>= ,都返回布尔值
- 如果操作数都是数值,执行数值比较
- 如果有任一操作数是数值,则将另一个操作数转换为数值,执行比较
- 如果有任一操作数是对象,则调用 valueOf() 方法,取得结果后再根据前面的规则进行比较;如果没有 valueOf() 操作符,则调用 toString() 方法,取得结果后再根据前面的规则进行比较
- 如果有任一操作数是布尔值,则将其转换为数值再进行比较
- 两个字符串比较,会比较该字符串对应的编码的数值
- NaN 和 任何操作符比较时都是 false
相等操作符
等于和不等于
比较之前会执行类型转换。等于用符号 == 表示;不等于用符号 != 表示
- 如果任一操作数是布尔值,则将其转换为数值再进行比较
- 如果一个操作数是字符串,另一个是数值,则将字符串转换为数值,再进行比较
- 如果一个操作数是对象,另一个不是,则调用 valueOf() 方法取得其原始值,再根据前面的规则进行比较
- null 和 undefined 相等
- null 和 undefined 不能转换为其他类型的值再进行比较
NaN 不等于任何值 - 如果两个操作数都是对象,则比较他们是不是同一个对象。如果都指向同一个对象,则相等
全等和不全等
比较时不进行类型转换,只有两个操作数在不转换的前提下相等才返回 true。全等用符号 === 表示,不全等用 !== 表示
逗号操作符
在赋值时使用逗号操作符分隔值,最终会返回表达式中最后一个值
1 | let num = ( 4,2,1,2,0 ) // num 的值为 0 |
语句
do-while 语句
循环体中的代码执行后才会退出条件进行求值。换句话说,循环体内的代码至少执行一次
1 | let i = 0 |
标签语句
用于给语句加标签,典型应用场景是嵌套循环
1 | let num = 0; |
break 和 continue
breack 用于立即退出循环
continue 用于立即退出此次循环,进行下一轮循环
with 语句
用于将代码作用于设置为特定的对象
1 | let qs = location.search.substring(1) |
严格模式不允许使用 with 语句,否则会抛出错误
由于 with 语句影响性能且难于调试,通常不推荐在产品代码中使用
switch 语句
switch 语句在比较每个条件的值时会使用全等操作符(===)
函数
严格模式下对函数的一些限制:
- 函数不能以 eval 或 arguments 作为名称
- 函数的参数不能叫 eval 或 arguments
- 两个命名参数不能拥有同一名称