前言
本文给大家详细讲讲,我们在JS中很容易忽略的一些细节问题--NaN与Symbol,很多学者在学习JS时可能知道这两个概念,但是本文会带你彻底详细地聊透它们,让你在JS的理解上领先别人一大截。
NaN 是什么?
在JS中,NaN(Not-a-Number)是Number数据类型中的一个特殊值,表示"无效数值",我们可以用isNaN
的方法来判断该变量是否属于NaN。
js
typeof NaN //Number
const num1 = NaN;
const num2 = 123;
console.log(isNaN(num1)); //true
console.log(isNaN(num2)); //false
什么情况下会出现NaN?
那么,什么情况下会出现NaN呢?
前面我们说了,NaN是无效数值,那么就可以通过一些数学中的非法运算 得到NaN;同时也可以通过JS对数字的一些非法操作得到NaN.
非法计算得到NaN:
1. 分母为0
js
console.log(1/0) //NaN
2. 负数的平方根
js
console.log(Math.sqrt(-1)) //NaN
非法操作得到NaN:
1. 对NaN进行运算
js
console.log(NaN + 1) //NaN
2. Number转换非数字(Boolean除外)
js
console.log(Number("123")) //123
console.log(Number(undefined)) //NaN
console.log(Number("imok")) //NaN
console.log(Number(true)) //1
3. praseInt转换非数字(Boolean除外)
js
console.log(praseInt("123")) //123
console.log(praseInt(undefined)) //NaN
console.log(praseInt("imok")) //NaN
console.log(praseInt(true)) //1
console.log(praseInt("a123")) //a
console.log(praseInt("123a")) //123
这里注意praseInt的后两种情况:当123a时,也可以成功转换,因为praseInt是挨个检查字符的,直到检查到非数字的字符为止
NaN并不唯一
我们观察以下代码
js
console.log(NaN === NaN) //false
这是因为得到NaN的方式有很多种,每一种方式得到的NaN都不是唯一的,所以NaN之间也不会相等
实际上,这是遵循了IEEE 754浮点数标准的规定
"任何与 NaN 的比较操作(包括相等)都必须返回 false,即使比较对象是 NaN 本身"
为什么要有NaN?
有人说,NaN既不是具体的数字,但是又属于Number数据类型,这很矛盾。
其实,这都是由于早期JS开发的时间周期比较短,所以就带来了这些表面矛盾的设计,早期JS开发的目的是为了实现页面显示和交互,所以JS并不是一个为了计算而设计的语言,所以开发团队就直接采用了 IEEE 754 标准(浮点数国际标准)中的 NaN 概念,但未针对其类型逻辑进行额外封装。
但很可惜的是,直到后面的ES6出来之后,这个问题并没有得到改善,虽然如此,但我们作为JS的学习者还是需要了解这种历史背景,并且熟悉这种方式。

有几种数据类型
当字节的面试官问你,在JS中有几种数据类型?你会怎么回答?
这个问题好像不像表面上那么简单。
一般人会回答: 有八种数据类型,分别是 Number
、String
、Boolean
、null
、Undefined
、Object
、Symbol
、BigInt
这个答案是正确的,但是我想说的是它还不够完美,特别是在大厂中,如果你能按照我以下的回答,我相信,你离JavaScript大神 也就不远了!
严格来说,根据ECMA-262的标准,有七种数据类型,分别是 Numeric
、String
、Boolean
、null
、Undefined
、Object
、Symbol

其中 Primitive
表示简单数据类型 而Non-Primitive
就表示复杂数据类型,也就是对象。
你会发现其中,与我们常说的不同的是多了Numeric,少了Number和BigInt ,这是因为在ECMA-262 中,Numeric包含子类型Number
(IEEE 754 双精度浮点数) ,BigInt
(任意精度整数)
所以当你这样跟面试官表述时,我想你就是一个JS的顶级高手!
Symbol部分
上面提到了JS的数据类型,你会发现,除了我们常见的几种数据类型以外,其中还包含一个数据类型叫Symbol ,很多学者可能对其会不太熟悉,下面我们就彻底理清Symbol的定义以及特性,还有应用!
Symbol是什么
在 JavaScript 中,Symbol 是 ES6 引入的基本数据类型,表示唯一且不可变的值 。它通过 Symbol()
创建。
js
let s = Symbol(); //注意不能用 new Symbol()创建
typeof s n// "symbol"
上面代码中,变量s
就是一个独一无二的值。typeof
运算符的结果,表明变量s
是 Symbol 数据类型。 注意不能用new Symbol()创建Symbol,因为Symbol是简单数据类型,不是对象!
为Symbol添加描述
Symbol可以直接用Symbol()
创建,也可以在创建时添加描述,表示对该Symbol实例的描述,添加了描述后,我们可以用转变成字符串的方式区分描述,也可以直接用description属性得到描述。
js
let s1 = Symbol('foo');
let s2 = Symbol('bar');
s1 // Symbol(foo)
s2 // Symbol(bar)
s1.toString() // "Symbol(foo)"
s2.toString() // "Symbol(bar)"
s1.description //foo
s2.description //bar
但是要注意,Symbol()创建的是独一无二的值,即使加了相同描述,或者不加任何描述,两个Symbol实例仍然是不相同的
js
// 没有参数的情况
let s1 = Symbol();
let s2 = Symbol();
s1 === s2 // false
// 有参数的情况
let s1 = Symbol('foo');
let s2 = Symbol('foo');
s1 === s2 // false
Symbol的应用
由于Symbol的特殊性,Symbol在JS中有许许多多的应用。
作为属性名
Symbol可以用于对象的属性名,这样就保证不会出现同名的属性,在大型项目中,能防止一个键不小心被另一个键覆盖。
下面是Symbol作为对象属性名的几种写法:
js
let mySymbol = Symbol();
// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';
// 第二种写法
let a = {
[mySymbol]: 'Hello!'
};
// 第三种写法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });
// 以上写法都得到同样结果
a[mySymbol] // "Hello!"
定义一组常量
Symbol类型还可以用于定义一组不相等的常量,这就好像别的语言中的枚举类型。
js
const emnus = {
UP: Symbol("UP"),
DOWN: Symbol("DOWN"),
LEFT: Symbol("LEFT"),
}
js
const COLOR_RED = Symbol();
const COLOR_GREEN = Symbol();
function getComplement(color) {
switch (color) {
case COLOR_RED:
return COLOR_GREEN;
case COLOR_GREEN:
return COLOR_RED;
default:
throw new Error('Undefined color');
}
}
通过将Symbol设置为常量,可以保证其独一无二与不可改变的性质!设置了该Symbol后,其它任何值都不可能有相同的值了!
属性名的遍历
Symbol作为属性名时,遍历对象的时候,该属性不会出现在for...in
、for...of
循环中,也不会被Object.keys()
、Object.getOwnPropertyNames()
返回
而且它也不是私有属性,因为有一个Object.getOwnPropertySymbols
方法可以返回该对象所有的Symbol属性名。
js
const s1 = Symbol('s1');
const s2 = Symbol('s2');
const s3 = Symbol('s3');
const s4 = Symbol('s4');
const obj = {
[s1]: 's1',
[s2]: 's2',
[s3]: 's3',
[s4]: 's4',
age: 18,
sex: '男'
}
for(const e in obj){
console.log(e) // 只打印了age sex 没有打印出Symbol(ss1) Symbol(ss2) Symbol(ss3) Symbol(ss4)
}
Object.getOwnPropertyNames(obj) // ["age", "sex"]
Object.getOwnPropertySymbols(obj) // [Symbol(ss1), Symbol(ss2), Symbol(ss3), Symbol(ss4)]
所以我们就可以利用Symbol,为对象定义一些非私有的、但又希望只用于内部的方法。
js
const _internalMethod = Symbol('internalMethod');
const obj = {
publicMethod() {
console.log('This is a public method');
this[_internalMethod](); // 调用内部方法
},
[_internalMethod]() {
console.log('This is an internal method (hidden from most iterations)');
}
};
obj.publicMethod();
// 输出:
// This is a public method
// This is an internal method (hidden from most iterations)
Symbol.for()&Symbol.keyFor()
前文我们提到,Symbol是独一无二的,即使在创建Symbol()时使用相同参数,但我们仍然得到的是两个独立的Symbol,但是,有没有一种方法能够让我们重新使用一个已经创建了的Symbol值呢?
JS的Symbol.for()
方法帮我们实现了这一点。
js
let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');
s1 === s2 // true
你会发现,使用Symbol.for()
可以得到我们已经创建过的上一个Symbol值,将其返回给s2变量,这样s1和s2变量指向的就是同一个Symbol值。
Symbol.for()的使用
实际上,Symbol.for()必须传入一个参数,不传入参数会报错。传入参数时,首先会检查在全局中存在以该参数创建的Symbol.for(),若存在,则返回已存在的以该参数创建的Symbol;若不存在,则创建一个新的Symbol返回并且在全局中记录该Symbol。
Symbol()与Symbol.for()的区别
Symbol()和Symbol.for()都可以生成新的Symbol,它们两者的区别是:Symbol()可传参可不传参,不会检查之前是否存在Symbol,直接创建一个新的Symbol并且返回,Symbol.for()必须传参,会检查在全局中是否有以相同参数创建的Symbol,如果有则返回全局中的Symbol,如果没有则创建新的Symbol并注册到全局中。
所以当你调用Symbol("dog")
20次,会生成20个不同的Symbol,而当你调用Symbol.for("dog")
20次,只会产生一个Symbol,并且所有的变量都指向这个Symbol。
js
Symbol.for("bar") === Symbol.for("bar") // true
Symbol("bar") === Symbol("bar") // false
Symbol.keyFor()
Symbol.keyFor()是用来返回一个已经在全局中创建的Symbol的参数,如果全局中不存在,则返回undefined,通常Symbol.keyFor()和Symbol.for()配合使用。
js
let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"
let s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined
总结
以上就是本文介绍的关于NaN和Symbol的全部内容了,本文除了详细讲解NaN和Symbol之外,还帮大家提升了面试中关于数据类型的表达,通过精确地回答ECMA-262的规范之后,可以帮大家从面试中的90分 提升到了100分。 看完并理解全文之后!恭喜你的JS基础已经更上一层楼!
🌇结尾
本文部分内容参考阮一峰的:ECMAScript 6(ES6)标准入门教程 第三版
感谢你看到最后,最后再说两点~
①如果你持有不同的看法,欢迎你在文章下方进行留言、评论。
②如果对你有帮助,或者你认可的话,欢迎给个小点赞,支持一下~
我是3Katrina,一个热爱编程的大三学生
(文章内容仅供学习参考,如有侵权,非常抱歉,请立即联系作者删除。)
作者:3Katrina
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。