# 数据类型

# 值和类型

类型是值的内部特征,它定义了值的行为,以使其区别于其他值。

JavaScript 中的变量是没有类型的,只有值才有。变量可以随时持有任何类型的值。

# 内置类型

JavaScript 内置了 7 中类型:

null、undefined、boolean、number、string、symbol(ES6 新增)、object

其中 object 是引用类型,其他 6 种是基本类型(也有叫原始类型的)。

# Number 类型

与其他语言不同,ECMScript 没有为整数和浮点数值分别定义不同的数据类型,Number 类型可用于表示所有数值。Number 类型使用 IEEE754 格式来表示整数和浮点数值(浮点数值在某些语言中也被称为双精度数值)。

ECMA-262 定义了不同的数值字面量格式。

  • 十进制整数
var intNum = 55; // 整数
  • 八进制 八进制字面值的第一位必须是零(0),然后是八进制数字序列(0 ~ 7)。如果字面值中的数值超出了范围,那么前导零将被忽略,后面的数值将被当作十进制数值解析。
    八进制字面量在严格模式下是无效的,会导致支持的 JavaScript 引擎抛出错误。
var octalNum1 = 070; // 八进制的 56
var octalNum2 = 079; // 无效的八进制数值——解析为 79
var octalNum3 = 08; // 无效的八进制数值——解析为 8
  • 十六进制 十六进制字面值的前两位必须是 0x,后跟任何十六进制数字(0 ~ 9 及 A ~ F)。其中,字母 A ~ F 可以大写,也可以小写。
//prettier-ignore
var hexNum1 = 0xA; // 十六进制的 10
var hexNum2 = 0x1f; // 十六进制的 31

在进行算术计算时,所有以八进制和十六进制表示的数值最终都将被转换成十进制数值。

# 浮点数

所谓浮点数值,就是该数值中必须包含一个小数点,并且小数点后面必须至少有一位数字。虽然小数点前面可以没有整数,但不推荐这种写法。

var floatNum1 = 1.1;
var floatNum2 = 0.1;
//prettier-ignore
var floatNum3 = .1; // 有效,但不推荐

保存浮点数值需要的内存空间是保存整数值的两倍,因此 ECMAScript 会不失时机地将浮点数值转换为整数值。

var floatNum1 = 1; // 小数点后面没有数字——解析为 1
var floatNum2 = 10.0; // 整数——解析为 10

# e 表示法(即科学计数法)

用 e 表示法表示的数值等于 e 前面的数值乘以 10 的指数次幂。
在默认情况下,ECMASctipt 会将那些小数点后面带有 6 个零以上的浮点数值转换为以 e 表示法表示的数值(例如,0.0000003 会被转换成 3e-7)。

var floatNum = 3.125e7; // 等于 31250000
var num = 3e-17; // 等于 0.00000000000000003

# 0.1+0.2 不等于 0.3

浮点数值的最高精度是 17 位小数,但在进行算术计算时其精确度远远不如整数。例如,0.1 加 0.2 的结果不是 0.3,而是 0.30000000000000004。这个小小的舍入误差会导致无法测试特定的浮点数值。
这是使用基于 IEEE754 数值的浮点计算的语言的通病。
在 js 中可以这样解决:

parseFloat((0.1 + 0.2).toFixed(10));

不过这也不是最好的方案,关于更多解决方案可以看这篇文章 (opens new window)

# 数值范围

由于内存的限制,ECMAScript 并不能保存世界上所有的数值。ECMAScript 能够表示的最小数值保存在 Number.MIN_VALUE 中——在大多数浏览器中,这个值是 5e-324;能够表示的最大数值保存在 Number.MAX_VALUE 中——在大多数浏览器中,这个值是 1.7976931348623157e+308。如果某次计算的结果得到了一个超出 JavaScript 数值范围的值,那么这个数值将被自动转换成特殊的 Infinity 值。具体来说,如果这个数值是负数,则会被转换成-Infinity(负无穷),如果这个数值是正数,则会被转换成 Infinity(正无穷)。

Infinity(或-Infinity)参与计算时始终返回 Infinity(或-Infinity

Infinity + 100; // Infinity
-Infinity - 100; // -Infinity

isFinite() 函数可以判断某个值是不是位于最小和最大的数值之间,即判断是不是不是无穷数

var result = Number.MAX_VALUE + Number.MAX_VALUE;
alert(isFinite(result)); //false

# 整数检测

// 方法一:ES6之前
function isInteger() {
  if (!Number.isInteger) {
    Number.isInteger = function(num) {
      return typeof num == 'number' && num % 1 == 0;
    };
  }
}
// 方法二:ES6
Number.isInteger(42); // true
Number.isInteger(42.0); // true
Number.isInteger(42.3); // false

# NaN

NaN,即非数值(Not a Number)是一个特殊的数值,这个数值用于表示一个本来要返回数值的操作数未返回数值的情况(这样就不会抛出错误了)。 NaN 有 2 个特点:

  • 任何涉及 NaN 的操作(例如 NaN/10)都会返回 NaN
  • NaN 与任何值都不相等,包括 NaN 本身

# isNaN()

isNaN()函数判断给定的参数是否“不是数值”。
isNaN()在接收到一个值之后,会尝试将这个值转换为数值。某些不是数值的值会直接转换为数值,例如字符串"10"或 Boolean 值。而任何不能被转换为数值的值都会导致这个函数返回 true。

alert(isNaN(NaN)); //true
alert(isNaN(10)); //false(10 是一个数值)
alert(isNaN('10')); //false(可以被转换成数值 10)
alert(isNaN('blue')); //true(不能转换成数值)
alert(isNaN(true)); //false(可以被转换成数值 1)
alert(isNaN()); //true(没有参数时返回true)

isNaN() 只能判断值不是一个 number,那么怎么判断值是 NaN 呢?

// ES6
Number.isNaN(2 / 'foo'); // true
Number.isNaN('foo'); // false
Number.isNaN(); // false
Number.isNaN(NaN); // true
// 非ES6,利用NaN不等于自身
function isNaN(n) {
  return n !== n;
}

# 没有重载

在其他语言(如 Java)中,可以为一个函数编写两个定义,只要这两个定义的签名(接受的参数的类型和数量)不同即可。
ECMAScirpt 函数没有签名,因为其参数是由包含零或多个值的数组来表示的。而没有函数签名,真正的重载是不可能做到的。
如果在 ECMAScript 中定义了两个名字相同的函数,则该名字只属于后定义的函数。

# 判断类型

# typeof 操作符

对于基本类型,除了 null 其他类型都能正确显示对应类型:

typeof null; // "object"
typeof undefined; // "undefined"
typeof true; // "boolean"
typeof 123; // "number"
typeof NaN; // "number"
typeof '456'; // "string"
typeof Symbol('foo'); // "symbol"
typeof 123n; // "bigint"

对于 nulltypeof 会返回 object
红宝书对此解释为:从逻辑角度来看,null 值表示一个空对象指针,而这也正是使用 typeof 操作符检测 null 值时会返回"object"的原因。

对于引用类型,除了函数返回 function 类型,其他都返回 object

typeof { a: 1, b: 2 }; // "object"
typeof [1, 2, 3]; // "object"
typeof new Set([1, 2, 3]); // "object"
typeof new WeakSet(); // "object"
typeof new Map(); // "object"
typeof new WeakMap(); // "object"
typeof new Date(); // "object"
typeof new RegExp(); // "object"
typeof function foo() {}; // "function"
// 基本包装类型
typeof new String('some text'); // "object"
typeof new Number('123'); // "object"
typeof new Boolean(false); // "object"
// 单体内置对象
typeof Math; // "object"
// typeof Global; // "object",全局对象在不同环境有不同的实现,比如在浏览器就是window

# 使用 toString() 检测对象类型

可以通过 toString() 来获取每个对象的类型。为了每个对象都能通过 Object.prototype.toString() 来检测,需要以 Function.prototype.call() 或者 Function.prototype.apply() 的形式来调用,传递要检查的对象作为第一个参数,称为 thisArg

var toString = Object.prototype.toString;

toString.call(123); // [object Number]
toString.call(NaN); // [object Number]
toString.call(123n); // [object Bigint]
toString.call('some text'); // [object String]
toString.call(true); // [object Boolean]
toString.call(Symbol('foo')); // [object Symbol]

//Since JavaScript 1.8.5
toString.call(undefined); // [object Undefined]
toString.call(null); // [object Null]
// 对象类型
toString.call({ a: 1, b: 2 }); // [object Object]
toString.call([1, 2, 3]); // [object Array]
toString.call(new Date()); // [object Date]
toString.call(new RegExp()); // [object RegExp]
toString.call(function foo() {}); // [object Function]
// 基本包装类型
toString.call(new String()); // [object String]
toString.call(new Number('123')); // [object Number]
toString.call(new Boolean(false)); // [object Boolean]
// 单体内置对象
toString.call(Math); // [object Math]

# instanceof 操作符

# 语法

instance instanceof constructor;

instanceof 运算符用来检测 constructor.prototype 是否存在于参数 instance 的原型链上。(即 Object.getPrototypeOf(instance) === constructor.prototype)

# example

function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}
const auto = new Car('Honda', 'Accord', 1998);

console.log(auto instanceof Car);
// expected output: true

console.log(auto instanceof Object);
// expected output: true

对于引用类型的值可以使用 instanceof 操作符来判断其是哪种引用类型的实例。

// 注意当{}在左侧的时候,会认为是一段块级代码,所以下面的表达式需要写成下面的2种方式
({ a: 1, b: 2 } instanceof Object);
// prettier-ignore
({ a: 1, b: 2 }) instanceof Object;
// [1, 2, 3, 4] instanceof Object; // true
var arr = ['a', 'b', 'c'];
console.log(Object.getOwnPropertyNames(arr).sort()); // ["0", "1", "2", "length"]
// 类数组对象
var obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.getOwnPropertyNames(obj).sort()); // ["0", "1", "2"]
//不可枚举属性
var my_obj = Object.create(
  {},
  {
    getFoo: {
      value: function() {
        returnthis.foo;
      },
      enumerable: false
    }
  }
);
my_obj.foo = 1;
console.log(Object.getOwnPropertyNames(my_obj).sort()); // ["foo", "getFoo"]