一文搞懂JavaScript数组的特性
<h2 id="前言">前言</h2><p>数组是几乎所有编程语言的基础语法,JavaScript因为语法特性,之前缺少一些集合类对象,对数组的使用就会更多一些,因此我们更需要理解数组知识。<br>
然而大部分人对数组都已经非常熟悉了,所以本文将不会介绍数组的基础语法和用法,而是从JavaScript中数组的一些特殊之处入手,通过这些少有特性的详细介绍,加深我们对数组的理解。</p>
<h2 id="基本介绍">基本介绍</h2>
<p>首先,作为开始,我们还是需要简单介绍下JavaScript中的数组,基本如下:</p>
<ul>
<li>有序的数据集合,索引值从0开始递增</li>
<li>拥有length长度属性</li>
<li>数组元素值可以是JavaScript中的任何类型</li>
<li>是动态的,可以增减元素</li>
<li>可以循环数组元素,拥有一系列可操作的实例方法</li>
<li>支持元素为数组的多维数组</li>
<li>读取元素以<code>数组名[索引值]</code>的方式表示</li>
</ul>
<p>以上就是对数组的基础介绍,大部分都很熟悉,接下来,我们就来看看数组的一些特殊之处,</p>
<h2 id="数组类型和判断">数组类型和判断</h2>
<p>在JavaScript中,是没有数组这种数据类型的,所以数组本质上是一种特殊的对象,它的类型值会返回 <code>object</code>,如下所示:</p>
<pre><code class="language-js">typeof [] // 'object'
</code></pre>
<p>由于返回的是 <code>object</code> 类型,就无法通过 <code>typeof</code> 语法判断一个值或对象是否是数组,得使用其他方式,才能正确的判断数组对象。</p>
<blockquote>
<p>typeof的详细知识,可见博文 typeof详解。</p>
</blockquote>
<h3 id="判断为数组的方式">判断为数组的方式</h3>
<p>判断一个对象是否为数组,有不少种方式,但我们一般使用较多也就两三种,而其中最正确有效的方式就两种。</p>
<h4 id="正确判断方式">正确判断方式</h4>
<p>这两种正确有效判断数组类型的方式如下:</p>
<ul>
<li>Array.isArray()<br>
ES6推出的语法,专门用于判断对象类型是否为数组,是则返回true,否则返回false,简单好用。</li>
<li>Object.prototype.toString.call()<br>
在ES6推出之前的JS语法中,一般使用这种方式来判断数组,除了数组,它可以准确判断出其他几乎所有的JS数据类型。</li>
</ul>
<pre><code class="language-js">Array.isArray() // true
Object.prototype.toString.call([]) // ''
Object.prototype.toString.call({}) // ''
</code></pre>
<p>以上代码,就是这两种有效方式的示例,都能准确有效的判断。<br>
当前的前端开发种,ES6语法基本普及的情况下,使用 <code>Array.isArray()</code> 将更方便。</p>
<h4 id="其他方式介绍">其他方式介绍</h4>
<p>除以上两种以外,还有其他几种基于原型链上的判断方式,可用于判断数组,但这些方式都不够准确:</p>
<ul>
<li>[] instanceof Array<br>
这里使用instanceof运算符表示给定值是否是数组的实例。</li>
<li>[].constructor === Array<br>
给定值的实例构造函数是否是数组。</li>
<li>Array.prototype.isPrototypeOf([])<br>
给定值的原型链上是否存在数组。</li>
<li>Object.getPrototypeOf([]) === Array.prototype<br>
给定值的原型对象是否等于数组的原型对象。</li>
</ul>
<p>这几种方式本质上都比较类似,只不过由于原型链能够被修改,所以这几种方式并不推荐使用。<br>
如使用 <code>instanceof</code> 判断的方式:</p>
<pre><code class="language-js">[] instanceof Array // true
[] instanceof Object // true
</code></pre>
<p>以上代码,使用 <code>instanceof</code> 运算符时,一个数组实例属于 <code>Array</code> 和 <code>Object</code>,都是成立的,因为Object在Array的原型链上。</p>
<h2 id="数组索引值和长度">数组索引值和长度</h2>
<p>数组通过下标索引值进行元素值的读取,必须要使用方括号才可以,否则无法读取元素值。</p>
<pre><code class="language-js">const arr =
arr // 2
arr.1 // Uncaught SyntaxError: Unexpected number
</code></pre>
<p>以上代码,使用 <code>arr.1</code> 的写法,就报了语法错误,因为JavaScript中单独数字作为标识符是不合法的。关于错误类型,可见博文js中的Error错误类型。</p>
<p>前面提到,数组是一种特殊的对象,而object对象,是可以通过键名来读取元素的,只不过数组的键名只能是数字,所以当做标识符读取时报错。而object对象如果使用数字作键名时,也无法通过标识符来读取:</p>
<pre><code class="language-js">const obj = { 1: 'hello' }
obj.1 // Uncaught SyntaxError: Unexpected number
</code></pre>
<p>如上代码所示,对象使用 <code>obj.1</code> 的方式读取属性,也是报同样的错误。</p>
<h3 id="索引值是字符串">索引值是字符串</h3>
<p>数组使用方括号读取元素值,而Object对象也能通过这样的方法读取属性值,这样就算对象的键名是数字也能正常读取了:</p>
<pre><code class="language-js">const obj = { 1: 'hello', key: 'world' }
obj // 'hello'
obj['key'] // 'world'
</code></pre>
<p>事实上,JavaScript中Object对象的键名均为字符串类型,而数组的类型又是object,所以它的索引值(键名)也可以使用字符串。</p>
<pre><code class="language-js">const arr =
arr['1'] // 2
arr['2'] // 3
</code></pre>
<p>以上代码,当使用 '1'、'2' 等字符串时,也能正确读取数组的元素。</p>
<p>但需要注意的是,数组的索引值,必须是能自动转成正整数数字的值。如果是其他数值的时候,则需要注意。</p>
<h4 id="索引值为小数负数">索引值为小数、负数</h4>
<p>如果数组使用小数或者负数读写操作时,数组是什么一种表现呢,可以看下面的代码:</p>
<pre><code class="language-js">const arr =
arr[-1] = 0
arr = 5
arr = 8
Array.isArray(arr) // true
arr //
arr.length // 3
arr[-1] // 0
arr // 0
</code></pre>
<p>以上代码所示:</p>
<ul>
<li>使用小数 <code>2.0</code>,能自动转换成正整数,所以可以作为数组的第三个元素;</li>
<li>使用负数 <code>-1</code> 和无法自动转换成正整数的小数 <code>3.6</code>,这两种情况都作为了数组的键值对的方式成为了数组的属性,但并不被包含在数组元素中,因为数组的length属性为3,并不包含这两个值。</li>
<li>负数 <code>-1</code> 和小数 <code>3.6</code>,都被当作字符串在使用,同理,也可以使用 <code>arr = 50</code>,这里的bool值 <code>true</code>,也被当做了字符串 'true'。</li>
</ul>
<p>所以,负数或小数不能作为数组的索引值,但可以被当做键值对的方式,作为数组的属性被读写。</p>
<h4 id="索引值是字符串等其他类型时">索引值是字符串等其他类型时</h4>
<p>如果我们给数组使用字符串、布尔值等其他类型的值作为下标索引时,这个时候和小数负数的表现类似。<br>
就是把数组用作了对象,这些类型的下标索引则都被当做了数组对象的属性在操作,可以正常读写,但不是数组的元素,不计入数组的length长度中。</p>
<pre><code class="language-js">const arr =
arr['key'] = 0
arr['value'] = '是的'
arr.length // 1
arr //
</code></pre>
<p>以上代码,就体现了数组属于对象类型的特点,可以增加键值对的属性。</p>
<h4 id="索引与属性">索引与属性</h4>
<p>前文已经多次提到,数组是一种特殊类型的对象,而数组的这些特别之处很多都与对象有关。<br>
另外,我们还需要知道的是,数组的索引虽然可以像对象一样,当做键名字符串使用,但它们还是有所区别的。<br>
数组是按照数字的顺序进行排列的集合,而对象的属性名则是无序的。所以我们使用特殊键名(如字符串、负数等)给数组赋值时,这些值都不是数组的元素,而是当做了对象的属性。</p>
<h3 id="length属性">length属性</h3>
<p>length是数组的长度属性,表示数组元素的个数,但请注意,它不是只读,而是可写的,即我们可以给数组的length属性赋值:</p>
<pre><code class="language-js">[].length = 3
// 数组的长度被赋予为3
</code></pre>
<p>但有几点要注意:</p>
<ul>
<li>如果设置数组的length值小于元素个数,则数组会自动删除所有大于length值的元素</li>
<li>如果设置数组的length值大于元素个数,则数组元素个数会自动增加到length值,并且新增的元素都是空位(返回undefined)</li>
<li>如果将数组的length属性设置为0,则会清空整个数组,变为空数组</li>
<li>数组的length值必须为正整数(或可以自动转换成正整数的值),其他值都是不合法的,设置不合法值时会报错。
<ul>
<li>能自动转换成正整数的值包含 <code>2.0</code>、<code>''</code>、<code>true</code>、<code>false</code>、<code>'10'</code> 等等这类</li>
</ul>
</li>
</ul>
<pre><code class="language-js">const arr =
arr.length = true
console.log(arr) //
arr.length = -1 // VM307:1 Uncaught RangeError: Invalid array length
</code></pre>
<p>以上代码示例,<br>
修改了数组的length值为true,true可以转换成1,数组的长度就变为了1,元素被删除了一个;<br>
当设置数组长度为负数的时候,报错,无效的数组长度。</p>
<p>另外,除了以上几种情况,还有我们需要知道的一点就是,数组有最大长度。</p>
<h3 id="数组的最大长度">数组的最大长度</h3>
<p>JavaScript中的数组能赋予的最大长度是32位的正整数,即 <code>2**32 - 1</code> = 4294967295,从0计算,长度减1。<br>
当我们给数组设置的长度超过最大长度的值时,也会报无效长度的错误:</p>
<pre><code class="language-js">[].length = 4294967296 // Uncaught RangeError: Invalid array length
</code></pre>
<p>但如果我们使用超过最大长度的数字作为键名给数组赋值的话,则仍然可以使用,如下代码所示:</p>
<pre><code class="language-js">const arr = []
arr = 1
arr //
</code></pre>
<p>前面也有介绍过,这是由于数组本质上是个对象,当使用超出最大长度范围的数字时,这个数字会被当做数组一个属性的键名,并且可以自动转成字符串,这个时候它并不数组的元素,也不计算在数组的length属性里,和前文介绍索引值时一样。</p>
<h2 id="创建数组的三种方式">创建数组的三种方式</h2>
<p>当前JS中,创建数组大致有三种方式:数组字面量语法、Array构造函数、Array.of()。</p>
<h3 id="数组字面量语法">数组字面量语法</h3>
<p>字面量语法创建数组,是JavaScript中最常用的一种方式,由于方便简单,大部分代码都会使用它。</p>
<pre><code class="language-js">const arr1 = [] // 创建一个空数组
const arr2 = // 给定数组的元素
console.log(arr2) //
</code></pre>
<p>以上代码,就是使用了字面量语法创建数组,如果不给元素值就会创建一个空数组。<br>
需要了解的是,数组字面量语法其实也是基于对Array构造函数的一种简化使用。</p>
<h3 id="array构造函数">Array构造函数</h3>
<p>Array对象本身是JavaScript的一个内置对象,它是 <code>function</code> 类型,能当做构造函数使用,这与大多数内置对象一样,是否使用 <code>new</code> 操作符都可以。<br>
因此,我们通过调用 <code>Array()</code> 或 <code>new Array()</code>,就能创建数组对象。数组构造函数可以使用多种形式的可选参数,只不过依据参数不同,结果会有一定差异。</p>
<ol>
<li><strong>不传参数时,返回一个空数组</strong></li>
</ol>
<pre><code class="language-js">Array() // []
</code></pre>
<ol start="2">
<li><strong>只传入一个参数时,表现不太一致</strong></li>
</ol>
<ul>
<li>当参数为0时,创建一个空数组</li>
</ul>
<pre><code class="language-js">Array(0) // []
</code></pre>
<ul>
<li>当参数是单个正整数时,将创建一个length长度为该参数数值的数组,并且所有数组元素都是空位(返回undefined)</li>
</ul>
<pre><code class="language-js">const arr = new Array(10)
arr.length // 10
arr // undefined
</code></pre>
<ul>
<li>当参数是非正整数,如负数、小数等等,则会报错,无法创建数组</li>
</ul>
<pre><code class="language-js">Array(10.6) // Uncaught RangeError: Invalid array length
Array(-6) // Uncaught RangeError: Invalid array length
</code></pre>
<p>以上代码,小数和负数,都报无效数组长度的错误。</p>
<p>但是,对于能自动转换成整数的数字(小数位为0),则可以正常创建数组,如下代码所示:</p>
<pre><code class="language-js">const arr = new Array(10.0)
arr.length // 10
arr // undefined
</code></pre>
<ul>
<li>当参数是非数字的其他类型,如字符串值、布尔值、对象、数组、函数、null、undefined等等,则该参数将会成为新数组的第一个元素,数组的length长度自然就是1。</li>
</ul>
<pre><code class="language-js">Array('hello') // ['hello']
Array(true) //
Array() //
Array(null) //
</code></pre>
<ul>
<li>只有一个参数的情况下,如果参数里多了尾逗号,则会忽略,仍能正常创建数组:</li>
</ul>
<pre><code class="language-js">Array(0,) // []
Array(true,) //
</code></pre>
<ol start="3">
<li><strong>传入多个参数时</strong></li>
</ol>
<p>所有参数都会成为新数组的元素,不论参数是什么类型的值。</p>
<pre><code class="language-js">Array(1, 2) //
Array(1, 2, true, null, 'hello')//
Array(-1, 2.2, 3) // [-1, 2.2, 3]
Array(1, 2, 3,) //
</code></pre>
<p>以上代码,<br>
第三行,加了负数和小数,也能正常创建数组;<br>
第四行,参数里多了一个尾逗号,但会被忽略。</p>
<h3 id="arrayof">Array.of()</h3>
<p>Array.of(item…) 是ES6提供的一个数组静态方法,使用它同样可以创建数组。</p>
<p>该方法也提供了可选参数,但与Array构造函数的区别在于:<code>Array.of()</code> 的行为更一致,不论它的参数有几个、是什么类型,都会被当做新数组的元素成员,当然如果不传入参数,则会生成空数组。包括负数和小数,也都会被当成元素,不会报错。<br>
比如,<code>Array.of(5)</code> 会创建只有一个成员(5)且长度为1的数组,而 <code>Array(5)</code> 则是创建一个长度为5且元素皆是空位的数组。</p>
<p>所以,Array.of方法弥补了Array构造函数参数差异导致的不足,除了字面量语法外,我们应该尽量用于替代Array构造函数。</p>
<pre><code class="language-js">Array.of() // []
Array.of(5) //
Array.of(1, 2, true, null, 'hello') //
Array.of(-1) // [-1]
Array.of(2.2, 0) //
</code></pre>
<p>从以上代码可以看出,当我们使用Array.of方法时,所有参数都作为了数组的元素,这可以完全解决Array构造函数带来的不一致问题。</p>
<h2 id="空位空元素">空位(空元素)</h2>
<p>空位是指数组中某个<strong>逗号前面</strong>,没有任何值,是空的,这样的元素就是空元素,又叫空位。<br>
空位在JavaScript的数组中是允许存在的,并不会报语法错误或其他异常,数组能正常使用,所以我们需要了解它的一些特性。</p>
<blockquote>
<p>逗号后面没有值,不产生空位,也不影响数组。</p>
</blockquote>
<p>我们先看一个空位的示例:</p>
<pre><code class="language-js">const arr1 = [,]
const arr2 =
</code></pre>
<p>以上代码,就是空位在数组中的表现,其中arr1有1个空位,arr2在首尾两个元素中间有2个空位。</p>
<p>空位作为数组的一种特殊存在,它的一些基本表现如下:</p>
<ul>
<li>空位是数组的一个正常元素</li>
<li>空位会被计算进数组的长度里,即length属性会包含空位</li>
<li>空位可以通过索引读取,返回undefined。</li>
</ul>
<pre><code class="language-js">const arr =
arr.length // 4
arr // undefined
</code></pre>
<p>以上代码,定义了有2个空位的数组,数组长度不受影响,是4,读取空位值时返回undefined。</p>
<p>除了以上这些特点以外,空位还有其他一些需要注意的地方。</p>
<h3 id="delete">delete</h3>
<p>delete运算符能够删除对象的属性,那么同样可在数组中使用它。<br>
使用delete操作符删除数组元素的时候,就会产生空位,元素表现也符合空位的特点。</p>
<pre><code class="language-js">const arr = ['a', 'b', 'c']
delete arr
console.log(arr) // ['a', , 'c']
</code></pre>
<p>以上代码,使用delete删除了数组的第二个元素,就在这个位置形成了一个空位。</p>
<h3 id="循环遍历空位">循环遍历空位</h3>
<p>空位是被当做一个正常的数组元素,并被计算在length属性里的,所以当我们遍历有空位的数组的时候,需要小心,不然会产生一些不必要的问题。</p>
<p>针对空位遍历时主要的一些注意点,总结如下:</p>
<ul>
<li>for、while、for-of三个循环语句,都会正常遍历到空位,并输出空位的值为undefined。</li>
<li>for-in循环语句,则会直接跳过空位,不会遍历空位。</li>
<li>数组的实例方法里面:
<ul>
<li>forEach、map、every、some、filter、reduce、flat,也都会直接跳过空位;</li>
<li>find、findIndex,则会读取到空位,返回undefined。</li>
<li>indexOf、lastIndexOf、includes,无法读取空位,因为空位啥也没有。</li>
</ul>
</li>
<li>当使用Object对象的keys、values、entries方法的时候,也是直接跳过空位。</li>
</ul>
<pre><code class="language-js">const arr =
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']
</code></pre>
<p>以上代码,就是遍历时的部分示例,与上面总结的表现一致,<code>for-of</code> 能读取空位,其他如 <code>for-in</code>、<code>forEache</code>、<code>Object.keys</code> 则直接跳过空位,不会遍历输出。</p>
<blockquote>
<p>需要注意的是,空位在索引读取、或者部分语法访问的时候返回的undefined,但它并不等于undefined,空位所有的这些特点,undefined并没有。如果遍历的时候,undefined是数组元素的话,那它在任何情况下都能读取到。</p>
</blockquote>
<h3 id="数组空位的字符串输出">数组空位的字符串输出</h3>
<p>另外,当数组被当做字符串输出的时候,空位也有自己特有的输出。</p>
<ul>
<li>使用join() 和 toString() 方法的时候,空位元素输出只有一个逗号。</li>
</ul>
<pre><code class="language-js">.join() // '1,,,4'
.toString() // '1,,,4'
</code></pre>
<ul>
<li>使用JSON.stringify() 方法的时候,空位输出为 <code>null</code> 字符串。</li>
</ul>
<pre><code class="language-js">JSON.stringify() // ''
</code></pre>
<h2 id="in运算符">in运算符</h2>
<p>in运算符用于检查对象的某个属性键名是否存在,返回布尔值,数组属于对象,所以也能适用。<br>
数组的键名就是索引值,我们使用数组索引值判断即可。</p>
<pre><code class="language-js">const arr =
0 in arr // true
'2' in arr // true
3 in arr // false
</code></pre>
<p>以上代码,数组有三个元素,索引值0-2,而3并不属于、返回了false。<br>
之前已有介绍对象键名是字符串,数组的索引值使用数字和字符串都没问题。</p>
<p>另外,就是对于数组中的空位,in运算符返回的是false:</p>
<pre><code class="language-js">const arr =
0 in arr //true
1 in arr // false
</code></pre>
<p>以上代码,数组的第二个元素是空元素,那它使用 <code>in</code> 判断时,返回了false。</p>
<h2 id="总结">总结</h2>
<p>本文我们主要讲述的是数组的特性,从类型、数组判断、索引值、length属性、创建数组的方式、构造函数、空位、in运算符等几个方面对数组做了详细的介绍,着重于数组的特别之处。<br>
通过这些内容知识,相信能够让我们更加深入的理解JavaScript中的数组,为我们写出更好的代码添砖加瓦。</p><br><br>
来源:https://www.cnblogs.com/jimojianghu/p/17292277.html
頁:
[1]