数据类型及转换

为什么let声明的变量会出现暂时性死区问题

使用 var 声明变量时,由于声明会被提升,javascript 引擎会自动将多余的声明在作用域顶部合并为一个声明。因为 let 的作用域是块,所以不可能检查前面是否已经使用 let 声明过同名变量,同时也就不能在没有声明的情况下使用它

typeof

typeof是一个操作符而不是函数,所以不需要传参(但可以使用参数)

1
2
3
let msg='hello world'
typeof msg; //-> 'string'
typeof(mgs); //-> 'string'

即使变量未声明,也可以执行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
2
3
4
5
6
let num = 10;
console.log(num.toString()) //-> '10'
console.log(num.toString(2)) //-> '1010'
console.log(num.toString(8)) //-> '12'
console.log(num.toString(10)) //-> '10'
console.log(num.toString(16)) //-> 'a'
  • 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
2
3
4
5
6
7
8
let foo = Symbol.for('foo')
let otherFoo = Symbol.for('foo')
foo === otherFoo //-> true

// 即使使用相同的符号描述,在全局注册表中定义的符号跟使用 Symbol() 定义的符号也不等同
let one = Symbol('foo')
let two = Symbol.for('foo')
one === two //-> false

全局注册表中的符号必须使用字符串键来创建,作为参数传给 Symbol.for() 的任何值都会被转换为字符串

Symbol.keyFor()

用来查询 全局注册表。该方法接收符号,返回该全局符号对应的字符串键。如果查询的不是全局符号,则返回 undefined

1
2
3
4
5
6
7
8
9
10
// 创建全局符号
let aaa = Symbol.for([1,2])
Symbol.keyFor(aaa) //-> '1,2'

// 创建普通符号
let bbb = Symbol('bar')
Symbol.keyFor(bbb) //-> undefined

// 如果传的不是符号,则抛出 TypeError 错误
Symbol.keyFor(1123) //-> TypeError: 1123 is not a symbol

Object

Object 是派生其他对象的基类。Object 类型的所有属性和方法在派生的对象上同样存在。每个Object 实例都有如下属性和方法:

  • constructor: 用于创建当前对象的函数。
  • hasOwnProperty(属性名):用于判断当前对象实例(非原型)上是否存在给定的属性。要检查的属性名必须是字符串或符号
  • isPrototypeOf(object):用于判断当前对象是否存在于另一个对象的原型链上
  • propertyIsEnumerable(属性名):用于判断给定的属性是否可以使用 for-in 语句枚举。属性名必须是字符串
  • toLocalString():返回对象的字符串表示,该字符串反映对象所在的本地化执行环境
  • toString():返回对象的字符串表示
  • valueOf():返回对象对应的字符串、数值或布尔值表示。通常与 toString() 的返回值相同

操作符

一元操作符

只操作一个值的操作符叫一元操作符

递增/递减操作符
前缀版:变量的值会在语句被求值之前改变(在计算机科学中,这通常被称为具有副作用)

1
2
3
4
5
let age = 29
let anotherAge = --age + 2

console.log(age) // 28
console.log(anotherAge) // 30

前缀递增和递减在语句中的优先级是相等的,因此会从左到右一次求值

1
2
3
4
5
6
7
let num1 = 2
let num2 = 20
let num3 = --num1 + num2
let num4 = num1 + num2

console.log(num3) // 21
console.log(num4) // 21

后缀版:递增和递减在语句被求值后才发生

1
2
3
4
5
6
7
let num1 = 2
let num2 = 20
let num3 = num1-- + num2
let num4 = num1 + num2

console.log(num3) // 22
console.log(num4) // 21

递增和递减操作符可以作用于任何值,包括字符串、布尔值、浮点值,甚至对象。操作符遵循如下规则:

  • 对于字符串,如果是有效的数值形式,则转换为数值再应用改变。变量类型从字符串变成数值;如果不是有效的数值形式,则将变量的值设置为 NaN。变量类型从字符串变成数值
  • 对于布尔值,如果是 false,则转换为0再应用改变,变量类型从布尔值变成数值;如果是 true,则转换为1再应用改变,变量类型从布尔值变成数值
  • 对于浮点数,加1或减1
  • 对于对象,则调用 valueOf() 方法取得可以操作的值,对得到的值应用上述规则。如果是NaN,则调用 toString() 并再次应用其他规则。变量类型从对象变成数值

位操作符

JS中所有的数值都以 IEEE754 64位格式存储,操作时是先把值转换为32位数值,再进行操作,然后再把结果转换为64位。
有符号整数使用32位的前31位表示整数值,第32位表示数值的符号,如0表示正,1表示负,这一位称为符号位(sign bit),它的值决定了数值其余部分的格式。正值以真正的二进制格式存储,负值以一种称为二补数(或补码)的二进制编码存储。
一个数值的二补数通过如下3个步骤计算得到:

1
2
3
4
5
6
7
8
9
10
11
12
/*
* 1、确定绝对值的二进制表示(如:对于 -18,先确定18的二进制表示)
* 2、找到数值的一补数(或反码),换句话说,就是每个0都变成1,1变成0
* 3、给结果加1
*/
// 18 的二进制表示
0000 0000 0000 0000 0000 0000 0001 0010
// 计算一补数,即反转后的二进制值:
1111 1111 1111 1111 1111 1111 1110 1101
// 最后,给一补数加1
1111 1111 1111 1111 1111 1111 1110 1110
// 以上值就是-18的二进制表示

如果将位操作符应用到非数值,那么首先会使用 Number() 函数将该值转换为数值,然后再应用位操作。最终结果是数值

按位非

按位非操作符用波浪符 ~ 表示,它的作用是返回数值的一补数。即二补数(复数)不执行最后加1那一步,最终结果是对数值取反并减1,就像执行如下操作:

1
2
3
4
let num1 = 25
let num2 = -num1 - 1

console.log(num2) // -26

尽管两者的返回结果一样,但位操作的速度快得多。这是因为位操作是在数值的底层表示上完成的

按位与

按位与操作符用 & 表示,有两个操作数。本质上,按位与就是将两个数的每一个位对齐,然后基于真值表中的规则,对每一位执行相应的与操作
真值表

1
2
3
4
5
6
7
8
9
10
11
let result = 25 & 3

console.log(result) // 1

// 25 的二进制值为
0000 0000 0000 0000 0000 0000 0001 1001
// 3 的二进制值为
0000 0000 0000 0000 0000 0000 0000 0011
// 比较结果为
0000 0000 0000 0000 0000 0000 0000 0001
// 转换成10进制值为 1

按位或

用符号 | 表示。按位或操作至少一位是 1 时返回 1,两位都是 0 时返回 0

1
2
3
4
5
6
7
8
9
10
11
let result = 25 | 3

console.log(result) // 27

// 25 的二进制值为
0000 0000 0000 0000 0000 0000 0001 1001
// 3 的二进制值为
0000 0000 0000 0000 0000 0000 0000 0011
// 比较结果为
0000 0000 0000 0000 0000 0000 0001 1011
// 转换成10进制值为 27

按位异或

用符号 ^ 表示。按位异或只在一位上是 1 的时候返回 1,两位都是 1 或 0,返回 0

1
2
3
4
5
6
7
8
9
10
11
let result = 25 ^ 3

console.log(result) // 26

// 25 的二进制值为
0000 0000 0000 0000 0000 0000 0001 1001
// 3 的二进制值为
0000 0000 0000 0000 0000 0000 0000 0011
// 比较结果为
0000 0000 0000 0000 0000 0000 0001 1010
// 转换成10进制值为 26

左移

用符号 << 表示。会按照指定的位数将数值的所有位向左移动。高位左移溢出则舍弃该高位

1
2
3
4
5
6
let old = 2  // 等于二进制  10
let new1 = old << 5 // 等于二进制 1000000 即十进制 64

/*
* 在数字没有溢出的前提下,对于正数和负数,左移一位相当于乘以2的1次方,左移 n 位相当于乘以 2 的 n 次方
*/

注意:左移会保留它所操作数值的符号,比如 -2 左移 5 位,将得到 -64,而不是正 64

有符号右移

用符号 >> 表示,会将数值的所有32位都向右移,同时保留符号(正或负)
右移后空位会出现在左侧,且在符号位之后。ECMAScript会用符号位的值来填充这些空位,以得到完成的数值
有符号右移实例图片

无符号右移

用符号 >>> 表示,会将数值的所有 32 位都向右移。对于正数,无符号右移与有符号右移结果相同。
与有符号右移不同,无符号右移会给空位补0,而不管符号位是什么。

  • 对正数来说,跟有符号右移效果相同
  • 对负数来说,结果相差太多
1
2
let old = -64 // 二进制  1111 1111 1111 1111 1111 1111 1100 0000
let new1 = old >>> 5 // 二进制 0000 0111 1111 1111 1111 1111 1111 1110 -> 十进制 134217726

布尔操作符

逻辑非

用符号 ! 表示。首先将操作数转换为布尔值,然后再对其取反
同时使用两个 !! ,相当于调用了转型函数 Boolean()

逻辑与

用符号 && 表示。只有两个操作符都为 true 时才返回 true
如果有操作符不是布尔值,则逻辑与并不一定会返回布尔值,而是遵循如下规则:

  • 如果第一个操作数为true,则返回第二个操作数
  • 如果第一个操作数为false,则返回第一个操作数
    短路操作符/短路特性:如果第一个操作符决定了结果,那么永远不会对第二个操作数求值
    对逻辑与操作符来说,如果第一个操作符是 false , 那么无论第二个操作数是什么值,结果都不可能为 true
1
2
3
4
5
6
7
let font = true
let result = ( font && aaa ) // 报错。因为 aaa 未声明
console.log(reslut) // 不执行

let font = false
let result = ( font && aaa ) // 不报错
console.log(reslut) // false

逻辑或

用符号 || 表示。只要有一个为 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
2
3
4
5
console.log(Math.pow(3,2))  // 9
console.log(3 ** 2) // 9

console.log(Math.pow(16,0.5)) // 4
console.log(16 ** 0.5) // 4

指数操作符也支持指数赋值操作符 **=

1
2
3
let a = 3
a **= 2
console.log(a) 9

加性操作符

加法操作符

  • 两个数值相加

    • 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
2
3
4
let a = 5 + 5
console.log(a) // 10
let b = '5' + 5
console.log(b) // '55'

减法操作符

如下规则:

  • 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
2
3
4
let i = 0
do{
i+=2
} while(i < 10)

标签语句

用于给语句加标签,典型应用场景是嵌套循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 // 添加标签,不仅退出内部循环,也会退出外部循环
}
num++
}
}
console.log(num) // 55

let num = 0;
outermost: for ( let i = 0; i < 10; i++ ){
for ( let j = 0; j < 10; j++ ){
if( i ===5 && j ==5 ){
continue outermost // 添加标签,结束本轮循环,进行下一轮循环
}
num++
}
}
console.log(num) // 95 j 执行到5时,本轮循环结束,进行了下一轮,相当于少执行了5次,故为 95

break 和 continue

breack 用于立即退出循环
continue 用于立即退出此次循环,进行下一轮循环

with 语句

用于将代码作用于设置为特定的对象

1
2
3
4
5
6
7
8
9
let qs = location.search.substring(1)
let hostname = location.hostname
let url = location.href
// 上面的代码中都用到了 location 对象,可以使用 with 语句,如下
with(location){
let qs = search.substring(1)
let hostname = hostname
let url = href
}

严格模式不允许使用 with 语句,否则会抛出错误
由于 with 语句影响性能且难于调试,通常不推荐在产品代码中使用

switch 语句

switch 语句在比较每个条件的值时会使用全等操作符(===)

函数

严格模式下对函数的一些限制:

  • 函数不能以 eval 或 arguments 作为名称
  • 函数的参数不能叫 eval 或 arguments
  • 两个命名参数不能拥有同一名称