3

一文搞懂JavaScript数组的特性 - jimojianghu

 1 year ago
source link: https://www.cnblogs.com/jimojianghu/p/17292277.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

数组是几乎所有编程语言的基础语法,JavaScript因为语法特性,之前缺少一些集合类对象,对数组的使用就会更多一些,因此我们更需要理解数组知识。
然而大部分人对数组都已经非常熟悉了,所以本文将不会介绍数组的基础语法和用法,而是从JavaScript中数组的一些特殊之处入手,通过这些少有特性的详细介绍,加深我们对数组的理解。

首先,作为开始,我们还是需要简单介绍下JavaScript中的数组,基本如下:

  • 有序的数据集合,索引值从0开始递增
  • 拥有length长度属性
  • 数组元素值可以是JavaScript中的任何类型
  • 是动态的,可以增减元素
  • 可以循环数组元素,拥有一系列可操作的实例方法
  • 支持元素为数组的多维数组
  • 读取元素以数组名[索引值]的方式表示

以上就是对数组的基础介绍,大部分都很熟悉,接下来,我们就来看看数组的一些特殊之处,

数组类型和判断

在JavaScript中,是没有数组这种数据类型的,所以数组本质上是一种特殊的对象,它的类型值会返回 object,如下所示:

typeof [] // 'object'

由于返回的是 object 类型,就无法通过 typeof 语法判断一个值或对象是否是数组,得使用其他方式,才能正确的判断数组对象。

typeof的详细知识,可见博文 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 运算符时,一个数组实例属于 ArrayObject,都是成立的,因为Object在Array的原型链上。

数组索引值和长度

数组通过下标索引值进行元素值的读取,必须要使用方括号才可以,否则无法读取元素值。

const arr = [1, 2, 3]
arr[1] // 2
arr.1 // Uncaught SyntaxError: Unexpected number

以上代码,使用 arr.1 的写法,就报了语法错误,因为JavaScript中单独数字作为标识符是不合法的。关于错误类型,可见博文js中的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''truefalse'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属性里,和前文介绍索引值时一样。

创建数组的三种方式

当前JS中,创建数组大致有三种方式:数组字面量语法、Array构造函数、Array.of()。

数组字面量语法

字面量语法创建数组,是JavaScript中最常用的一种方式,由于方便简单,大部分代码都会使用它。

const arr1 = [] // 创建一个空数组
const arr2 = [1, 2, true, null, 'hello'] // 给定数组的元素
console.log(arr2) // [1, 2, true, null, 'hello']

以上代码,就是使用了字面量语法创建数组,如果不给元素值就会创建一个空数组。
需要了解的是,数组字面量语法其实也是基于对Array构造函数的一种简化使用。

Array构造函数

Array对象本身是JavaScript的一个内置对象,它是 function 类型,能当做构造函数使用,这与大多数内置对象一样,是否使用 new 操作符都可以。
因此,我们通过调用 Array()new Array(),就能创建数组对象。数组构造函数可以使用多种形式的可选参数,只不过依据参数不同,结果会有一定差异。

  1. 不传参数时,返回一个空数组
Array() // []
  1. 只传入一个参数时,表现不太一致
  • 当参数为0时,创建一个空数组
Array(0) // []
  • 当参数是单个正整数时,将创建一个length长度为该参数数值的数组,并且所有数组元素都是空位(返回undefined)
const arr = new Array(10)
arr.length // 10
arr[2] // undefined
  • 当参数是非正整数,如负数、小数等等,则会报错,无法创建数组
Array(10.6) // Uncaught RangeError: Invalid array length
Array(-6) // Uncaught RangeError: Invalid array length

以上代码,小数和负数,都报无效数组长度的错误。

但是,对于能自动转换成整数的数字(小数位为0),则可以正常创建数组,如下代码所示:

const arr = new Array(10.0)
arr.length // 10
arr[2] // undefined
  • 当参数是非数字的其他类型,如字符串值、布尔值、对象、数组、函数、null、undefined等等,则该参数将会成为新数组的第一个元素,数组的length长度自然就是1。
Array('hello') // ['hello']
Array(true) //[true]
Array([30]) // [Array(1)]
Array(null) // [null]
  • 只有一个参数的情况下,如果参数里多了尾逗号,则会忽略,仍能正常创建数组:
Array(0,) // []
Array(true,) // [true]
  1. 传入多个参数时

所有参数都会成为新数组的元素,不论参数是什么类型的值。

Array(1, 2) // [1, 2]
Array(1, 2, true, null, 'hello')  // [1, 2, true, null, 'hello']
Array(-1, 2.2, 3) // [-1, 2.2, 3]
Array(1, 2, 3,) // [1, 2, 3]

以上代码,
第三行,加了负数和小数,也能正常创建数组;
第四行,参数里多了一个尾逗号,但会被忽略。

Array.of()

Array.of(item…) 是ES6提供的一个数组静态方法,使用它同样可以创建数组。

该方法也提供了可选参数,但与Array构造函数的区别在于:Array.of() 的行为更一致,不论它的参数有几个、是什么类型,都会被当做新数组的元素成员,当然如果不传入参数,则会生成空数组。包括负数和小数,也都会被当成元素,不会报错。
比如,Array.of(5) 会创建只有一个成员(5)且长度为1的数组,而 Array(5) 则是创建一个长度为5且元素皆是空位的数组。

所以,Array.of方法弥补了Array构造函数参数差异导致的不足,除了字面量语法外,我们应该尽量用于替代Array构造函数。

Array.of() // []
Array.of(5) // [5]
Array.of(1, 2, true, null, 'hello') // [1, 2, true, null, 'hello']
Array.of(-1) // [-1]
Array.of(2.2, 0) // [2.2, 0]

从以上代码可以看出,当我们使用Array.of方法时,所有参数都作为了数组的元素,这可以完全解决Array构造函数带来的不一致问题。

空位(空元素)

空位是指数组中某个逗号前面,没有任何值,是空的,这样的元素就是空元素,又叫空位。
空位在JavaScript的数组中是允许存在的,并不会报语法错误或其他异常,数组能正常使用,所以我们需要了解它的一些特性。

逗号后面没有值,不产生空位,也不影响数组。

我们先看一个空位的示例:

const arr1 = [,]
const arr2 = [1, , , 4]

以上代码,就是空位在数组中的表现,其中arr1有1个空位,arr2在首尾两个元素中间有2个空位。

空位作为数组的一种特殊存在,它的一些基本表现如下:

  • 空位是数组的一个正常元素
  • 空位会被计算进数组的长度里,即length属性会包含空位
  • 空位可以通过索引读取,返回undefined。
const arr = [1, , , 4]
arr.length // 4
arr[2] // undefined

以上代码,定义了有2个空位的数组,数组长度不受影响,是4,读取空位值时返回undefined。

除了以上这些特点以外,空位还有其他一些需要注意的地方。

delete

delete运算符能够删除对象的属性,那么同样可在数组中使用它。
使用delete操作符删除数组元素的时候,就会产生空位,元素表现也符合空位的特点。

const arr = ['a', 'b', 'c']
delete arr[1]
console.log(arr) // ['a', , 'c']

以上代码,使用delete删除了数组的第二个元素,就在这个位置形成了一个空位。

循环遍历空位

空位是被当做一个正常的数组元素,并被计算在length属性里的,所以当我们遍历有空位的数组的时候,需要小心,不然会产生一些不必要的问题。

针对空位遍历时主要的一些注意点,总结如下:

  • for、while、for-of三个循环语句,都会正常遍历到空位,并输出空位的值为undefined。
  • for-in循环语句,则会直接跳过空位,不会遍历空位。
  • 数组的实例方法里面:
    • forEach、map、every、some、filter、reduce、flat,也都会直接跳过空位;
    • find、findIndex,则会读取到空位,返回undefined。
    • indexOf、lastIndexOf、includes,无法读取空位,因为空位啥也没有。
  • 当使用Object对象的keys、values、entries方法的时候,也是直接跳过空位。
const arr = [1, , , 4]
for (let i in arr) {
  console.log(i)
} // 跳过空位,输出为 '0' 和 '3'
for (let i of arr) {
  console.log(i)
} // 读取空位,输出为 1 undefined undefined 4

arr.forEach((vl) => {
  console.log(vl)
}) // 跳过空位,输出为 5 2

Object.keys(arr) // 也是跳过空位,输出为 ['0', '3']

以上代码,就是遍历时的部分示例,与上面总结的表现一致,for-of 能读取空位,其他如 for-inforEacheObject.keys 则直接跳过空位,不会遍历输出。

需要注意的是,空位在索引读取、或者部分语法访问的时候返回的undefined,但它并不等于undefined,空位所有的这些特点,undefined并没有。如果遍历的时候,undefined是数组元素的话,那它在任何情况下都能读取到。

数组空位的字符串输出

另外,当数组被当做字符串输出的时候,空位也有自己特有的输出。

  • 使用join() 和 toString() 方法的时候,空位元素输出只有一个逗号。
[1, , , 4].join() // '1,,,4'
[1, , , 4].toString() // '1,,,4'
  • 使用JSON.stringify() 方法的时候,空位输出为 null 字符串。
JSON.stringify([1, , , 4]) // '[1,null,null,4]'

in运算符

in运算符用于检查对象的某个属性键名是否存在,返回布尔值,数组属于对象,所以也能适用。
数组的键名就是索引值,我们使用数组索引值判断即可。

const arr = [1, 2, 3]
0 in arr // true
'2' in arr // true
3 in arr // false

以上代码,数组有三个元素,索引值0-2,而3并不属于、返回了false。
之前已有介绍对象键名是字符串,数组的索引值使用数字和字符串都没问题。

另外,就是对于数组中的空位,in运算符返回的是false:

const arr = [1, ,2]
0 in arr //true
1 in arr // false

以上代码,数组的第二个元素是空元素,那它使用 in 判断时,返回了false。

本文我们主要讲述的是数组的特性,从类型、数组判断、索引值、length属性、创建数组的方式、构造函数、空位、in运算符等几个方面对数组做了详细的介绍,着重于数组的特别之处。
通过这些内容知识,相信能够让我们更加深入的理解JavaScript中的数组,为我们写出更好的代码添砖加瓦。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK