深入解析 Array.prototype.slice.call 实现类数组转数组
本文深入解析 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;
}
}
D3.js 比例尺与坐标轴的配置详解
2026-11-05 16:54:46
JavaScript怎么遍历对象的Symbol键和不可枚举属性
2026-11-05 13:13:39
封装一个健壮的JS字符串判空工具函数
2026-11-05 12:16:14
JavaScript合并数组的三种常用方法:concat、扩展运算符与push
2026-11-05 11:22:29
JavaScript立即执行函数(IIFE)是什么及基本语法
2026-11-05 10:51:19
