前言:
在开发或者面试中,我们经常会遇到或者被问到关于“类数组”的问题,什么是类数组?什么是类数组对象?js里面哪些可以称为类数组?类数组又有哪些特点?怎么把类数组转成js数组?那么既然这个问题被反复问到,以及在我们的编码过程中难免会遇到类数组,那我们应该怎么去操作类数组呢,类数组又隐含着多少知识点呢下面我们开始探讨下,可能自己收集的不是那么全面,并且有什么不对的地方也希望被指出,一起学习一起进步
1. 想想js里面哪些可以称为类数组?
首先我也列出来(部分):字符串,arguments, document.getElementByClassName获取到的元素集合等...
接下来是几张上面列出来的类数组的在控制台打印出来的截图:
在上面几张截图里面都是被可以被称为类数组几种类型,那他们有什么共同特点呢,什么样的才能被称为类数组
2.类数组的定义 (ArrayLike)
其实在网上找了很多资料也没有找到,关于js里面类数组的官方定义,但是这不代表我们就不能定义一种具有特定结构的数据类型的叫法或者昵称
根据网上以及自己理解,总结下来满足具有以下两点特性的结构类型我们称之为“类数组”或者有些叫法也叫“伪数组”,what is this in English? ArrayLike.
- 拥有length属性,其它属性(索引)为非负整数
- 不具有数组所具有的方法(非必须)
上面的定义是非官方的,但是很具有参考性,一般情况下在网络上的资料以及我们在使用或者判定过程中都是遵循这样的一些约定俗成的定义。
再回头看上面的截图可以看出上面列出的几种数据类型,都是满足上面的条件。
3.如何判断给定的类型是否是类数组(类数组对象)?
我们可以根据关于类数组的定义来判断一个给定的类型是不是类数组
function isArrayLike(arrlike) {
return typeof arrlike !== "undefined" && arrlike.length > 0;
}
这是我自己编写的判断逻辑,来我们看看著名的类库“鲁大师”(lodash)是怎么做的,
var MAX_SAFE_INTEGER = 9007199254740991;
function isLength(value) {
return typeof value == 'number' &&
value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
}
function isArrayLike(value) {
return value != null && isLength(value.length) && !isFunction(value);
}
比较一下,比我自己写的要严谨,虽然在我们的业务场景下不会遇到这么大的值,但是终究还是严谨。
上面是判断类数组,下面在提供一下判断类数组对象的方法,同样来自于“鲁大师”
function isObjectLike(value) {
return value != null && typeof value == 'object';
}
function isArrayLikeObject(value) {
return isObjectLike(value) && isArrayLike(value);
}
其实就是在原来类数组的判断上加了关于对象的判断。
4.类数组转成数组
顾名思义,之所以我称之为类数组,肯定是有一定原因的不仅是“长得像”(有人还说我长得像雨神, 然后我就喜欢听雨神的歌了),
首先我们看一下类数组的遍历和数组的遍历比较:
let arr = ["a", "b", "c"],
arrLike = {
0: "a",
1: "b",
2: "c",
length: 3
},
newArr = [],
newArrLikeArr = [];
for (let i = 0, j = arr.length; i < j; i++ ) {
newArr.push(arr[i]);
}
console.log(newArr); // ["a", "b", "c"]
for (let i = 0, j = arrLike.length; i < j; i++ ) {
newArrLikeArr.push(arrLike[i]);
}
console.log(newArrLikeArr); // ["a", "b", "c"]
可以看出除了变量的,命名不一样的以外,好像别的什么都一样,果然是“长得像”,既然长得这么像,但是类数组看起来好像是很单薄的,因为他身上只有属性,没有像数组一样可以通过一些方法来操作里面的属性以及值(这时我们把数组的索引值以及元素当做键值对),其实从上面的对比我们可大胆的猜一下,如果数组的方法内部是通过取length
值以及对象取值的形式arr["1"]
来做操作的,是不是我们就可以让类数组去调用数组上面的方法来达到操作自身的目的呢?
既然上面都是我们的猜测,那我们看下数组方面的方法内部实现,是不是如我们所想,虽然我们看不到数组原生方法的源码,但是我们可以从开源的polyfill
上面找到我们要的答案:
Array.prototype.find = function (predicate, thisValue) {
var arr = Object(this);
if (typeof predicate !== 'function') {
throw new TypeError();
}
for(var i=0; i < arr.length; i++) {
if (i in arr) {
var elem = arr[i];
if (predicate.call(thisValue, elem, i, arr)) {
return elem;
}
}
}
return undefined;
}
正如我们所想,看上面代码,如果我们操作的是类数组对象,同样能达到相同的效果。
那么我们就可以让类数组对象借用数组上面的方法了,下面这段代码是不是很熟悉呢:
function Foo() {
var args = [].slice.call(arguments);
.... // something do
}
在es6
之前我们做一个函数柯里化
或者写一些不定参数的函数或者根据参数个数来实现面向对象编程里面的重载
方法的时候,会经常用到这样的写法。当然在es6
出来以后,我们有了一种更为简单的方获取参数:
function foo(...arguments) {
console.log(arguments);
}
foo(1, 2, 3); // [1, 2, 3]
出了上面非常常用的一种方法来实现类数组对象转为数组的方式,我们还有另外一种:
Array.prototype.splice.call({
length: 2,
0: "a",
1: "b"
}, 0); // ["a", "b"]
上面只是了类数组怎么转为数组,既然上面已经讨论了,类数组与数组如此的“长得像”,那么有些情况下,我们在操作类数组的时候,是不是不用先转为数组,而是直接借用
数组的方法来操作类数组对象呢。答案是肯定的,那么是不是数组上面所有的方法,类数组对象都可以借用的?或者我们在编码过程中怎么去实现一个类数组对象?jQuery的类数组对象是怎么构造的呢?又和我们平时创建的类数组对象有什么不同呢?带着这些疑问我们一起继续探讨“js类数组更深层的秘密”。
请移步 解开那一层面纱,js类数组的小秘密(下篇)