这是一篇关于Javascript数据类型总结和对象的分类的文章。在书写这篇文章的过程中,再次验证了一个道理:在寻找答案的过程中,你会找到比答案本身还要多的知识。一开始本来只想总结红宝书中的Js的数据类型章节的内容,但是觉得引用类型部分说得还不够明确,后来再去看了《了不起的Js》、ECMAScript规范和犀牛书,从“数据类型”,“引用类型”、再慢慢牵扯出“对象的分类”和对构造函数的重新思考,不得不说,这一过程实在是太刺激了。

Javascript数据类型总结和对象的分类

目录

  1. Js数据类型
  2. 基本类型和引用类型的区别
  3. 基本类型中,要注意的几点
  4. 关于对象

参考文献

ECMAScript-262
《了不起的Js 中卷》第一章
《Javascript高级程序设计》第三章、第四章、第五章
《Javascript权威指南》第六章

一、Js数据类型

在《了不起的Js 中卷》第一章中,有一句话深受启发:

JavaScript 中的变量是没有类型的,只有【值】才有。变量可以随时持有任何类型的值。在对变量执行 typeof 操作时,得到的结果并不是该变量的类型,而是该变量持有的值的类型,因为 JavaScript 中的变量没有类型。

(typeof 运算符总是会返回一个字符串:typeof typeof 42; // "string"

ECMAScript有6种数据(值)类型:

  • Undefined类型 // undefined
  • Null类型 // null
  • Boolean类型 // true、false
  • Number类型 // 1、2、3…
  • String类型 // “a”、”B”、”123”
  • Object类型 // {a: 1, fun: function(){console.log(this.a)}}

其中,Undefined、Null、Boolean、Number、String类型的数据(值)被称为primitive value(直译为:原始数值)。Undefined、Null、Boolean、Number、String类型属于“基本类型”。

Object类型的数据(值)(即:对象的实例,形如{...}),实际上是一些属性的集合,每个Object类型的数据(值)都有一个prototype object(原型对象)。Object类型的值存放在堆中。Object类型属于“引用类型”。

这里有一个问题:什么是“基本类型”、“引用类型”呢?

如果一个数据(值)在赋值给一个变量时,是把【值的本身】直接赋给了变量的话,那么这个值属于基本类型。

如果一个数据(值)在赋值给一个变量时,只是把这个【值的引用(或者说指针)】赋给了变量的话,那么这个值属于引用类型。

Object类型之所以属于引用类型,是因为Object类型的值在赋值给一个变量时,只是把该值的引用(指针)赋给了变量,如果想要把该值本身赋给变量,就要使用我们平常常说的“浅拷贝”、“深拷贝”(通过递归的方式,把值中的每一个属性都“拷贝”出一个“副本”,把这些属性的“副本”组合成一个新的值,再把这个值赋给变量)。

现在我们知道对象的实例其实就是一个Object类型的值,而且我们还知道Array对象、Function对象、RegExp对象、Date对象Boolean对象、String对象和Number对象等都是继承Object对象的,所以平常所说的Array类型、Function类型、RegExp类型等其实均可看作是Object类型的“子类型”,所以Array类型、Function类型、RegExp类型、Date类型等都属于引用类型。

pic

二、基本类型和引用类型的区别

我们现在知道了,Javascript中的数据类型主要有6种,并分为两类:基本类型、引用类型。

定义基本类型值和引用类型值的方式是类似的:创建一个变量,并为该变量赋值。

而基本类型和引用类型的区别主要有2个:

  1. 值被保存到变量后的操作方式
    对于引用类型的值,我们可以为其添加属性和方法,也可以改变和删除其属性和方法。
    而基本类型的值,不可以为其添加属性和方法。

  2. 复制变量中的值时的方式
    在把一个变量中的值,复制到另一个变量中时(a = b),基本类型和引用类型的处理方式并不相同。

    如果从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上。

    例如:
    var num1 = 5;
    var num2 = num1;

    在此,num1 中保存的值是5。当使用num1 的值来初始化num2 时,num2 中也保存了值5。但num2中的5 与num1 中的5 是完全独立的,该值只是num1 中5 的一个副本。此后,这两个变量可以参与任何操作而不会相互影响。下图形象地展示了复制基本类型值的过程。

    pic

    当从一个变量向另一个变量复制引用类型的值时,同样也会将存储在变量对象中的值复制一份放到为新变量分配的空间中。不同的是,这个值的副本实际上是一个【指针】,而这个指针指向存储在堆中的一个对象。复制操作结束后,两个变量实际上将引用同一个对象。因此,改变其中一个变量,就会影响另一个变量。

    例如:

    var obj1 = new Object();
    var obj2 = obj1;
    obj1.name = “Nicholas”;
    alert(obj2.name); //“Nicholas”

    首先,变量obj1 保存了一个对象的新实例。然后,这个值被复制到了obj2 中;换句话说,obj1和obj2 都指向同一个对象。这样,当为obj1 添加name 属性后,可以通过obj2 来访问这个属性,因为这两个变量引用的都是同一个对象。下图展示了保存在变量对象中的变量和保存在堆中的对象之间的这种关系。

    pic

三、基本类型中,要注意的几点

  1. 【Undefined类型和Null类型的区分】

    • Undefined类型的值只有一个,即undefined。当一个变量被声明(var)后,但未被给定初值时,变量的值默认为undefined。(注意:如果变量未被声明(var),它只有一种操作是合法的,就是typeof,此时会返回”undefined”类型,此外所有操作都是不合法的,会报错referrence erro…)

    • Null类型的值也是只有一个,即null,这个值表示空对象指针,所以用typeof来检测时,会返回“object”。

    • 实际上,undefined 值是派生自null 值的,因此ECMA-262 规定对它们的相等性测试要返回true:alert(null == undefined); //true

  2. 【Boolean类型的转换】

    转换规则:

    pic

    pic

  3. 【Number类型浮点数值计算会产生四舍五入的误差】

    • 浮点数值的最高精度是17 位小数,但在进行算术计算时其精确度远远不如整数。例如,0.1 加0.2的结果不是0.3,而是0.30000000000000004。这个小小的舍入误差会导致无法测试特定的浮点数值。因此,永远不要测试某个特定的浮点数值。

    • 关于浮点数值计算会产生舍入误差的问题,有一点需要明确:这是使用基于IEEE754 数值的浮点计算的通病,ECMAScript 并非独此一家;其他使用相同数值格式的语言也存在这个问题。

四、关于对象

(Js对象的分类)

constructor(构造函数)用来创建一个原生对象(native object),并返回该对象的实例。

在constructor里面的this指向这个【新创建的对象】,用new关键字调用constructor后,会自动返回这个新创建对象的实例(即:Object类型的数据(值))。

  • 【若这个“新创建的对象”是内置对象(build-in object)】:【内置对象(build-in object)的constructor】会创建内置对象,并返回内置对象的实例。(内置对象可以看作是“正规军”,是ECMAScript规范定义的对象,例如Array,Function等。)。内置对象(build-in object)的constructor对应有:Object()、Array()、Function()、Date()等,这些constructor官方已经写好,可以直接用new关键字调用,无需用户再写constructor,例如: var arr = new Array()var myDate = new Date()

  • 【若这个“新创建的对象”是自定义对象(user-defined object)】:由于自定对象不是“正规军”,因此【自定义对象的constructor】要由用户自己来写。例如:

// 用户自定义的constructor
// 当用new关键字来调用这个constructor时,会创建一个自定义对象
// constructor里面的this会指向这个正在被创建的自定义对象
// 通过【this.属性名】或【this.方法名】来创建对象实例的属性和方法
// 最后自动返回这个对象的实例
function myConstructor() {
    this.a = "Hello";
    this.b = function() {
        console.log(this.a);    
    }
}

var myObj = new myConstructor();  
// new关键字调用constructor后,constructor会返回一个Object类型的值(或者说是返回了一个对象的实例)
// 变量myObj存了一个Object类型的值(或者说变量myObj存了一个对象的实例)

附注:ECMAScript中对象的分类

pic

原生对象(native object):遵循ECMAScript规范定义的对象。有2类,一类是内置对象(build-in object),一类是【自定义对象】(user-defined object)

内置对象(build-in object):遵循ECMAScript规范定义的对象,是ECMAScript官方定义的对象,看以看作是“正规军”对象。
ECMAScript的build-in object有:

  1. The Global Object
  2. Object Objects
  3. Function Objects
  4. Array Objects
  5. String Objects
  6. Boolean Objects
  7. Number Objects
  8. The Math Object
  9. Date Objects
  10. RegExp (Regular Expression) Objects
  11. Error Objects
  12. The JSON Object

(其中,Global对象和Math对象由被称为“单体内置对象”,这两个对象不需要用new关键字来调用,而是直接使用,例如Math.floor(...)
(另外,Array对象、Function对象、RegExp对象、Boolean对象、String对象和Number对象等都是继承Object对象的)

自定义对象(user-defined object):在运行中的Javascript代码创建的对象。

宿主对象(host object):在某个特殊的环境(场景)中,存在的一些特殊的对象,这些特殊的对象只有在这个特殊的环境(场景)中才存在,并遵循着这个特殊环境中的规范。例如宿主环境为浏览器时,会有window对象、navigator对象、location对象、 history对象、screen对象、document对象、XMLHttpRequest对象(所有不是native object 的都属于host object)