-
Array.prototype.slice.call(arguments)的作用为:强制转化arguments为数组格式,一般出现在框架活插件的源码中
-
调用者的作用:沿着原型链向上找,最终找到Array为止,slice为Array原型上的一个方法
-
等价于[ ].slice.call(arguments)或一个数组调用 [1,2,3].slice.call(arguments)
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;
}
一、理解arguments
arguments在JavaScript语法中是函数特有的一个对象属性(Arguments对象),用来引用调用该函数时传递的实际参数
例1:使用Array的一个实例方法slice
function test(){
//将参数转为一个数组
var args = Array.prototype.slice.apply(arguments);
alert(args); //1,2,3,4,5
}
test(1,2,3,4,5);
- 解释:apply是用来改变函数执行的this指向,这里以argumens对象为this来执行Array.prototype.slice函数,而Array.prototype.slice函数不带参数时默认返回的是数组对象本身。
var ar = Array.prototype.slice.apply({0:1,length:1})
console.log(ar); //[1]
- 这里会将{0:1,length:1}形成一个新数组(这里属性名必须是0,1,2…,而且length属性不能少,而且应该跟前面属性个数对应,这样就模拟了一个数组)
总结:
-
arguments是一个类数组对象,包含着传入函数中的所有参数,而且可以使用length属性来确定传递进来多少个参数。
-
直接调用arguments.slice()将返回一个"Object doesn’t support this property or method"错误,因为arguments不是一个真正的数组。
-
以上代码调用Array.prototype.slice.apply(arguments)的意义就在于它能将函数的参数对象转化为一个真正的数组。另一方面也可推知Arguments对象和Array对象的亲缘关系。
例2:使用Array的另一个实例方法shift(用于获取并返回数组的第一个元素)
function test(){
var arg0 = Array.prototype.shift.apply(arguments);
alert(arg0); //8
}
test(8,2,3,4,5);
知识延伸
要实现将arguments强制转化为数组的其他方式:
- 利用es6的Array.from()
function fn(){
var a = Array.from(arguments);
var b = Array.from(arguments).slice(1);
console.log(a); //1,2,6,3,4,12
console.log(b); //2,6,3,4,12
}
fn(1,2,6,3,4,12);
- 利用es6的展开表达式
function fn(){
var a = [...arguments]; //1,2,6,3,4,12
var b = [...arguments].slice(1); //2,6,3,4,12
console.log(a);
console.log(b);
}
fn(1,2,6,3,4,12);
二、理解原理
- call(),apply()和 bind()方法
-
每个函数都包含两个非继承而来的方法:apply()和call()
-
这两个方法的用途是在特定的作用域中调用函数,即扩充函数赖以运行的作用域,实际上等于设置函数体内this对象的值。
-
apply()方法接受两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是Array的实例,也可以是arguments对象。
-
call()方法接受的参数:一个是在其中运行函数的作用域,从第二个参数开始,每一个参数会依次传递给调用函数。
-
bind()方法会创建一个函数的实例,其this值会被绑定到传给bind()函数的值;除了返回是函数以外,其参数与call一样。
例3:使用apply()方法切换了函数内部 this 的调用
function sum(num1,num2) {
return num1 + num2;
}
function callSum1(num1,num2) {
return sum.apply(this,arguments);
}
function callSum2(num1,num2) {
return sum.apply(this,[num1,num2]);
}
alert(callSum1(10,10)); //20
alert(callSum2(10,10)); //20
- callSum1()在执行sum()函数时传入了this作为this值(因为是在全局作用域中调用的,所以传入的就是window对象)和arguments对象。而callSum2同样也调用了sum()函数,但它传入的则是this和一个参数数组。
例4:使用call()切换了函数内部 this 的调用
function sum(num1,num2) {
return num1 + num2;
}
function callSum3(num1,num2) {
return sum.call(this,num1,num2);
}
alert(callSum3(10,10)); //20
- call()方法与apply()方法的作用相同,它们的区别仅在于接收参数的方式不同。对于call()方法,第一个参数是作用域没有变化,变化的只是其余的参数都是直接传递给函数的。
例5:使用call()扩充函数赖以运行的作用域
window.color = 'red';
var o = {color:'blue'};
function sayColor() {
alert(this.color);
}
sayColor(); //red
sayColor.call(this); //red
sayColor.call(window); //red
sayColor.call(o); //blue
- 当运行sayColor.call(o)时,函数的执行环境就不一样了,因为此时函数体内的this对象指向了o,于是结果显示"blue"。使用call()或者apply()来扩充作用域的最大好处,就是对象不需要与方法有任何耦合关系。
例6:使用bind()方法其this值会被绑定到传给bind()函数的值
window.color = 'red';
var o = {color:'blue'};
function sayColor() {
alert(this.color);
}
var objectSayColor=sayColor.bind(o);
objectSayColor(); //blue
- SayColor()调用bind()并传入对象o,创建了objectSayColor()函数。objectSayColor()函数的this等于o,因此即使是在全局作用域中调用这个函数,也会看到“blue”。
注:
(1)apply() 或 call() 只是切换了函数内部 this 的调用,但是执行的方法依然是原始对象上的方法, 即使在 Array.prototype.slice.call(obj)的 obj 上覆盖了slice() ,依然会执行 Array 上的 slice() 。
(2)由于apply() 或 call() 也可以绑定函数执行时所在的对象,但是会立即执行函数,因此需要把绑定语句写在一个函数体内。建议使用函数改变this指向时使用 bind() 。
(3)bind()每运行一次,就返回一个新函数,这会产生一些问题。
如监听事件时,不能写成下面这样:
element.addEventListener('click', o.m.bind(o));
以上click事件绑定bind方法生成的一个匿名函数,这样会导致无法取消绑定,因此以下代码无效:
element.removeEventListener('click', o.m.bind(o));
正确写法:
var listener = o.m.bind(o);
element.addEventListener('click', listener);
// ...
element.removeEventListener('click', listener);
- 数组是一个特殊的object对象
-
js 中存在一种类数组对象,如 {0:1,length:1} 或 DOM 对象或 arguments 对象;
-
数组只是一种特殊的对象,数组的特殊性体现在,它的键默认是按次序排列的整数(0,1,2…),所以数组不用为每个元素指定键名,而对象的每个成员都必须指定键名。
因此 js 中的数组也可以看成是这样的对象:
var array = [1, 2, 3];
var obj = {
0: 1,
1: 2,
2: 3,
length:3
}
注:length属性很重要,有了length就可以像数组一样遍历此对象
例7:实现slice方法
function slice(start, end) {
var array = [];
start = start ? start : 0;
end = end ? end : this.length;
for (var i = start, j = 0; i < end; i++, j++) {
array[j] = this[i];
}
return array;
}
//Array.prototype.slice.apply({0:1,length:1});通过apply,将slice方法中的this指向该对象,遍历生成新的数组对象
console.log(Array.prototype.slice.apply({0: 1, length: 1})); //法一
var array=[1];
console.log(array.slice()); //法二
- 法一和法二的作用效果一样