JS类型

389 阅读7分钟

这里有一份简洁的前端知识体系等待你查收,看看吧,会有惊喜哦~如果觉得不错,恳求star哈~


类型

运行时类型是代码实际执行过程中我们用到的类型。

所有的类型数据都会属于7个类型之一。

从变量、参数、返回值到表达式中间结果,任何 JS 代码运行过程中产生的数据,都具有运行时类型。

这7种数据类型,分别是:

Undefined
Null
Boolean
String
Number
Symbol
Object

这里将重新学习一下这些类型。


Undefined、Null

Undefined 类型表示未定义,它的类型只有一个值,就是 undefined。任何变量在赋值前是 Undefined类型、值为 undefined。

由于 undefined 不是一个关键字,这是 JS 语言公认的设计失误之一,所以,我们为了避免无意中被篡改,建议使用 void 0 来获取undefined值。

Null 类型也只有一个值,就是 null,它的语义表示空值,与 undefined 不同,null 是 JS 关键字,所以在任何代码中,你都可以放心用 null 关键字来获取 null 值。


Boolean

Boolean 类型有两个值, true 和 false,它用于表示逻辑意义上的真和假,同样有关键字 true 和 false 来表示两个值。


String

String 用于表示文本数据。String 有最大长度是 2^53 - 1,值得注意的是,这个最大长度,取决于字符串的编码长度。

String 的意义并非“字符串”,而是字符串的 UTF16 编码,我们字符串的操作 charAt、charCodeAt、length 等方法针对的都是 UTF16 编码。

关于 Unicode 及 UTF16 ,这里有专门的介绍。

JavaScript 中的字符串是永远无法变更的,一旦字符串构造出来,无法用任何方式改变字符串的内容,所以字符串具有值类型的特征。


Number

Number类型表示我们通常意义上的“数字”。

JS 中的 Number 类型有 18437736874454810627(即2^64-2^53+3) 个值。

JS 中的 Number 类型基本符合 IEEE 754-2008 规定的双精度浮点数规则,但是JavaScript为了表达几个额外的语言场景(比如不让除以0出错,而引入了无穷大的概念),规定了几个例外情况:

  1. NaN,占用了 9007199254740990,这原本是符合IEEE规则的数字;
  2. Infinity,无穷大;
  3. -Infinity,负无穷大。

另外,值得注意的是,JavaScript中有 +0 和 -0,在加法类运算中它们没有区别,但是除法的场合则需要特别留意区分。区分 +0 和 -0 的方式,正是检测 1/x 是 Infinity 还是 -Infinity。

根据双精度浮点数的定义,Number类型中有效的整数范围是-0x1fffffffffffff至0x1fffffffffffff,所以Number无法精确表示此范围外的整数。

同样根据浮点数的定义,非整数的Number类型无法用 ==(===也不行) 来比较,一段著名的代码,为什么在JavaScript中,0.1+0.2不能=0.3,这是因为0.1转换成二进制数是 0.0001100110011...。

所以,非整数的Number类型的比较,应该使用 JS 提供的最小精度值:

console.log( Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON);  // TRUE

检查等式左右两边差的绝对值是否小于最小精度,才是正确的比较浮点数的方法。这段代码结果就是 true 了。


Symbol

Symbol 是 ES6 中引入的新类型,它是一切非字符串的对象key的集合,在ES6规范中,整个对象系统被用Symbol 重塑。

Symbol 可以具有字符串类型的描述,但是即使描述相同,Symbol也不相等。

我们创建 Symbol 的方式是使用全局的 Symbol 函数。例如:var mySymbol = Symbol("my symbol");

一些标准中提到的 Symbol,可以在全局的 Symbol 函数的属性中找到。例如,我们可以使用 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)
console.log(v); // 0 1 2 3 ... 9

代码中我们定义了iterator之后,用for(var v of o)就可以调用这个函数,然后我们可以根据函数的行为,产生一个for…of的行为。

这些标准中被称为“众所周知”的 Symbol,也构成了语言的一类接口形式。它们允许编写与语言结合更紧密的 API。


Object

Object 是 JavaScript 中最复杂的类型,也是 JavaScript 的核心机制之一。Object表示对象的意思,它是一切有形和无形物体的总称。

在 JS 中,对象的定义是“属性的集合”。属性分为数据属性和访问器属性,二者都是key-value结构,key可以是字符串或者 Symbol类型。

提到对象,我们必须要提到一个概念:类。

因为 C++ 和 Java 的成功,在这两门语言中,每个类都是一个类型,二者几乎等同,以至于很多人常常会把 JS 的“类”与类型混淆。

事实上,JavaScript 中的“类”仅仅是运行时对象的一个私有属性,而 JS 中是无法自定义类型的。

JS 中的几个基本类型,都在对象类型中有一个“亲戚”。它们是:

Number
String
Boolean
Symbol

所以,我们必须认识到 3 与 new Number(3) 是完全不同的值,它们一个是 Number 类型, 一个是对象类型。

Number、String和Boolean,三个构造器是两用的,当跟 new 搭配时,它们产生对象,当直接调用时,它们表示强制类型转换。

Symbol 函数比较特殊,直接用 new 调用它会抛出错误,但它仍然是 Symbol 对象的构造器。

JS 语言设计上试图模糊对象和基本类型之间的关系,我们日常代码可以把对象的方法在基本类型上使用,比如:

console.log("abc".charAt(0)); //a

甚至我们在原型上添加方法,都可以应用于基本类型,比如以下代码,在 Symbol 原型上添加了hello方法,在任何 Symbol 类型变量都可以调用。

Symbol.prototype.hello = () => console.log("hello");
var a = Symbol("a");
console.log(typeof a); //symbol,a并非对象
a.hello(); //hello,有效

为什么给对象添加的方法能用在基本类型上?答案就是. 运算符提供了装箱操作,它会根据基础类型构造一个临时对象,使得我们能在基础类型上调用对应对象的方法。

关于装箱操作,这就是接下来要重点介绍的类型转换


typeof vs instanceof

提到类型,就不得不提下typeof 跟 instanceof。

typeof 对于原始类型来说,除了 null 都可以显示正确的类型。

typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'

typeof 对于对象来说,除了函数都会显示 object,所以说 typeof 并不能准确判断变量到底是什么类型。

如果我们想判断一个对象的正确类型,这时候可以考虑使用 instanceof,因为内部机制是通过原型链来判断的。

instanceof运算符用于测试构造函数的prototype属性是否出现在对象的原型链中的任何位置

但instanceof 也不是百分百可靠。看这个例子:

class PrimitiveString {
  static [Symbol.hasInstance](x) {
    return typeof x === 'string'
  }
}
console.log('hello world' instanceof PrimitiveString) // true

Symbol.hasInstance 允许我们自定义 instanceof 的行为。


结语

在本篇文章中,我们介绍了 JS 运行时的类型系统。

有一个说法是:程序 = 算法 + 数据结构,运行时类型包含了所有 JS 执行时所需要的数据结构的定义,所以我们要对它格外重视。