数组是几乎所有编程语言的基础语法,JavaScript因为语法特性,之前缺少集合类对象,对数组的使用相对更多。
数组作为基础的语法知识,大部分人都已经非常熟悉了。本文将不会介绍它的基础语法和用法,而是从JavaScript中数组的一些特殊之处入手,通过这些少有的特性介绍,更加加深我们对数组知识的理解。
基本介绍
首先,作为开始,我们还是需要简单介绍下JavaScript中的数组:
- 有序的数据集合,索引值从0开始递增
- 拥有length长度属性
- 数组元素值可以是JavaScript中的任何类型
- 是动态的,可以增减元素
- 可以循环数组元素,拥有一系列可操作的实例方法
- 支持元素为数组的多维数组
- 读取元素以
数组名[索引值]
的方式表示
以上就是对数组的基础介绍,大部分都很熟悉,接下来,我们就来看看数组的一些特殊之处,
数组类型和判断
在JavaScript中,是没有数组这种数据类型的,所以数组本质上是一种特殊的对象,它的类型值会返回 object
,如下所示:
typeof [] // 'object'
由于返回的是 object
类型,就无法通过 typeof
语法判断一个值或对象是否是数组,得使用其他方式,才能正确的判断数组对象。
typeof的详细知识,可见博文一文搞懂js中的typeof用法。
判断为数组的方式
判断一个对象是否为数组,有不少种方式,但我们一般使用较多也就两三种,而其中最正确有效的方式就两种。
正确判断方式
这两种正确有效判断数组类型的方式如下:
- Array.isArray()
ES6推出的语法,专门用于判断对象类型是否为数组,是则返回true,否则返回false,简单好用。 - Object.prototype.toString.call()
在ES6推出之前的JS语法中,一般使用这种方式来判断数组,除了数组,它可以准确判断出其他几乎所有的JS数据类型。
Array.isArray([1, 2, 3]) // true
Object.prototype.toString.call([]) // '[object Array]'
Object.prototype.toString.call({}) // '[object Object]'
以上代码,就是这两种有效方式的示例,都能准确有效的判断。
当前的前端开发种,ES6语法基本普及的情况下,使用 Array.isArray()
将更方便。
其他方式介绍
除以上两种以外,还有其他几种基于原型链上的判断方式,可用于判断数组,但这些方式都不够准确:
- [] instanceof Array
这里使用instanceof运算符表示给定值是否是数组的实例。 - [].constructor === Array
给定值的实例构造函数是否是数组。 - Array.prototype.isPrototypeOf([])
给定值的原型链上是否存在数组。 - Object.getPrototypeOf([]) === Array.prototype
给定值的原型对象是否等于数组的原型对象。
这几种方式本质上都比较类似,只不过由于原型链能够被修改,所以这几种方式并不推荐使用。
如使用 instanceof
判断的方式:
[] instanceof Array // true
[] instanceof Object // true
以上代码,使用 instanceof
运算符时,一个数组实例属于 Array
和 Object
,都是成立的,因为Object在Array的原型链上。
数组索引值和长度
数组通过下标索引值进行元素值的读取,必须要使用方括号才可以,否则无法读取元素值。
const arr = [1, 2, 3]
arr[1] // 2
arr.1 // Uncaught SyntaxError: Unexpected number
以上代码,使用 arr.1
的写法,就报了语法错误,因为JavaScript中单独数字作为标识符是不合法的。关于错误类型,可见博文JavaScript中的Error错误对象与自定义错误类型。
前面提到,数组是一种特殊的对象,而object对象,是可以通过键名来读取元素的,只不过数组的键名只能是数字,所以当做标识符读取时报错。而object对象如果使用数字作键名时,也无法通过标识符来读取:
const obj = { 1: 'hello' }
obj.1 // Uncaught SyntaxError: Unexpected number
如上代码所示,对象使用 obj.1
的方式读取属性,也是报同样的错误。
索引值是字符串
数组使用方括号读取元素值,而Object对象也能通过这样的方法读取属性值,这样就算对象的键名是数字也能正常读取了:
const obj = { 1: 'hello', key: 'world' }
obj[1] // 'hello'
obj['key'] // 'world'
事实上,JavaScript中Object对象的键名均为字符串类型,而数组的类型又是object,所以它的索引值(键名)也可以使用字符串。
const arr = [1, 2, 3]
arr['1'] // 2
arr['2'] // 3
以上代码,当使用 ‘1’、‘2’ 等字符串时,也能正确读取数组的元素。
但需要注意的是,数组的索引值,必须是能自动转成正整数数字的值。如果是其他数值的时候,则需要注意。
索引值为小数、负数
如果数组使用小数或者负数读写操作时,数组是什么一种表现呢,可以看下面的代码:
const arr = [1,2]
arr[-1] = 0
arr[2.0] = 5
arr[3.6] = 8
Array.isArray(arr) // true
arr // [1, 2, 5, -1: 0, 3.6: 8]
arr.length // 3
arr[-1] // 0
arr[3.6] // 0
以上代码所示:
- 使用小数
2.0
,能自动转换成正整数,所以可以作为数组的第三个元素; - 使用负数
-1
和无法自动转换成正整数的小数3.6
,这两种情况都作为了数组的键值对的方式成为了数组的属性,但并不被包含在数组元素中,因为数组的length属性为3,并不包含这两个值。 - 负数
-1
和小数3.6
,都被当作字符串在使用,同理,也可以使用arr[true] = 50
,这里的bool值true
,也被当做了字符串 ‘true’。
所以,负数或小数不能作为数组的索引值,但可以被当做键值对的方式,作为数组的属性被读写。
索引值是字符串等其他类型时
如果我们给数组使用字符串、布尔值等其他类型的值作为下标索引时,这个时候和小数负数的表现类似。
就是把数组用作了对象,这些类型的下标索引则都被当做了数组对象的属性在操作,可以正常读写,但不是数组的元素,不计入数组的length长度中。
const arr = [1]
arr['key'] = 0
arr['value'] = '是的'
arr.length // 1
arr // [1, key: 0, value: '是的']
以上代码,就体现了数组属于对象类型的特点,可以增加键值对的属性。
length属性
length是数组的长度属性,表示数组元素的个数,但请注意,它不是只读,而是可写的,即我们可以给数组的length属性赋值:
[].length = 3
// 数组的长度被赋予为3
但有几点要注意:
- 如果设置数组的length值小于元素个数,则数组会自动删除所有大于length值的元素
- 如果设置数组的length值大于元素个数,则数组元素个数会自动增加到length值,并且新增的元素都是空位(返回undefined)
- 如果将数组的length属性设置为0,则会清空整个数组,变为空数组
- 数组的length值必须为正整数(或可以自动转换成正整数的值),其他值都是不合法的,设置不合法值时会报错。
- 能自动转换成正整数的值包含
2.0
、''
、true
、false
、'10'
等等这类
- 能自动转换成正整数的值包含
const arr = [1, 2]
arr.length = true
console.log(arr) // [1]
arr.length = -1 // VM307:1 Uncaught RangeError: Invalid array length
以上代码示例,
修改了数组的length值为true,true可以转换成1,数组的长度就变为了1,元素被删除了一个;
当设置数组长度为负数的时候,报错,无效的数组长度。
另外,除了以上几种情况,还有我们需要知道的一点就是,数组有最大长度。
数组的最大长度
JavaScript中的数组能赋予的最大长度是32位的正整数,即 2**32 - 1
= 4294967295,从0计算,长度减1。
当我们给数组设置的长度超过最大长度的值时,也会报无效长度的错误:
[].length = 4294967296 // Uncaught RangeError: Invalid array length
但如果我们使用超过最大长度的数字作为键名给数组赋值的话,则仍然可以使用,如下代码所示:
const arr = []
arr[4294967296] = 1
arr // [4294967296: 1]
前面也有介绍过,这是由于数组本质上是个对象,当使用超出最大长度范围的数字时,这个数字会被当做数组一个属性的键名,并且可以自动转成字符串,这个时候它并不数组的元素,也不计算在数组的length属性里,和前文介绍索引值时一样。