在 JavaScript 中有种称作为类数组(ArrayLike)的概念常常使得开发者困惑。类数组像数组但是却没有数组的方法。我举个例子:
const arr = ["张三","李四"]
访问数组的第一个元素只需要:
console.log(arr [0])
当然,我们也可以将数组改写成对象格式:
const obj = {
0:"张三",
1:"李四"
}
访问张三只需要调用obj[0]即可,当对象使用数字作为key时,效果和数组完全一样。数组中有个length属性,用来表明数组有多少个元素,如果我们在上述对象中也添加一个 length 属性,这样这个对象就长得像数组了。
// 类数组
const obj = {
0:"张三",
1:"李四",
length:2
}
类数组需要符合两个条件:
- 使用数字作为
key。- 需要具备
length属性
字符串也是一种类数组,因为它也满足上述条件:
const str ="abc"
console.log(str.length) // 3
console.log(str[0]) // a
类数组的遍历一样可以使用for来遍历:
const str = 'abcd'
const arr = ['a','b','c','d']
const obj = {
0:'a',
1:'b',
2:'c',
3:'d',
length:4
}
for(let i=0;i<obj.length;i++){
console.log(obj[i])
}
但是为什么需要类数组这种数据结构呢?大多数文章并没有很好地解释。下面说说我个人的理解。
JavaScript 是基于对象设计的语言,其实本质上字符串和数组都是属于对象类型。数组只是在对象基础上实现的一种特殊的对象。这种对象具有特有的性质,例如 push 方法往尾部加入一个元素.
数组 push 方法内部实现细节:
Array.prototype.push = function(value){
this[this.length] = value
this.length++
}
push 时候其本质也是往对象上添加了一个数字类型的 key,并将 length 属性值加上 1。 push方法其实完全可以在对象上一样适用:
const obj = {
0:'a',
1:'b',
2:'c',
3:'d',
length:4,
push(value){
this[this.length] = value
this.length++
}
}
理论上可以将数组才具有的push,slice 等方法都加到类数组上面.但这样我们为什么还需要类数组?直接使用数组不就可以了。类数组的作用其实本意就是只设计一个有序和可遍历的对象。其余数组中拥有的方法自己完全不需要。
const obj = {
0:'a',
1:'b',
2:'c',
3:'d',
length:4
}
for(let i =0;i<obj.length;i++){
console.log(obj[i])
}
下面看一个经典的类数组 arguments 对象
function sum(a,b){
console.log(arguments)
}
sum(1,2)
arguments 就是一个类数组
由于函数的参数个数是外部传入的,传入的时候已经确认好了。在函数内部再去改变 arguments 是让人难以接受的。
function sum(a,b){
arguments.push(3) // 如果为数组容易造成混乱
}
sum(1,2)
所以类数组对象的使用更多是只让你遍历和访问下标,而不是去添加或删除元素。形象一点说就是有些对象虽然需要数组的一些特征,但是如果完全使用数组呢,数组原型上的一些push,slice等方法的存在又会显得画蛇添足,甚至还会危害自身安全,所以这种对象类数组对象。
不过在开发时候还是需要对类数组中数据进行过滤(filter)或者映射(map)等操作,所以就有很多类数组转数组的方法了。
1. Array.from
function sum(a,b){
const arr = Array.from(arguments) // ES6 类数组转化成数组
console.log(arr) // [1,2]
}
sum(1,2)
2. call 方法
function sum(a,b){
const arr= Array.prototype.filter.call(arguments,(value)=>{
return value >1
}) // 不算是类数组转化成数组方法,只是改变了数组filter执行环境
console.log(arr) // [2]
// 还可以使用数组 slice 方法
const arr2 = Array.prototype.slice.call(arguments)
}
sum(1,2)
其实类数组转数组这种说法并不严谨。并不是类数组能力上的缺陷一定要转成数组,对于简单的取索引和遍历完全不需要转成数组,转成数组也只是类似于深拷贝了一份,原先的类数组还是原先的类数组!
(完)
欢迎访问 骚俊的个人博客