JavaScript 特别对象 Array-Like Objects 详解

这篇文章拖了有两周,本日来跟人人聊聊 JavaScript 中一类特别的对象 -> Array-Like Objects。

(本文节选自 underscore 源码解读系列文章,完整版请关注 https://github.com/hanzichi/underscore-analysis

Array-Like

JavaScript 中一切皆为对象,那末什么是 Array-Like Objects?望文生义,就是像数组的对象,固然,数组自身就是对象嘛!轻微有点基础的同砚,一定晓得 arguments 就是 Array-Like Objects 的一种,能像数组一样用 [] 去接见 arguments 的元素,有 length 属性,然则却不能用一些数组的要领,如 push,pop,等等。

那末,什么样的元素是 Array-Like Objects?我们来看看 underscore 中对其的定义。

var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
var getLength = property('length');
var isArrayLike = function(collection) {
  var length = getLength(collection);
  return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
};

很简朴,不是数组,然则有 length 属性,且属性值为非负 Number 范例即可。至于 length 属性的值,underscore 给出了一个上限值 MAX_ARRAY_INDEX,现实上是 MAX_SAFE_INTEGER(谢谢 @HangYang 同砚指出) ,因为这是 JavaScript 中能准确示意的最大数字。

想一想另有什么同时能满足以上前提的?NodeList,HTML Collections,细致想一想,以至另有字符串,或许具有 length 属性的对象,函数(length 属性值为形参数目),等等。

Array-Like to Array

有的时刻,需要将 Array-Like Objects 转为 Array 范例,使之能用数组的一些要领,一个异常简朴粗犷而且兼容性优越的要领是新建个数组,然后轮回存入数据。

我们以 arguments 为例。

function fn() {
  // Uncaught TypeError: arguments.push is not a function
  // arguments.push(4);

  var arr = [];
  for (var i = 0, len = arguments.length; i < len; i++)
    arr[i] = arguments[i];

  arr.push(4); // [1, 2, 3, 4]
}

fn(1, 2, 3);

然则这不是最文雅的,更文雅的解法人人肯建都晓得了,use Array.prototype.slice(IE9- 会有题目)。

function fn() {
  var arr = Array.prototype.slice.call(arguments);
  arr.push(4); // arr -> [1, 2, 3, 4]
}

fn(1, 2, 3);

或许能够用 [] 替代 Array.prototype 节约几个字节。

function fn() {
  var arr = [].slice.call(arguments);
  arr.push(4); // arr -> [1, 2, 3, 4]
}

fn(1, 2, 3);

假如非得寻求机能,用 [] 会新建个数组,机能一定不及前者,然则因为引擎的优化,这点差别基础能够忽略不计了(所以许多框架用的就是后者)。

为何如许能够转换?我们简朴了解下,重要的原因是 slice 要领只需要参数有 length 属性即可。起首,slice 要领获得的结果是一个 新的数组,经由过程 Array.prototype.slice.call 传入的参数(假设为 a),假如没有 length 属性,或许 length 属性值不是 Number 范例,或许为负,那末直接返回一个空数组,不然返回 a[0]-a[length-1] 构成的数组。(详细能够看下 v8 源码 https://github.com/v8/v8/blob/master/src/js/array.js#L621-L660

固然,ES6 供应了更轻便的要领。

var str = "helloworld";
var arr = Array.from(str); 
// ["h", "e", "l", "l", "o", "w", "o", "r", "l", "d"]

小结下,假如要把 Array-Like Objects 转为 Array,首选 Array.prototype.slice,然则因为 IE 下 Array.prototype.slice.call(nodes) 会抛出毛病(because a DOM NodeList is not a JavaScript object),所以兼容的写法以下。(但另有一点要注意的是,假如是 arguments 转为 Array,最好别用 Array.prototype.slice,V8 下会很慢,详细能够看下 防止修正和通报 arguments 给其他要领 — 影响优化

function nodeListToArray(nodes){
  var arr, length;

  try {
    // works in every browser except IE
    arr = [].slice.call(nodes);
    return arr;
  } catch(err){
    // slower, but works in IE
    arr = [];
    length = nodes.length;

    for(var i = 0; i < length; i++){
       arr.push(nodes[i]);
     }  

    return arr;
  }
} 

Others

许多时刻,某个要领你认为吸收的参数是数组,实在类数组也是能够的。

Function.prototype.apply() 函数吸收的第二个参数,实在也能够是类数组。

var obj = {0: 4, length: 2};
var arr = [1, 2, 3];
Array.prototype.push.apply(arr, obj);
console.log(arr); // [1, 2, 3, 4, undefined]

Read More

    原文作者:fish
    原文地址: https://segmentfault.com/a/1190000005800697
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞