Skip to content

引用类型

引用类型的值(对象)是引用类型的一个实例。
在 ECMAScript 中,引用类型是一种数据结构,用于将数据和功能组织在一起。
它也常被称为类,但这种称呼并不妥当。

尽管 ECMACsript 从技术上讲是一门面向对象的语言,但它不具备传统的面向对象所支持的类和接口等基本结构。

引用类型有时候也被称为对象定义,因为它们描述的是一类对象所具有的属性和方法

虽然引用类型与类看起来相似,但它们并不是相同的概念

对象是某个特定引用类型的实例。
新对象是使用 new 操作符后跟一个构造函数来创建的。
构造函数本身就是一个函数,只不过该函数是处于创建新对象的目的而定义的

1
var person = new Object();

这行代码创建了 Object 引用类型的一个新实例,然后把该实例保存在了变量 person 中。
使用的构造函数是 Object,它只为新对象定义了默认的属性和方法。
ECMAScript 提供了很多原生引用类型(例如 Object),以便开发人员用以实现常见的计算任务

Object 类型

Object 是 ECMAScript 中使用最多的一个类型。
虽然 Object 的实例不具备多少功能,但对于在应用程序中存储和传输数据而言,它们确实是非常理想的选择

创建 Object 实例的方式有两种。
第一种是使用 new 操作符后跟 Object 构造函数

1
2
3
var person = new Object();
person.name = 'Nicholas';
person.age = 29;

另一种方式是使用对象字面量表示法。
对象字面量是对象定义的一种简写形式,目的在于简化创建包含大量属性的对象的过程。

1
2
3
4
var person = {
  name: 'Nicholas',
  age: 29
}

在这个例子中,左边的花括号表示对象字面量的开始,因为它出现在了表达式上下文中。
ECMAScript 中的表达式上下文指的是该上下文期待一个值(表达式)

赋值操作符表示后面是一个值,所以左花括号在这里表示一个表达式的开始。
同样的花括号,如果出现在一个语句上下文中,例如跟在 if 语句条件的后面,则表示一个语句块的开始

在使用对象字面量语法时,属性名也可以使用字符串

1
2
3
4
5
var person = {
  'name': 'Nicholas',
  'age': 29,
  5: true
}

这个例子会创建一个对象对象,包含三个属性:name、age 和 5.
但这里的数值属性名会自动转换为字符串

另外,使用对象字面量语法时,如果留空花括号,则可以定义只包含默认属性和方法的对象

1
2
3
var person = {}; // 与 new Object() 相同
person.name = 'Nicholas';
person.age = 29;

虽然可以用前面介绍的任何一种方法来定义对象,但开发人员更青睐对象字面量语法,因为这种语法要求的代码量少,而且能够给人封装数据的感觉

实际上,对象字面量也是向函数传递大量可选参数的首选方式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
function displayInfo(args) {
  var output = '';
  if (typeof args.name == 'string') {
    output += 'Name: ' + args.name + '\n';
  }
  if (typeof args.name == 'number') {
    output += 'Age: ' + args.age + '\n';
  }
  alert(output);
}
displayInfo({
  name: 'Nicholas',
  age: 29
})
displayInfo({
  name: 'Greg'
})

一般来说,访问对象属性时使用的都是点表示法,这也是很多面向对象语言中通用的语法。
不过,在 JavaScript 也可以使用方括号表示法来访问对象的属性。
在使用方括号语法时,应该将要访问的属性以字符串的形式放在方括号中

1
2
alert(person['name']);
alert(person.name);

从功能上看,这两种访问对象属性的方法没有任何区别。
但方括号语法的主要有点时可以通过变量来访问属性

1
2
var propertyName = 'name';
alert(person[propertyName]);

如果属性名中包含会导致语法错误的字符,或者属性名使用的是关键字或保留字,也可以使用方括号表示法。

1
person['first name'] = 'Nicholas';

由于"first name"中包含一个空格,所以不能使用点表示法来访问它。
然而,属性名中是可以包含非字母非数字的,这时候就可以使用方括号表示法来访问它们

通常,除非必须使用变量来访问属性,否则建议使用点表示法

Array 类型

除了 Object 之外,Array 类型恐怕是 ECMAScript 中最常用的类型了。
而且,ECMAScript 中的数组与其他多数语言中的数组有着相当大的区别。
虽然 ECMAScript 数组与其他语言中的数组都是数据的有序列表,但与其他语言不同的是,ECMAScript 数组的每一项可以保存任何类型的数据。
也就是说,可以用数组的第一个位置来保存字符串,用第二个位置来保存数值,用第三个位置来保存对象,以此类推。
而且,ECMAScript 数组的大小是可以动态调整的,即可以随着数据的添加自动增长以容纳新增数据

创建数组的基本方式有两种。
第一种是使用 Array 构造函数

1
var colors = new Array();

如果预先知道数组要保存的项目数量,也可以给构造函数传递该数量,而该数量会自动变成 length 属性的值。
例如,下面的代码将创建 length 值为 20 的数组

1
var colors = new Array(20);

也可以向 Array 构造函数传递数组中应该包含的项。
以下代码创建了一个包含 3 个字符串值的数组:

1
var colors = new Array('red', 'blue', 'green');

当然,给构造函数传递一个值也可以创建数组。
但这时候问题就复杂一点了,因为如果传递的数值,则会按照该数值创建包含给定项数的数组;
而如果传递的是其他类型的参数,则会创建包含那个值的只有一项的数组

1
2
var colors = new Array(3); // 创建一个包含 3 项的数组
var names = new Array('Greg'); // 创建一个包含 1 项,即字符串 'Greg' 的数组 

另外,在使用 Array 构造函数时也可以省略 new 操作符。

1
2
var colors = Array(3);
var names = Array('Greg');

创建数组的第二种基本方式是使用数组字面量表示法。
数组字面量由一对包含数组项的方括号表示,多个数组项之间以逗号隔开

1
2
3
4
var colors = ['red', 'blue', 'green']; // 创建一个包含 3 个字符串的数组
var names = []; // 创建一个空数组
var values = [1, 2,] // 不要这样!这样会创建一个包含 2 或 3 项的数组  
var options = [,,,,,]; // 不要这样!这样会创建一个包含 5 或 6 项的数组 

在读取和设置数组的值时,要使用方括号并提供相应值的基于 0 的数字索引

1
2
3
4
var colors = ['red', 'blue', 'green']; // 定义一个字符串数组
alert(colors[0]); // 显示第一项
colors[2] = 'black'; // 修改第三项
colors[3] = 'brown'; // 新增第四项

如果设置某个值的索引超过了数组现有项数,如这个例子中的 colors[3] 所示,数组就会自动增加到该索引值加 1 的长度(就这个例子而言,索引是 3,因此数组长度就是 4)

数组的项数保存在其 length 属性中,这个属性始终会返回 0 或更大的值

1
2
3
4
var colors = ['red', 'blue', 'green'];
var names = [];
console.log(colors.length); // 3
console.log(names.length); // 0

数组的 length 属性很有特点--它不是只读的。
因此,通过设置这个属性,可以从数组的末尾移除项或向数组中添加新项

1
2
3
var colors = ['red', 'blue', 'green'];
colors.length = 2;
console.log(colors[2]); // undefined

这个例子中的数组 colors 一开始有 3 个值。
将其 length 属性设置为 2 会移除最后一项(位置为 2 的那一项),结果再访问 colors[2] 就会显示 undefined 了。
如果将其 length 属性设置为大于数组项数的值,则新增的每一项都会取得 undefined 值

1
2
3
var colors = ['red', 'blue', 'green'];
colors.length = 4;
console.log(colors[3]); // undefined

在此,虽然 colors 数组包含 3 项,但把它的 length 属性设置为 4.
这个数组不存在位置 3,所以访问这个位置的值就得到了特殊值 undefined

利用 length 属性也可以方便地在数组末尾添加新项

1
2
3
var colors = ['red', 'blue', 'green'];
colors[colors.length] = 'black';
colors[colors.length] = 'brown';

当把一个值放在超过当前数据大小的位置上时,数据就会重新计算其长度值,即长度值等于最后一项的索引加 1,如下面的例子所示:

1
2
3
4
5
6
7
8
9
let colors = ['red', 'blue', 'green']
colors[99] = 'black'
console.log(colors.length)
colors[colors.length] = 'brown'
console.log(colors.length)
console.log(colors[50])
// 100
// 101
// undefined

在这个例子中,我们向 colors 数组的位置 99 插入了一个值,结果数组新长度就是 100(99 + 1)。
而位置 3 到位置 98 实际上都是不存在的,所以访问它们都将返回 undefined

检测数组

使用instanceof操作符就能得到满意的结果:

1
2
3
4
5
let l = [1, 2, 3]
if (l instanceof Array) {
  console.log('hhh')
}
// hhh

instanceof操作符的问题在于,它假定只有一个全局执行环境。
如果网页中包含多个框架,那实际上就存在两个以上不同的全局执行环境,从而存在两个以上不同版本的Array构造函数。如果你从一个框架向另一个框架传入一个数组,那么传入的数组与第二个框架中国呢原生创建的数据分别具有各自不同的构造函数。

为了解决这个问题,ECMAScript 5 新增了Array.isArray()方法。
这个方法的目的是最终确定某个值到底是不是数组,而不管它是在哪个全局执行环境中创建的。

1
2
3
4
5
let l = [1, 2, 3]
if (Array.isArray(l)) {
  console.log('hhh')
}
// hhh

转换方法

如前所述,所有对象都具有toLocaleString(), toString()valueOf()方法。
其中,调用valueOf()返回的还是数组本身,而调用数组的toString()方法会返回由数组中每个值的字符串形式拼接而成的一个以逗号分割的字符串。
实际上,为了创建这个字符串会调用数组每一项的toString()方法。

1
2
3
4
5
6
7
let colors = ["red", "blue", "green"]
console.log(colors.toString())
console.log(colors.valueOf())
console.log(colors)
// red,blue,green
// [ 'red', 'blue', 'green' ]
// [ 'red', 'blue', 'green' ]

栈方法

ECMAScript 数组也提供了一种让数组的行为类似于其他数据结构的方法。
具体来说,数据可以表现得就像栈一样,后者是一种可以限制插入和删除项的数据结构。

ECMAScript 为数组专门提供了push()pop()方法,以便实现类似栈的行为。
push()方法可以接收任意数量的参数,把它们逐个添加到数组末尾,并返回修改后数组的长度。
pop()方法则从数组末尾移除最后一项,减少数组的length值,然后返回移除的项

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
let colors = new Array()
let count = colors.push("red", "green")
console.log(count)
count = colors.push("black")
console.log(count)
let item = colors.pop()
console.log(item)
console.log(colors.length)
// 2
// 3
// black
// 2
1
2
3
4
5
6
7
8
let colors = ["red", "blue"]
colors.push("brown")
colors[3] = "black"
console.log(colors.length)
let item = colors.pop()
console.log(item)
// 4
// black

队列方法

要模拟队列只需一个从数组前端取得项的方法。
实现这一操作的数组方法就是shift(),它能够移除数组中的第一项并返回该项,同时将数组长度减一。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
let colors = new Array()
let count = colors.push("red", "green")
console.log(count)
count = colors.push("black")
console.log(count)

let item = colors.shift()
console.log(item)
console.log(colors.length)
// 2
// 3
// red
// 2