Array.prototype.slice.apply(arguments)

  • 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强制转化为数组的其他方式:

  1. 利用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);
  1. 利用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);

二、理解原理

  1. 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);
  1. 数组是一个特殊的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());  //法二
  • 法一和法二的作用效果一样
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值