原始类型
原始类型的数据都是一些比较简单数据,比如字符串,数字等,比如:true和25,这些数据会被直接存储在变量的内存空间中。ECMAScript 5 给我们提供了5种原始类型:
类型 | 数据 | 说明 |
Boolean | true、false | 布尔值,true或false |
Number | 12、12.5、NaN | 整型、浮点型、特殊值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中保存的还是先前的值。
检测原始类型的数据
检测原始类型的数据最好的方式是使用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
原始类型数据的方法
虽然字符串,数字,布尔值是原始数据类型,但是也有很多方法可以使用(null和undefined没有方法)。例如:
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'
注意:尽管它们可以像对象一样具有方法,但是它们依然是原始类型,不是对象,后面讲到原始包装器类型的时候再给大家详细讲解。
引用类型
引用类型的数据稍微复杂一点,指的是JS中的对象,类似于其他编程语言中的类。对象是由一系列的键值对(属性名和属性值)组成的无序列表。ECMAScript给我们提供了如下几种内置对象:
- Object
- Array
- Date
- RegExp(regular expression)
- Function
- Error
- String
- Number
- Boolean
我们可以通过new操作符和构造函数创建对象的实例,还可以通过字面量的方式创建对象的实例。例如,下面的代码通过new操作符和创建一个Object对象的实例,并将实例保存到obj变量中:
var obj = new Object();
obj.name = 'zhangsan';
obj['age'] = 20;
console.log(obj)
引用类型的数据并没有直接存储在变量的内存空间中,变量的内存空间中保存的仅仅是引用类型数据在内存中的地址(指针)。为了方便理解,此处不对堆栈内存进行区分。
当我们将一个引用类型的变量赋值给另一个变量时,实际上将变量的中保存的地址拷贝了一份给了另一个变量,这时这两个变量都指向了同一个对象。
var obj1 = new Object();
obj1.name = 'zhangsan';
var obj2 = obj1;
通过其中一个变量改变对象的 name 属性值后,会影响到另一个变量,如下图所示:
var obj1 = new Object();
obj1.name = 'zhangsan';
var obj2 = obj1;
obj2.name = 'lisi';
虽然JavaScript是一种具有垃圾回收机制的语言,我们不用关心引用类型数据的内存分配问题。但是当我们不再使用某个引用类型的变量时,最好还是解除变量对实例的引用,这样有利于垃圾回收机制及时的进行回收,从而释放内存。解除引用最简单的方式就是,将变量赋值为null。
实例化内置类型
通过构造函数的方式
我们可以使用new操作符实例化每一个内置引用类型,例如:
var items = new Array();
var now = new Date();
var func = new Function('a','b','return a + b');
var obj = new Object();
var re = new RegExp('\\d+');
var error = new Error('你的浏览器不支持canvas');
通过字面量的方式
使用字面可以使我们在不使用new操作符和构造函数的情况下也可以实例化引用类型。
Object 和 Array的字面量
通过字面量的方式创建Object的实例:
var obj = {
name: 'zhangsan',
age: 20
};
我们还可以使用字符串作为属性名,当我们的属性名中有空格或其他特殊字符时,可以这样写:
var obj = {
name: 'zhangsan',
'my age': 20
};
console.log(obj['my name'])
尽管这两种写法在语法上有差异,但是它们的作用是相同的。它们等价于:
var obj = new Object();
obj.name = 'zhangsan';
obj['age'] = 20;
通过字面量的形式创建Array的实例:
var colors = ['red', 'green', 'blue'];
console.log(colors[0]); //'red'
上面代码等价于:
var colors = new Array('red', 'green', 'blue');
console.log(colors[0]); //'red'
函数的字面量
在我们创建方法的时候,使用最多的方式字面量的方式。使用Function构造函数的方式很少见。
通过字面量的方式创建Function类型的实例:
function reflect(value) {
return value;
}
// 上面代码等价于:
var reflect = new Function('value', 'return value');
使用字面量比使用构造函数更易于编写和理解。使用构造函数的方式不利于代码的调试,JavaScript的调试器不能正确识别它们。
正则表达式字面量
JavaScript给正则表达式提供了字面量的形式:
var pattern1 = /\d+/gi;
//等价于:
var pattern2 = new RegExp('\\d+', 'gi');
正则表达式的字面量形式比构造函数形式更容易处理,我们不用关心字符串中的字符是否需要转义。在使用构造函数的时候,匹配模式是以字符串的形式传递的,所以需要将反斜杠进行转义。
在实例化内置引用类型时,使用字面量或构造函数都可以,没有对错之分,但是在实例化Function类型的时,建议使用字面量的形式。
访问对象的属性
属性是以键值对的形式存储在对象中,访问属性最常用的方式是使用点的方式,但也可以是方括号的形式访问:
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('你的代码有错误');
// 使用 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('你的代码有错误');
// 使用instanceof操作符检测引用类型
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
原始包装器类型
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);
console.log(firstChar);
temp = null;
String类型的对象只存在于调用方法(charAt方法)的那一刻,随后就被销毁了(这一过被称为 自动装箱)。