类型定义
原始值类型
对于七种类型Undefined
,Null
,Boolean
,Number
,BigInt
,Symbol
,String
,它们的值在 ES 规范中被称为原始值(primitive value)
Null
null
和undefined
什么区别
- 从含义上解释的话,
null
是空对象指针,而undefined
是声明了但是没有初始化的变量值,undefined
会被 JS 自动处理,但是null
只会由人为赋值产生
Undefined
- Undefined 类型的值只有一个,一般可以用全局变量
undefined
来表示; - 需要注意的是
undefined
是一个全局变量而不是关键字,在函数作用域内是可以修改的;因此某些代码规范推荐使用void 0
来表示undefined
值(void x
的void
后面加任何表达式或值都是undefined
)
function foo() {
var undefined = 5;
console.log(undefined); //5
}
foo();
console.log(undefined); //undefined
Boolean
- 这里有一份类型转换表
数据类型 | true | false |
---|---|---|
String | 非空字符串 | 空字符串 |
Number | 非 0 和 NaN 数值 | 0 和 NaN |
Undefined | undefined | |
Null | null | |
Object | 所有对象 |
String
- JS 字符串是一组由 UTF-16 编码字符组成的字符集,而字符串的长度表示字符串内含有的 16 位值的个数
- String 类型的字符串有最大长度
2^53 - 1
,大约9PB
,但是这个长度并不是表面上显示的字符的个数;JS 采用的是 UTF-16 编码的 Unicode 字符集,最常用的 Unicode 字符都是 16 位编码的单个字符,对于不能用 16 位表示的,用两个 16 位表示,也就是有时候你看到的一个字符的长度其实是 2 个 16 位值组成的,长度也就是 2
var p = 'Π';
var e = Math.E;
console.log(p.length); //1
console.log(e.length); //2
- String 类型的字符串是永远不可变的,一旦字符串构造出来,无法用任何方式改变字符串的内容,平时方法的修改都是复制一份副本进行修改,然后再销毁原本的字符串
字符串类型的转换
- 利用
Number
,Boolean
,String
,Object
,Symbol
继承的toString()
方法,这里要特别说一下Number
类型转换成String
类型,toString
方法可以带基数,默认是将数字转换成 10 进制的字符串表示形式,但是也可以传入2
,8
等进制的基数
console.log((0x10).toString()); //16
- 使用任何类型值都能使用的
String()
构造函数,String
构造函数会以下面的规则进行转换- 如果该类型有
toString
方法就调用toString()
- 如果是
null
,则返回null
- 如果是
undefined
,则返回undefined
- 如果该类型有
字符串转 Number 类型
parseInt(STR, radix)
:将字符串以 radix 进制转换成 10 进制的数,不过首先会判断这个数字是不是该进制转换来的;在不传入第二个参数的情况下,在 ES5 以后parseInt
只支持 16 进制前缀0x
,遇到非数字字符就会停止转换,也不支持科学计数法;因此建议任何时候都要传第二个进制参数
Number('-Infinity')['1', '2', '3'].map(parseInt) => {
parseInt(1, 0); //按照parseInt处理radix是0的情况,将按照10进制转换成10进制,所以仍然输出1
parseInt(2, 1); //输出NaN
parseInt(3, 2); //因为3根本不是二进制的数字,除了0和1,其他都不属于二进制数字,所以直接输出NaN
}
//console
[1, NaN, NaN]
parseInt(''); //NaN
parseInt(undefined); //NaN
parseInt(null); //NaN
parseInt('123e-1'); //123
parseInt('0x11'); //17
parseInt('0b11'); //0
parseInt('0o11'); //0
parseInt('-Infinity'); //NaN
parseFloat(STR)
:直接把原字符串作为十进制来解析;值得说的是parseFloat
本身是个全局函数,不属于任何对象,但是仍然有Number.parseInt()
方法,这个和直接使用parseFloat
是一样的
parseFloat(''); //NaN
parseFloat(undefined); //NaN
parseFloat(null); //NaN
parseFloat('123e-1'); //12.3
parseFloat('0x11'); //0
parseFloat('0b11'); //0
parseFloat('0o11'); //0
parseFloat('-Infinity'); //-Infinity
parseInt
和parseFloat
都具有一样的解析规则- 能识别正负号
- 参数首位和末位的空白符会被忽略
- 如果参数字符串的第一个字符不能被解析成为数字会直接返回
NaN
;但是parseInt
不能识别Infinity
,parseFloat
可以解析Infinity
Number(value)
:使用Number
类型的构造函数转换字符串往往是更好的选择,但是转换规则那是相当的复杂Boolean
类型的true
转为1
,false
转为0
null
会返回0
undefined
会返回NaN
- 空字符串会返回
0
- 如果字符串是一个基本的数值形式,会统一按 10 进制转换,包括科学计数法,其他进制的数
- 如果字符串含有数字以外的字符,直接返回
NaN
Number('123'); // 123
Number('12.3'); // 12.3
Number('12.00'); // 12
Number('123e-1'); // 12.3 科学计数法
Number(''); // 0 空字符串
Number(null); // 0
Number('0x11'); // 17 16进制
Number('0b11'); // 3 2进制
Number('0o11'); // 9 8进制
Number('foo'); // NaN
Number('100a'); // NaN
Number('-Infinity'); //-Infinity
Number
NaN
,not a number,这个数值设计是为了 Number 类型的计算不会报错来的
- 任何不符合数学运算规则(例如负数开方)的结果都返回
NaN
,任何与NaN
的操作都会返回NaN
- 任何值都不于
NaN
相等,包括他自己 isNaN
对任何不能被转换成数值的值使用都会返回true
Infinity
,无穷
- 可以加正负号,表示
+∞
和-∞
,默认是正的 isFinite
可以用来确定一个数值是不是在最大数和最小数之间
Number.MAX_VALUE
,JS 里能表示的最大的数
- 是一个具体的数,只不过用了变量形式代替,这个值接近于
2^1024 = 1.79E+308
,超过了它就是Infinity
Number.MIN_VALUE
,JS 里最小的正值
- 是 JS 里最接近
0
的正值,而不是最小的负值,约为5e-324
,小于Number.MIN_VALUE
的值会被转换成0
BigInt
- ES2020 新增了
BigInt
类型,表示大于Number.MAX_SAFE_INTEGER
的整数,也就是大于2^53-1
的整数值,从而可以为更大的数值提供精度 - 在过去的 JS 里,超过了
Number.MAX_SAFE_INTEGER
的整数会无法保证数值的精度,有些数值在从十进制转到 64 位二进制的过程中会丢失精度,有了BigInt
类型就能操作更大数字的计算,在科学和金融业务方面应用应该更多
Symbol
ES6 引入的一个基本类型,目前仅用作对象属性的标识符,从而防止对象属性名的冲突。
尽管Symbol
是类型,但是不支持new Symbol()
语法,只能通过Symbol(xxx)
来创建;括号内通常传入字符串或者其他类型,其他类型会被转成字符串然后调用。
关于Symbol
为什么不能用new
,其实这涉及到 JS 原始值类型包装对象的概念,new
本身用于函数前面就会以构造函数的形式去调用一个函数,对于其它原始值类型String
,Number
,Boolean
这些,都可以使用new
调用其构造函数创建一个包装对象出来,就算不显示这么做,在 JS 解析标识符的时候也会去进行包装对象的操作,即原始值类型都会创建一个临时的包装对象,这样才能使用其原型上的方法。从 ES 规范的定义来看,Symbol
的构造函数直接定义的是不能使用new
操作符:
The Symbol constructor is not intended to be used with the new operator. —— The Symbol Constructor
关于这个问题,知乎上也有个相关讨论 —— symbol 为什么没有包装类型?,从紫云飞的解释来看,这是 ES6 的尝试,因为本质上使用new
去构造原始值类型的包装对象这种操作是多余的行为,确实多余,平时基本不会有人这么干去声明一个原始值类型的包装对象出来。所以从 ES6 开始,这种使用new
调用原始值类型构造函数的行为被隐式废弃,就从Symbol
开始,并且可见的是的BigInt
也是这样的,只是BigInt
目前还是 Stage 4 的状态,ES2021 规范定义也表明了BigInt
的构造函数无法用new
—— The BigInt Constructor
作为属性名时必须用[]
标识,并且只能用[]
访问;如果想在定义后访问到这个属性,必须将Symbol(xxx)
先传递给一个变量,然后通过[]
标识和访问
var sym = Symbol('foo');
var obj = { [sym]: 1 };
obj[sym]; // 1
每次调用Symbol()
都会返回一个独一无二的值,即使括号内参数相同
Symbol('foo') === Symbol('foo'); // false
对象值类型
对于Object
及其衍生的内置标准对象类型,都属于对象值类型,它们可以添加键值对属性
ES 规范类型
ES 规范类型是描述 JS 语言实现层面的元数据类型,也可以说是实现 JS 引擎需要关注的内容,例如闭包(Closure),作用域(Environment Record)等,无法在 JS 代码里使用这里描述的任何属性和方法。