深入解析 Array.prototype.slice.call 实现类数组转数组

作者:异步漫步时间:2026-10-29 14:29:48浏览:507

本文深入解析 JavaScript 中 Array.prototype.slice.call(arguments, 0) 的实现原理。通过拆解 slice 截取逻辑与 call 作用域绑定机制,阐明如何将具有 length 属性的类数组对象(如 arguments)转换为真实数组,并对比 substring/substr 差异及 IE 兼容性问题。

写字前面

slice() 方法基础与负数处理

在 JavaScript 开发中,经常可以看到 Array.prototype.slice.call(arguments, 0) 这种写法。其核心作用是将类数组对象转换为真正的数组实例。针对这一机制,下文将展开详细分析。

该实现涉及 slice() 方法与 call() 方法,因此首先简要介绍这两个基础方法的功能。

slice() 方法概述

数组和字符串类型均提供了 slice 方法,用于截取指定范围的数据。该方法接收两个参数:第一个参数指定起始索引,第二个参数可选,指定结束索引(不包含该位置)。对于数组,返回值为由截取元素组成的新数组;对于字符串,返回值为由截取字符组成的新字符串。

该方法支持负数参数。当传入负值时,系统会将该参数与数组或字符串的长度相加,从而计算出实际的正数索引作为处理依据。

如下:


[1,2,3,4,5,6].slice(2,4);

[1,2,3,4,5,6].slice(-4,-2);

上述代码返回值均为 [3, 4],结果为数组类型。


'everything'.slice(2,4);

'everything'.slice(-4,-2);

字符串示例返回值分别为 'er' 和 'hi',结果为字符串类型。

若仅传入一个参数,则默认从指定起始位置截取至末尾所有元素,此处不再赘述具体示例。

字符串的其他类似方法

在字符串操作中,除了 slice() 外,还有两个功能相近的方法:

substring() 和 substr()。

其中,substring() 返回从起始位置到结束位置的子字符串;substr() 接收两个参数,第一个为起始位置,第二个为截取字符的数量,这与前两者的行为略有差异。

当传入负数参数时,这三种方法的处理逻辑存在细微区别。

具体差异如下:

slice() 的处理方式如前所述,即负数加上字符串长度得出对应的正值;

substring() 会将所有负数参数视为 0;

substr() 的第一个参数若为负值,则加上字符串长度转为正值,而第二个参数若为负值则被视为 0。

call() 与 apply() 的作用域机制

call() 和 apply() 方法解析

call() 和 apply() 的主要用途在于扩展函数的作用域。

这两个方法均接收两个参数:

apply() 的第一个参数指定作用域(this 指向),第二个参数为参数数组。该参数可以是数组实例,也可以是 arguments 对象。

call() 方法同样接收两个参数,区别在于传参方式:调用函数所需的参数必须逐个显式列出。

鉴于此处并非讨论重点,不再展开详述。


Array.prototype.slice.call(arguments,0)

Array.prototype.slice.call 转换原理

在 Array.prototype.slice.call(arguments, 0) 中,Array.prototype.slice 调用的是数组原型上的 slice 方法。标准数组实例拥有 slice() 方法,但 arguments 或自定义的类数组对象虽具备 length 等属性,却未直接继承 slice()。因此,需通过原型链调用 slice 方法,即 Array.prototype.slice(若类数组对象自定义了 slice 则直接调用)。

因此,Array.prototype.slice.call(arguments, 0) 的含义可理解为:针对 arguments 类数组,调用 Array.prototype.slice 原型方法,并利用 call() 将作用域绑定至 arguments 对象。此时 Array.prototype 在逻辑上等同于 arguments,参数 0 作为 slice() 的第一个参数指定起始索引。通过此机制,arguments 类数组被转换为真正的数组。

虽然也可以通过遍历 arguments 对象来构建数组,但那样代码量更多且不够直观。

众所周知,Array.prototype.slice.call(arguments) 可将具有 length 属性的对象转换为数组。但在 IE 浏览器中,节点集合无法转换,因为 IE 下的 DOM 对象以 COM 对象形式实现,JavaScript 对象与 COM 对象无法直接转换。 例如:


 var a={length:2,0:'first',1:'second'};
 Array.prototype.slice.call(a);// ["first", "second"]
 var a={length:2};
 Array.prototype.slice.call(a);// [undefined, undefined]

内部实现机制与通用转换函数

初学者可能难以理解该语句为何能实现转换功能。以下将深入探究其内部机制。

首先,slice 有两种用法:String.slice 和 Array.slice。前者返回字符串,后者返回数组。此处关注第二种情况。

既然 Array.prototype.slice.call(arguments) 能将 arguments 转为数组,是否等同于 arguments.toArray().slice()?这是否意味着该过程先将第一个参数转为数组再调用 slice? 再看 call 的用法,示例如下


 var a = function(){
   console.log(this);  // 'littledu'
   console.log(typeof this);   // Object
   console.log(this instanceof String);  // true
 }
 a.call('littledu');

可以看出,调用 call 后,当前函数被推入所传参数的作用域中。虽然表述未必严谨,但 this 确实指向了传入的对象。 至此,基本逻辑已清晰。我们可以推测 slice 的内部实现如下


 Array.prototype.slice = function(start,end){
   var result = new Array();
   start = start || 0;
   end = end || this.length; //this指向调用的对象,当用了call后,能够改变this的指向,也就是指向传进来的对象,这是关键
   for(var i = start; i < end; i++){
      result.push(this[i]);
   }
   return result;
 }

大致逻辑如此,理解原理即可,无需过度深究。

最后,提供一个通用的转换为数组的函数实现


var toArray = function(s){
  try{
    return Array.prototype.slice.call(s);
  } catch(e){
      var arr = [];
      for(var i = 0,len = s.length; i < len; i++){
        //arr.push(s[i]);
        arr[i] = s[i]; //据说这样比push快
      }
       return arr;
  }
}

作者最新文章