通过原型链探究ES中数据类型的异同

ES5版本中有6种数据类型,包括5种基本类型和1种复杂类型(引用类型)。本文从原型链的角度去深入学习各项数据类型。

《JavaScript高级程序设计》中包含的这些内容的章节:第三、四、五章

数据类型总览

老生常谈,JavaScript中的5种基本类型:
String Number Boolean undefined null
1种复杂类型(引用类型):
Object类型
同时ES中也提供了多种原生引用类型,它们在分类上属于引用类型,基于引用类型的原型对象实现。比如:
Object Array Date RegExp Function 等

基本类型

String Number Boolean 的含义和使用方法都很明确。

对于 undefined 和 null 它们在直观地理解上都表示空:

Boolean(undefined);  // false
Boolean(undefined) === Boolean(null);  // true

那么它们之间在含义和使用上有什么异同呢?
阮一峰有一篇关于这个问题的文章: undefined与null的区别

通过阅读书本和文章可以得到如下异同点:

相似点

如上面代码所以,它们在进行Boolean判断时返回都为 false ,也就是说它们在表达 Boolean 含义的层面上是一致的。

不同点

  • undefined 更多地表现为 未定义、未取到、无返回 的含义,比如:

当初始声明一个变量时,如果没有对这个变量赋值它会被设定默认值 undefined

let initVariable;
console.log(initVariable);  // undefined

当获取一个Object不存在属性或者获取一个Array越界的下标时会返回 undefined

let initObject = {};
console.log(initObject.undefinedProperty);  // undefined
initObject = [];
console.log(initObject[5]);  // undefined

当一个函数没有返回值时默认返回 undefined

let initFunction = function() {
    // 一个没有返回值的函数
}
console.log(initFunction());  // undefined
  • 而 null 值从逻辑角度来看,表示一个空对象指针。在后文中会提到,null还代表着最顶级的Object原型对象的原型对象的指向。
const initNull = null;
typeof initNull;  // "object"

因此建议如果定义的是一个object类型的变量的话最好将该变量初始化为 null 而不是默认的 undefined

  • 当它们在进行数字的转换时表现出来的值不同
    let initUndefined, initNull = null;
    console.log(Number(initUndefined));  // NaN
    console.log(Number(initNull));  // 0
  • 这里其实还可以提一点,就是对于后端来说更多的是只存在 null 而没有 undefined 这种数据类型。因此在和后端对接的时候可以看到--如果没有提前约定好字段的初始类型初始值的话,接口中的该字段会直接传过来 null 。

为什么说JavaScript中万物皆对象

首先要搞清楚原型对象,构造函数,实例三者之间的关系:
原型对象,构造函数,实例三者之间的关系

我们常说在JavaScript中万物皆对象,为什么这么说呢?很重要的一个原因是JavaScript中大部分的数据类型都是 Object 类型的实例。

引用类型

常用的几种原生引用类型自然不用多说:

  • Object
    /*Object 类型*/
    // Object本身就是一个构造函数
    console.log(Object);
    // 作为构造函数它本身就是Function的实例
    console.log(Object instanceof Function);  // true
    // Object 的原型对象是JavaScript封装的最顶级的对象,因此它不再是别的类型的实例
    console.log(Object.prototype.__proto__);  // null
    const initObject = {};
    // 在Firefox Safari Chrome中可以通过 __proto__ 属性访问到实例的原型对象
    console.log(initObject.__proto__);
    console.log(initObject.__proto__ === Object.prototype); // true
  • Array
    /*Array类型*/
    // Array本身就是一个构造函数
    console.log(Array);
    // 作为构造函数它本身就是Function的实例
    console.log(Array instanceof Function);  // true
    // 同时它也是Object的实例,因为Array的原型对象是Object.prototype的一个实例
    console.log(Array instanceof Object);  // true
    console.log(Array.prototype.__proto__ === Object.prototype);  //true

对于ES中的原生引用类型--Object Array Function Date RegExp 来说,它们都和上面写的代码类似:
它们本身是一个构造函数,而它们的原型对象又是Object原型对象的实例。

基本类型和基本包装类型

在设定中基本类型是没有属性和方法的,但是我们却能做一些这种操作:

const str = 'It is a string';
console.log(str.toString());

const floatNumber = 3.1415926;
console.log(floatNumber.toFixed(2));

这是不是说明他们其实也是一个对象呢?
接着上文的引用类型,ES不仅提供了多种原生引用类型,还提供了一种特殊的引用类型--基本包装类型
基本包装类型“包装了”三种基本类型:String Boolean Number,每当读取一个基本类型值的时候,后台就会创建一个对应的基本包装类型的对象,通过重写或添加在基本包装类型原型对象上的方法来实现对基本类型的方法调用。
而自动创建的基本包装类型的对象,则只存在于一行代码的执行瞬间,然后立即被销毁,所以我们依旧不能为基本类型添加属性和方法。

console.log(String);  //发现String本身和上面的原生引用类型一样是个构造函数
console.log(String.prototype.__proto__ === Object.prototype);  // true 那么它就和原生引用类型的表现一致了

经过测试,Boolean和Number的表现也和String及其它原生引用类型一致。

通过以上的学习,从原型链方面探讨了ES中各个数据类型的异同。
接下来将从内存空间方面探究基本类型和引用类型的异同。