跳到主要内容

类型定义

原始值类型

对于七种类型UndefinedNullBooleanNumberBigIntSymbolString,它们的值在 ES 规范中被称为原始值(primitive value)

Null

nullundefined什么区别

  • 从含义上解释的话,null是空对象指针,而undefined是声明了但是没有初始化的变量值,undefined会被 JS 自动处理,但是null只会由人为赋值产生

Undefined

  • Undefined 类型的值只有一个,一般可以用全局变量undefined来表示;
  • 需要注意的是undefined是一个全局变量而不是关键字,在函数作用域内是可以修改的;因此某些代码规范推荐使用void 0来表示undefined值(void xvoid后面加任何表达式或值都是undefined
function foo() {
var undefined = 5;
console.log(undefined); //5
}
foo();
console.log(undefined); //undefined

Boolean

  • 这里有一份类型转换表
数据类型truefalse
String非空字符串空字符串
Number非 0 和 NaN 数值0 和 NaN
Undefinedundefined
Nullnull
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 类型的字符串是永远不可变的,一旦字符串构造出来,无法用任何方式改变字符串的内容,平时方法的修改都是复制一份副本进行修改,然后再销毁原本的字符串

字符串类型的转换

  • 利用NumberBooleanStringObjectSymbol继承的toString()方法,这里要特别说一下Number类型转换成String类型,toString方法可以带基数,默认是将数字转换成 10 进制的字符串表示形式,但是也可以传入28等进制的基数
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
  • parseIntparseFloat都具有一样的解析规则
    • 能识别正负号
    • 参数首位和末位的空白符会被忽略
    • 如果参数字符串的第一个字符不能被解析成为数字会直接返回 NaN;但是parseInt不能识别InfinityparseFloat可以解析Infinity
  • Number(value):使用Number类型的构造函数转换字符串往往是更好的选择,但是转换规则那是相当的复杂
    • Boolean类型的true转为1false转为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本身用于函数前面就会以构造函数的形式去调用一个函数,对于其它原始值类型StringNumberBoolean这些,都可以使用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 代码里使用这里描述的任何属性和方法。