这篇文章拖了有两周,今天来跟大家聊聊 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

JavaScript 特殊对象 Array-Like Objects 详解的更多相关文章

  1. JavaScript原生对象属性和方法详解——Array对象

    http://www.feeldesignstudio.com/2013/09/native-javascript-object-properties-and-methods-array/ lengt ...

  2. JavaScript引用类型之Array类型API详解

    Array类型也是ECMASCRIPT中最常见的数据类型,而且数据的每一项可以保存任何类型的数值,而且数组的大小是可以动态调整的,可以随着数据的添加自动增长以容纳新的数据.下面,总结数据的一些常用方法 ...

  3. javascript 日期对象(date)详解

    Date 对象 Date 对象用于处理日期和时间. 创建 Date 对象的语法: var myDate=new Date(); 注释:Date 对象会自动把当前日期和时间保存为其初始值. 1.date ...

  4. javascript - DOM对象控制HTML元素详解

    1.方法   getElementsByName() -- 获取name getElementByTagName() -- 获取  getAttribute()         --获取元素属性 se ...

  5. web前端学习(四)JavaScript学习笔记部分(7)-- JavaScript DOM对象控制HTML元素详解

    1.方法 getElementsByName() 获取name 可以获取一个数组类型数据(参数加引号) getElementsByTagName() 获取元素   getAttribute() 获取元 ...

  6. JavaScript对象的property属性详解

    JavaScript对象的property属性详解:https://www.jb51.net/article/48594.htm JS原型与原型链终极详解_proto_.prototype及const ...

  7. 转: javascript模块加载框架seajs详解

    javascript模块加载框架seajs详解 SeaJS是一个遵循commonJS规范的javascript模块加载框架,可以实现javascript的模块化开发和模块化加载(模块可按需加载或全部加 ...

  8. js对象浅拷贝和深拷贝详解

    js对象浅拷贝和深拷贝详解 作者:i10630226 字体:[增加 减小] 类型:转载 时间:2016-09-05我要评论 这篇文章主要为大家详细介绍了JavaScript对象的浅拷贝和深拷贝代码,具 ...

  9. JavaScript调试技巧之console.log()详解

    JavaScript调试技巧之console.log()详解 对于JavaScript程序的调试,相比于alert(),使用console.log()是一种更好的方式,原因在于:alert()函数会阻 ...

  10. Dream------scala--类的属性和对象私有字段实战详解

    Scala类的属性和对象私有字段实战详解 一.类的属性 scala类的属性跟java有比较大的不同,需要注意的是对象的私有(private)字段 1.私有字段:字段必须初始化(当然即使不是私有字段也要 ...

随机推荐

  1. JS+HTML5的Canvas画图模拟太阳系运转

    查看效果:http://hovertree.com/texiao/html5/9.htm 地球自传 http://hovertree.com/texiao/html5/8.htm 代码如下: < ...

  2. jquery管理ajax异步-deferred对象

    今天跟大家分享一个jquery中的对象-deferred.其实早在jquery1.5.0版本中就已经引入这个对象了.不过可能在实际开发过程中用到的并不多,所以没有太在意. 这里先不说deferred的 ...

  3. dojo.require()的相关理解

    Dojo 提供了一个非常强大的javascript控件库. 在使用dojo之前,用户基本上不需要具备任何基础知识. 你可以用script远程链接到dojo(dojo.js), 也可以把dojo.js下 ...

  4. IBatis.Net项目数据库SqlServer迁移至Oracle经验

    最近完成了一个(IBatis.Net+MVC)项目的数据库+代码迁移工作,可把我折腾得~~~ IBatis.Net是一个ORM框架,具体介绍可以问度娘.我之前没用ORM框架使用经验,所以这一路我不是走 ...

  5. iOS导航栏标题颜色

    按钮的颜色 [self.navigationBar setTintColor:[UIColor whiteColor]]; 标题颜色.字体 [self.navigationBar setTitleTe ...

  6. Android 6.0 权限管理最佳实践

    博客: Android 6.0 运行时权限管理最佳实践 github: https://github.com/yanzhenjie/AndPermission

  7. 浅谈RecyclerView(完美替代ListView,GridView)

    Android RecyclerView 是Android5.0推出来的,导入support-v7包即可使用. 个人体验来说,RecyclerView绝对是一款功能强大的控件. 首先总结下Recycl ...

  8. #数据技术选型#即席查询Shib+Presto,集群任务调度HUE+Oozie

    郑昀 创建于2014/10/30 最后更新于2014/10/31   一)选型:Shib+Presto 应用场景:即席查询(Ad-hoc Query) 1.1.即席查询的目标 使用者是产品/运营/销售 ...

  9. ORA-19563: header validation failed for file

    在测试服务器还原数据库时遇到了ORA-19563错误.如下所示 RMAN-00571: ======================================================== ...

  10. Android 实现网络多线程APK文件下载

    (转自:http://blog.csdn.net/mad1989/article/details/38421465) 实现原理 (1)首先获得下载文件的长度,然后设置本地文件的长度. (2)根据文件长 ...