类型定义
原始值类型
对于七种类型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转为0null会返回0undefined会返回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 代码里使用这里描述的任何属性和方法。