超级面板
文章目录
最新文章
最近更新
文章分类
标签列表
文章归档

Array Like 对象

ArrayLike 对象

ArrayLike(类数组/伪数组) ,就是像数组的对象,常见的 ArrayLike 对象有:NodeListargumentsHTMLCollectionjQuery 对象甚至 String 等。

ArrayLike 实际上只需要一个硬性条件:有 length 属性即可:

var obj = {length: 3}

[].map.call(obj, item => item);
// [undefined x 4]

通过数组方法访问的话,只能访问到 0length-1 值为属性名的字段:

var obj = {
"0": 0,
"1": 1,
"2": 2,
length: 2
}

var arr = [].map.call(obj, item => item);
console.log(arr)
// [0, 1]

ArrayLike 最主要的特性就是它的结构和 Array 对象类似,可以使用数组的方法操作它们,可以更方便的自由扩展,不用担心污染到原生的 Array ,最典型的应用就是 jQuery 了, 如下使用 jQuery 选择器 $('div') 以后的结构:

$('div') = {
0: ..,
...
291: ...
context: document,
length: 292,
prevObject: jQuery.fn.init(1),
selector: "div",
__proto__: Object(0)
}

ArrayLike 的判断

这里直接拿来一下 lodash 的判断方法:

const MAX_SAFE_INTEGER = 9007199254740991

/**
* Checks if `value` is a valid array-like length.
*
* **Note:** This method is loosely based on
* [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength).
*
* @since 4.0.0
*/
function isLength(value) {
return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER
}

/**
* Checks if `value` is array-like. A value is considered array-like if it's
* not a function and has a `value.length` that's an integer greater than or
* equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.
*
* @since 4.0.0
*/
function isArrayLike(value) {
return value != null && typeof value != 'function' && isLength(value.length)
}

由于类数组对象并不是规范,所以不同项目的实现都有些微的差异。

ArrayLike 是否需要转换为 Array

基本上,类数组对象不需要特意转换为数组对象,因为数组的函数在实现的时候,并没有检测内部的 this 指针是否是数组,只需要有 length 属性和索引的元素访问即可,因此在类数组对象上也能使用,只需要使用 callapply 改变调用方式即可:

var arrLike = {0:"a", 1:"b", 2:"c", length:3};

Array.prototype.map.call(arrLike, item => item + item);
// ["aa", "bb", "cc"]

Array.prototype.filter.call(arrLike, item => item <= "a");
// ["a"]

Array-Like 转换为 Array

ES6 提供的 Array.from() 可以从一个类似数组或可迭代的对象中创建一个新的数组实例:

Array.from('foo');
// ["f", "o", "o"]

虽然 ES6 语法在最新的浏览器的普及度还是蛮高的,在一些老旧的浏览器下仍不支持,这个时候可以使用 Array.prototype.slice 将一个类数组对象或集合装换为数组:

const arrayLikeToArray = (collection) => Array.prototype.slice.call(collection);

// 或者
const arrayLikeToArray = (collection) => [].slice.call(collection);

第二种方法略有性能的损失,会新建一个数组,不过考虑到执行引擎的优化,性能损失可以忽略,反而更省事。

也可以使用 bind 来简化该过程:

var unboundSlice = Array.prototype.slice;
var slice = Function.prototype.call.bind(unboundSlice);

const arrayLikeToArray = collection => slice(collection);

但是 slice 方法在 IE < 9 以下浏览器还有问题,如果仍需要兼容该版本浏览器,需要做一个修改:

const arrayLikeToArray = collection => {
'use strict';
var _slice = Array.prototype.slice;

try {
// Can't be used with DOM elements in IE < 9
return _slice.call(document.documentElement);
} catch (e) { // Fails in IE < 9
var arr = [];
for (var i = 0, len = collection.length; i < len; i++) {
arr[i] = collection[i];
}
return arr;
}
}

参考