类数组定义
类数组其实不是数组,而是一个类似数组的对象。一个拥有 length 属性和若干索引属性的对象就可以被称为类数组对象。
即一个类数组对象应当符合以下两点:
- 使用数字作为属性名称
- 需要具备length属性
常见的类数组对象有 arguments
和 DOM方法返回结果
下面变量 arrayLike 就是一个类数组对象。
const arrayLike = {
0:'张三',
1:'李四',
length:2
}
访问张三只需要:
arrayLike[0]
由于类数组对象length
属性声明了对象有多少个属性,所以可以使用for
,while
, do while
遍历对象属性:
const arrayLike = {
0:'张三',
1:'李四',
length:2
}
// for
for(let i = 0;i < arrayLike.length; i++) {
console.log(arrayLike[i])
}
// while
...
// do while
...
字符串也是一种类数组,因为它也满足上述条件:
const str ="abc"
console.log(str.length) // 3
console.log(str[0]) // a
为什么设计类数组?
但是为什么需要类数组这种数据结构呢?下面说说我个人的理解。
JavaScript 是基于对象设计的语言,本质上字符串和数组都是属于对象类型。简单来说,数组就是一种特殊的对象:
const arr = ['张三','李四']
// 完全可以改写成对象方式
const arr = {
0:'张三',
1:'李四',
length:2
}
数组特殊在于其可以通过索引来访问值,还具有 push
, shift
等特有的方法。
数组 push 方法内部实现细节:
Array.prototype.push = function(value){
this[this.length] = value
// this.length++ 此处length值会在上局语句执行完毕后自增+1
}
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 等方法都加到类数组上面。但是这样为什么不直接使用数组就好了。其实恰恰相反,类数组不需要数组的那些方法,有了反而会画蛇添足。
下面看一个经典的类数组 arguments 对象
function sum(a,b){
console.log(arguments)
}
sum(1,2)
arguments 就是一个类数组
由于函数的参数个数是外部传入的,传入的时候已经确认好了。如果 arguments 也有 push方法,那么arguments 就容易被开发者随意篡改。
function sum(a,b){
arguments.push(3) // 如果为数组容易造成混乱
}
sum(1,2)
所以类数组对象的设计目的更多是只让你遍历和访问下标,而不是去添加或删除元素。当你需要设计一个方法,这个方法需要返回一个数组,但是又不想让这个数组有 push 等可能会修改这个数组的方法,这时候就可以考虑返回一个类数组对象了。
类数组转数组
不过在开发时候有时需要对类数组中数据进行过滤(filter)或者映射(map)等操作,所以就有很多类数组转数组的方法了。
1、Array.from
Array.from(arguments) // ES6 类数组转化成数组
2、es6 展开运算符(…)
function sum(a,b){
console.log([...arguments]) // [1,2]
}
sum(1,2)
注:经测试,解构可对arguments
, DOM方法返回结果
起作用。对自建的类数组(如上文arrayLike)无效,会报错:arrayLike is not iterable
3、call,apply 方法
通过调用数组的 slice
方法来实现转换
Array.prototype.slice.call(arguments);
Array.prototype.slice.apply(arguments);
通过调用数组的 concat
方法来实现转换
Array.prototype.concat.apply([], arguments);
通过调用数组的 splice
方法来实现转换,此方法会清空arguments本身,并返回数组结构的参数
Array.prototype.splice.call(arguments, 0);
Array.prototype.splice.apply(arguments, [0]);
注: 此方法无法对DOM方法返回结果
进行操作,会报:Failed to delete an indexed property from 'NodeList': Index property deleter is not supported.
4、改变了数组方法执行环境
function sum(a,b){
const arr1 = Array.prototype.filter.call(arguments, value => value > 1)
const arr2 = Array.prototype.filter.apply(arguments, [value => value > 1])
console.log(arr1, arr2) // [2] [2]
}
sum(1,2)
其实类数组转数组这种说法并不严谨。并不是类数组能力上的缺陷一定要转成数组,对于简单的取索引和遍历完全不需要转成数组,转成数组也只是类似于深拷贝了一份,原先的类数组还是原先的类数组!