深入理解面向对象中的原始类型和引用类型

1. 什么是数据类型?

我们先前学过的数据类型可以划分成两类:原始类型和引用类型。原始类型的数据都是一些比较简单数据,比如字符串,数字等。引用类型的数据稍微复杂一点,比如对象。

原始类型的数据直接存储在变量的内存空间中,而引用类型的数据并没有直接存储在变量的内存空间中,变量的内存空间中保存的仅仅是引用类型数据在内存中的地址(指针)。

2. 原始类型

原始类型的数据都是一些比较简单的数据,比如:true25,这些数据会被直接存储在变量的内存空间中。ECMAScript 5 给我们提供了5种原始类型:

类型数据说明
Boolean truefalse 布尔值,truefalse
Number 1212.5NaN 整型、浮点型、特殊值NaN(Not a number)
String 'hello'"hello" 被单引号或双引号扩起来的字符或字符串
Null null 只有一个值null
Undefined undefined 只有一个值undefined。任何只声明而没有赋值的变量都会被赋值为undefined

所有原始类型的值都可以使用字面量的方式表示。例如:

 // strings
    var name = "Hello world!";

    // numbers
    var count = 25;
    var price = 2.5;

    // boolean
    var married = true;

    // null
    var object = null;

    // undefined

    var flag = undefined;
    var ref;                // 会被自动初始化为undefined

变量在存储原始类型的数据时,直接将数据存储到变量的内存空间中。当我们将存储原始类型数据的变量赋值给另一个变量时,其实是将变量存储的值复制了一份保存到了另一个变量中。例如:

 var color1 = 'red';
    var color2 = color1;

变量对象

正因为每一个变量都是使用自己独立的存储空间保存原始类型的数据,因此当我们改变一个变量中的数据时不会影响到另个变量中的数据。例如:

var color1 = 'red';
    var color2 = color1;

    console.log(color1);        // 'red'
    console.log(color2);        // 'red'

    color1 = 'blue';

    console.log(color1);        // 'blue'
    console.log(color2);        // 'red'

在上面的代码中,color1的值被修改成blue,而color2中保存的还是先前的值。

2.1 检测原始类型的数据

检测原始类型的数据最好的方式是使用typeof操作符,该操作符会返回一个表示数据类型的字符串。例如:

 var str = '王志龙';
    var num = 23;
    var married = false;
    var id = undefined;

    console.log(typeof str);        // 'string'
    console.log(typeof num);        // 'number'
    console.log(typeof married);    // 'boolean'
    console.log(typeof id);         // 'undefined'

上面这些数据的检测结果跟我们预期的结果是一样,然而,最不好理解的是null

var value = null;
    console.log(typeof value);       // 'object'

当使用typeof检测null时,结果为object。实际上这是ECMAScript规范中一个公认的错误(TC39)。我们可以把null看作是一个对象的空指针。那我们怎样检测一个数据是否是null呢?其实我们可以将要检测的数据和null进行比较,例如:

 var value = null;
    console.log(value === null);    // true

2.2. 原始类型数据的方法

虽然字符串,数字,布尔值是原始数据类型,但是也有很多方法可以使用(nullundefined没有方法)。例如:

var str = 'Hello world';
    var lowercaseStr = str.toLowerCase();   // 转换成小写形式
    var firstLetter = str.charAt(0);        // 获取第一个字符
    var subString = str.substring(2,4);     // 获取2-4的字符
    var splitStr = str.split('o');          // 将字符串以指定的分隔符分割成数组

    var count = 10;
    var fixedCount = count.toFixed(2);      // 转换成 10.00
    var hexCount = count.toString(16);      // 转换成 'a'

    var flag = true;
    var stringFlag = flag.toString();       // 转换成'true'

注意:尽管它们可以像对象一样具有方法,但是它们依然是原始类型,不是对象,后面讲到原始包装器类型的时候再给大家详细讲解。

3. 引用类型

引用类型的数据指的是JS中的对象,类似于其他编程语言中的类。对象是由一系列的键值对(属性名和属性值)组成的无序列表。ECMAScript给我们提供了如下几种内置对象:

  • Object
  • Array
  • Date
  • RegExp(regular expression)
  • Function
  • Error
  • String
  • Number
  • Boolean

我们可以通过new操作符和构造函数创建对象的实例,还可以通过字面量的方式创建对象的实例。例如,下面的代码通过new操作符和创建一个Object对象的实例,并将实例保存到obj变量中:

var obj = new Object();
obj.name = 'zhangsan';

引用类型的数据并没有直接存储在变量的内存空间中,变量的内存空间中保存的仅仅是引用类型数据在内存中的地址(指针)。

引用类型的变量对内存中实例的引用示意图

当我们将一个引用类型的变量赋值给另一个变量时,实际上将变量的中保存的地址拷贝了一份给了另一个变量,这时这两个变量都指向了同一个对象。

var obj1 = new Object();
var obj2 = obj1;

两个变量指向同一个引用类型的实例

虽然JavaScript是一种具有垃圾回收机制的语言,我们不用关心引用类型数据的内存分配问题。但是当我们不再使用某个引用类型的变量时,最好还是解除变量对实例的引用,这样有利于垃圾回收机制及时的进行回收,从而释放内存。解除引用最简单的方式就是,将变量赋值为null

var object1 = new Object();

//do something

object1 = null;     //解除object1对实例的引用

4 实例化内置对象

通过构造函数的方式

我们可以使用new操作符实例化每一个内置引用类型,例如:

var items = new Array();
        console.log(items);

        var now = new Date();
        console.warn(now);

        var func  = new Function('a','b','return a + b');
        console.warn(func);

        var re = new  RegExp('\d+');
        console.warn(re);

        var error = new Error('你的有浏览器不支持canvas');
        console.warn(error);

        var num = new Number(123);
        console.warn(num);

        var bol = new Boolean(true);
        console.warn(bol);

4.1 通过字面量的方式

使用字面可以使我们在不使用new操作符和构造函数的情况下也可以实例化引用类型。

4.2 Object 和 Array的字面量

通过字面量的方式创建Object的实例:

var obj = {
    name: 'lisi',
    age: 25
};

我们还可以使用字符串作为属性名,当我们的属性名中有空格或其他特殊字符时,可以这样写:

var obj = {
    'name': 'lisi',
    'age': 25
};

尽管这两种写法在语法上有差异,但是它们的作用是相同的。它们等价于:

var obj = new Object();
obj.name = 'lisi';
obj.age = 25;

通过字面量的形式创建Array的实例:

var colors = ['red', 'green', 'blue'];
console.log(colors[0]);                 //'red'

上面代码等价于:

var colors = new Array('red', 'green', 'blue');
console.log(colors[0]);                 //'red'

4.3 函数的字面量

在我们创建方法的时候,使用最多的方式字面量的方式。使用Function构造函数的方式很少见。

通过字面量的方式创建Function类型的实例:

function reflect(value) {
    return value;
}

上面代码等价于:

var reflect = new Function('value','return value');

使用字面量比使用构造函数更易于编写和理解。使用构造函数的方式不利于代码的调试,JavaScript的调试器不能正确识别它们。

4.4 正则表达式字面量

JavaScript给正则表达式提供了字面量的形式:

var pattern1 = /\d+/gi;

//等价于:

var pattern2 = new RegExp('\\d+','gi');

正则表达式的字面量形式比构造函数形式更容易处理,我们不用关心字符串中的字符是否需要转义。在使用构造函数的时候,匹配模式是以字符串的形式传递的,所以需要将反斜杠进行转义。

在实例化内置引用类型时,使用字面量或构造函数都可以,没有对错之分,但是在实例化Function类型的时,建议使用字面量的形式

5. 访问对象的属性

属性是以键值对的形式存储在对象中,访问属性最常用的方式是使用点的方式,但也可以是方括号的形式访问:

var obj = {
    name: 'zhansan',
    age: 34
};

console.log(obj.name);      //'zhansan'

//等价于:

console.log(obj['name']);      //'zhansan'
var arr = new Array('1',2,false);

    var date = new Date();

    var rp = new RegExp('\d+', 'g');

    var func = new Function('a','b','return a + b');

    var obj = new Object();

    var error = new Error('你的代码有错误');

    // 1. 使用typeof操作符检测引用类型
    console.log(typeof arr);        // 'object'
    console.log(typeof date);       // 'object'
    console.log(typeof rp);         // 'object'
    console.log(typeof func);       // 'function'
    console.log(typeof obj);        // 'object'
    console.log(typeof error);      // 'object'

使用typeof检测引用类型时,除了Function以外,其他的引用类型都不能被正确的识别。这个时候我们可以使用instanceof来检测它们。

instanceof操作符需要一个对象的实例和对象的构造函数作为参数,如果实例是使用该构造函数创建话,返回true,否则返回false:

 var arr = new Array('1',2,false);

    var date = new Date();

    var rp = new RegExp('\d+', 'g');

    var obj = new Object();

    var error = new Error('你的代码有错误');

    // 1. 使用typeof操作符检测引用类型
    console.log(arr instanceof Array);         // true
    console.log(date instanceof Date);         // true
    console.log(rp instanceof RegExp);         // true
    console.log(obj instanceof Object);        // true
    console.log(error instanceof Error);       // true

因为所有的引用类型都继承自Object,因此每个引用类型的实例其实也是Object的实例:

    console.log(arr instanceof Object);         // true
    console.log(date instanceof Object);        // true

7. 原始包装器类型

JavaScript中最让疑惑的可能就是原始包装器类型。JavaScript给我们提供了 3 种包装器类型(String,Number,Boolean)。

原始包装器类型也是引用类型,当字符串,数字或布尔值被读取的时候,原始包装器类型会自动在后台创建。例如:

 var name = 'zhangsan';
    var firstChar = name.charAt(0);
    console.log(firstChar);

后台为我们做了什么呢?

 var name = 'zhangsan';

var temp = new String(name);
var firstChar = temp.charAt(0);
temp = null;

console.log(firstChar);

String类型的对象只存在于调用方法(charAt方法)的那一刻,随后就被销毁了(这一过被称为 自动装箱)。

8. 总结

JavaScript没有类,但是它有类型,分为原始类型和引用类型。

原始类型的值直接被保存在变量中,引用类型的值并不是直接保存变量中,变量中保存的仅仅是引用类型的值所在的内存地址。

在检测原始类型的值时,除了null以外,其他的原始类型都可以使用typeof操作符。null必须跟特殊值null进行比较(value === null)。

在创建引用类型的实例时,我们可以使用字面量和构造函数的方式。

在访问对象的属性时,我们可以使用点表示法(obj.name)和方括号表示法(obj['name'])。

函数也是JavaScript中的对象,我们可以使用typeof检测它。其他类型的对象必须使用instanceof操作符进行检测。

为了使我们在操作原始类型数据的时候更加方便,JavaScript允许原始类型的值使用方法。

JavaScript给我们提供了 3 种包装类型,StringNumberBoolean,一般情况下,我们不会直接使用它们。当我们读取原始类型的值时,JavaScript会在幕后(后台)自动创建它们。这样我们就可以向处理常规对象那样处理原始类型数据了,但是使用它们的语句一旦结束,这些临时创建的对象就会被销毁。

虽然我们可以直接创建原始包装器类型的实例,但是建议不要这样做,因为有些时候会发生错误。

posted @ 2017-09-12 10:23  小周sri的码农  阅读(636)  评论(0编辑  收藏  举报