一文了解JS的NaN与Symbol

前言

本文给大家详细讲讲,我们在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中有几种数据类型?你会怎么回答?

这个问题好像不像表面上那么简单。

一般人会回答: 有八种数据类型,分别是 NumberStringBooleannullUndefinedObjectSymbolBigInt

这个答案是正确的,但是我想说的是它还不够完美,特别是在大厂中,如果你能按照我以下的回答,我相信,你离JavaScript大神 也就不远了!

严格来说,根据ECMA-262的标准,有七种数据类型,分别是 NumericStringBooleannullUndefinedObjectSymbol

其中 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...infor...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

来源:稀土掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

相关推荐
雨汨9 分钟前
el-input限制输入数字,输入中文后数字校验失效
前端·javascript·vue.js
保持学习ing15 分钟前
帝可得- 人员管理
前端·vue.js·elementui
一嘴一个橘子15 分钟前
el-table 树形数据,子行数据可以异步加载
javascript·elementui
難釋懷16 分钟前
Vue全局事件总线
前端·javascript·vue.js
生产队队长16 分钟前
项目练习:element ui 的icon放在button的右侧
开发语言·javascript·ui
独立开阀者_FwtCoder30 分钟前
使用这个新的 ECMAScript 运算符告别 Try/Catch!
前端·javascript·github
云浪30 分钟前
让元素舞动!深度解密 CSS 旋转函数
前端·css
cdcdhj31 分钟前
vue中events选项与$on监听自定义事件他们的区别与不同,以及$emit与$on之间通信和mounted生命周期钩子函数有哪些作用和属性
前端·javascript·vue.js
Jinxiansen021143 分钟前
Vue 3 弹出式计算器组件(源码 + 教程)
前端·javascript·vue.js
东京老树根1 小时前
SAP学习笔记 - 开发22 - 前端Fiori开发 数据绑定(Jason),Data Types(数据类型)
前端·笔记·学习