JavaScript 数据类型

· words · 7 min

Undefined、Null

通常我们用 undefined 来获取 undefined 值(注意其中的不同)。但 undefined 不是一个关键字,是一个变量(设计失误),而变量是 assignable 的,为了避免无意中被覆盖掉,因此用 void 接表达式来获取 undefined 值,例如 void 0 。null 与 undefined 不同,我们可以放心的用 null 关键字来获取 null 值。ES6 之后,禁止对 undefined 赋值操作了。

undefined 意为没有定义,null 表示“定义了但是为空”,一些细微的差别。

String

String 表示的是字符串的 unicode UTF16 编码,因此字符串的最大长度 2^53 -1 指的也是编码后的长度。我们常用的 charCodeAtcharAtlength 等都针对的是 UTF16 编码。JS 字符串把每个 UTF16 单元当做一个字符来处理。

字符集国际标准中,字符以 Unicode 表示,每个 Unicode 码点表示一个字符,理论上无限长度。UTF 是 Unicode 的编码方式,规定了 Unicode 在计算机中的表示方式,常见的有 UTF8 和 UTF16。Unicode 码点通常用 U+???? 表示,???? 是十六进制码点值,0-65536(U+0000 - U+FFFF)的码点被称为基本字符区域(BMP)。

Number

Number 类型表示数字,在计算机中有精度的限制。JS 中 Number 类型有 2^64-2^53+3 个值,基本符合 IEEE-754 2008 规定的双精度浮点数规则,但为了表达额外的场景,多了 NaNInfinity-Infinity 几个值,其中 NaN 占用了9007199254740990 值。根据浮点数定义,非整数不可用 === 比较,比较经典的 0.1 + 0.2 ≠= 0.3 是因为浮点数的精度丢失导致相差了一个微小的值。正确的比较方式是使用 JS 提供的最小精度值。

// 检查等式左右两边差的绝对值是否小于最小精度
Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON

Symbol

Symbol 是 ES6 中引入的新类型,它是一切非字符串的对象 key 的集合。在 ES6 规范中,整个对象系统用 Symbol 重塑。Symbol 具有字符串类型的描述,但即使描述相同,Symbol 也不相等。

Symbol('symbol') === Symbol('symbol') // false

Symbol.iterator 定义 for...of 在对象上的行为:

var o = new Object

o[Symbol.iterator] = function() {
	var v = 0
  return {
    next: function() {
      return { value: v++, done: v > 10 }
    }
  }        
};

for(var v of o) {
	// 首先获取 iterator 返回的 next 函数
	// next() -> { value: 0, done: false } -> get 0
	// next() -> { value: 1, done: false } -> get 1
	// next() -> { value: 9, done: false } -> get 9
	// next() -> { value: 10, done: true } -> end
  console.log(v); // 0 1 2 3 ... 9
}

Object

JS 中,对象是“属性的集合”,属性分为“数据属性”和“访问器属性”,都为 key-value 结构,属性可以为 key 或 Symbol。JS 中的类是运行时对象的一个私有属性,无法自定义类型(这里指的是数据类型)。JS 中的三个基本类型 NumberStringBoolean 的构造器是两用的,用 new 调用产生新的对象,直接调用时表示强制的类型转换。Symbol 较特殊,不能用 new 关键字调用,但仍是 symbol 对象的构造器。

// num1 是 Number 类型,num2 是对象类型
var num1 = new Number(123); // typeof num1 ===  "object"
var num2 = 123; // typeof num2 === "number"
num1 === num2; // false

既然类型都不同,为什么调用类型的方法时却表现一致呢?原因是 “.” 运算符提供了装箱操作,会根据基础类型创造一个临时对象,使得我们能够在基础类型上调用对应对象的方法。

装箱转换

JS 是弱类型的动态语言,类型转换非常频繁,大部分运算会先进行类型转换。每一种基本类型在对象中都有着基本的类。所谓装箱转换,就是把基本类型转换为对应的对象。装箱机制会频繁的产生临时对象,对一些性能要求高的场景下,应该避免进行装箱转换。使用 Object 函数可以显示的调用装箱能力,例如 typeof Object(123) === 'object' 。每一个装箱对象都有私有的 class 属性,JS 中没有任何方法可以去修改,因此使用 Object.prototype.toString 是准确识别对象对应基本类型的方法,比 instanceof 更加准确。

拆箱转换

在 JS 标准中,规定了 ToPrimitive 函数,它是对象类型到基本类型的转换(拆箱转换)。拆箱转换会尝试调用 valueOftoString 来获得拆箱后的基本类型。如果 valueOftoString 都不存在,或者没有返回基本类型,则会产生类型错误 TypeError。在 ES6 之后,还允许对象通过显式指定 @@toPrimitive Symbol 来覆盖原有的行为。

var o = {
  valueOf : () => { console.log("valueOf"); return {} },
  toString : () => { console.log("toString"); return {} }
}

o[Symbol.toPrimitive] = () => {console.log("toPrimitive"); return "hello"}

console.log(o + "")
// toPrimitive
// hello

补充

JS 中,typeof null === 'object'typeof Object === 'function' 是特例,属于 typeof 设计缺陷,要特殊对待。

From 极客时间