JavaScript中的变量是没有类型的,只有值才有。
JavaScript有七种不同类型的值:
- 字符串(String)
- 数值(Number)
- 布尔值(Boolean)
- 未定义(Undefined)
- 空值(Null)
- 对象(Object)
- 符号(Symbol)
字符串(String)被用来表示一串字符,可以使用双引号("
)、单引号('
)、或者反引号(`
)表示。
下面对字符串的表示都是合法的:
let name = "Jack";
name = 'Jerry';
name = `Jessee`;
数值(Number)被用来表示一个数值,这个数值可以是整数、浮点数、或者是一个无穷大的值。
let number = 100; // 100是一个正整数
number = -100; // -100是一个负整数
number = 100.123; // 100.123是一个正浮点数
number = -100.123; // -100.123是一个负浮点数
number = Infinity; // Infinity是一个正无穷大的数值
number = -Infinity; // -Infinity是一个负无穷大的数值
number = NaN; // NaN是一个无效的数值
我们可能对Infinity
比较陌生:任何大到无法表示的正数以Infinity(正无穷大)表示,任何小到无法表示的负数以-Infinity(负无穷大)表示。
参考下面这个例子:

我们先是用typeof
确定了Infinity
是数字,然后确定了Infinity
大于任何数字,而-Infinity
小于任何数字(如果觉得举例用的数字不够大或不够小,你也可以自己换数字测试,结果会是一样的)。
用一个正数除以0
可以得到一个Infinity
(正无穷大),用一个负数除以0
可以得到-Infinity
(负无穷大)。

当然,我们也可以直接把Infinity
或-Infinity
赋给某个变量。
比如我们可以利用Infinity
的特点创建一个永远不会结束的死循环(可能会卡死浏览器,请谨慎测试)。
for (let i = 1; i < Infinity; i++) {
console.log(i);
}
也可以用isFinite()
函数来判断一个值是否有限大,如果返回true
就代表这个值是有限的,返回false
则代表这个值是无限大。

还有这个NaN
数值,也不太好理解。
NaN
数值表示本来要返回数值的操作失败了(不是抛出错误),通常是由于计算不成功而返回的无效数值,比如用0
除0
,或者用数字和字符串进行运算,都会产生无效数值。

还有一个isNaN()
函数可以判断一个值是不是不是数值,当这个值不是数值时返回true
,是数值时返回false
。

如上图所示,把任何类型的数字传给isNaN()
,都会返回false
,如果不是数字才会返回true
。
我们经常用isNaN()函数来判断一个值是不是数字,但这样判断并不是很靠谱。
因为当我们传入空字符串、传入null
、传入一个布尔值(true
或者false
)等类型值的时候,它也会返回false
,但这些都不是数字。

这些值明明不是数字,按道理应该返回true
才对,为什么会返回false
呢?
那是因为如果传给isNaN()
函数的值不是数值,该函数就会尝试先把它转换为数值,如果能够转换成功就会返回false
,否则返回true
。
拿上面的例子来说:空字符串会被转换成0
、true
会被转换成1
、false
会被转换为0
、null
会被转换成0
、[1]
会被转换成1
,它们都被成功转换成了数字,所以isNaN()
函数的返回结果都是false
。
布尔值(Boolean)用来表示“真”或“假”,有两个字面值true
和false
。
布尔值常用于判断语句。
例如:
if ( a > b ) {
console.log(a);
}
这个例子中的a > b
是一个条件表达式,如果a
确实大于b
,则条件为“真”,表达式会返回true
;否则条件为“假”,表达式会返回false
。
只有在条件为“真”时,才会执行{}
中的代码块,否则什么也不做。
在这个例子中:a > b
中的>
(大于)号是关系操作符,用于比较两个值。关系操作符除了>
(大于)以外,还有<
(小于)、==
(等于)、!=(
不等于)、===
(全等)、!==
(不全等)、<=
(小于等于)、>=
(大于等于),这几个操作符都返回布尔值。
除此以外,返回布尔值的还有很多,比如我们上面提过的isNaN()
和isFinite()
,以及布尔操作符!
(非)的返回值。
布尔操作符中的逻辑非操作符!
(非)始终返回布尔值,它首先将操作数转换为布尔值,然后再对其取反。

未定义的值(Undefined)类型只有一个值,就是特殊值undefined
。
当使用var
或let
声明了变量但没有初始化时,就相当于给变量赋于了undefined
值。

也就是说:undefined
是任何未经初始化的变量的初始值,我们永远不必显式地将变量值设置为undefined
。
空值(Null)类型同样只有一个值,即特殊值null
。
逻辑上来说,null
值表示一个空对象指针,这也是给typeof
传一个null
会返回"object"
的原因。
在定义将来要保存对象值的变量时,建议使用null
来初始化,不要使用其他值。

如前所述,我们永远不必显示地将变量名设置为undefined
,但null
不是这样的。
任何时候,只要变量要保存对象,而当时又没有那个对象可保存,就要用null
来填充该变量,这样就可以保持null
是空对象指针的语义,并进一步与undefined
区分开来。
对象(Object)是一组数据和功能的集合。
拿下面这个对象举例:
let obj = {
numberOne: 1,
numberTwo: 2,
sum: function() {
return this.numberOne + this.numberTwo;
}
}
这个对象的名称是obj
,它一共有三条属性,第一条属性的属性名是"numberOne"
,属性值是1
;第二条属性的属性名是"numberTwo"
,属性值是2
;第三条属性的属性名是"sum"
,属性值是一个函数。
我们可以根据需要,通过属性名取用属性中的数据或者执行属性中的函数。

JS中的数组和函数也是对象,它们都继承自Object
,甚至连字符串和数字也都能转换为对象,毫不夸张的说,JS处处都是对象。
符号(Symbol)是ES6新增的数据类型,符号是原始值,且符号是唯一,不可变的。
符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。
符号需要使用Symbol()
函数初始化,因为符号本身是原始类型,所以typeof
操作符对符号返回"symbol"
。

调用Symbol()
函数时,也可以传入一个字符串参数作为对符号的描述,这个字符串参数与符号定义或标识完全无关,仅用于对这个符号的用途作出对用户友好的字符串描述。

符号的输出结果如下:

Symbol()
函数返回唯一的符号值,即便以相同的字符串调用两次Symbol()
也会产生两个完全不同的符号值。

因为不允许重复,所以符号没有字面量语法,这也是它们发挥作用的关键。
按照规范,只要通过Symbol()
创建符号并将其用作对象的新属性,就可以保证它不会覆盖已有的对象属性,无论是符号属性还是字符串属性。
在ES6引入可迭代对象时,选择任何特定的字符串作为这个迭代器方法的名字都有可能与已有属性冲突,从而破坏已有代码,为此,符号应运而生(Symbol.iterator
是一个符号,它的内部保存了一个独一无二的值,用作方法名,让对象变得可迭代)。
直接使用Symbol()
创建符号有一个缺点,就是保存符号的变量必须声明在使用符号的代码能够访问的作用域内,这大大限制了它的灵活性。
为了解决这一问题,我们可以通过全局符号注册来创建这些符号值。
全局符号注册的语法是Symbol.for()
,接受一个字符串作参数(省略参数时默认将"undefined"
字符串作为参数)。
Symbol.for()
和Symbol()
的区别是:Symbol()
同样的指令执行多次会创建多个不同的值,即使传入的字符串参数相同(即传入Symbol()
的参数只用作描述值而不用作区分值);而Symbol.for()
创建的值全都存储在一个全局空间里,每次执行都会先查看全局空间内是否有与字符串参数相同的符号已经存在,如果有的话就返回它,没有的话就创建一个并返回(即传入Symbol.for()
的参数除了用作描述值,也用作区分值,因此我们需要使描述唯一)。
这个区别可以从下图中看出:

Symbol.for()
的这个特点意味着:只要创建时使用的字符串参数和全局符号注册表中的某个符号的字符串参数匹配,就可以在代码的任何地方通过Symbol.for()从注册表中获取这个符号。
最后,如果把符号用作对象的属性,那么它会以一种特殊的方式存储,使得这个属性不出现在对这个属性的一般枚举中,但是可以使用Object.getOwnPropertySymbols()
方法取得对象的符号属性:
