从原理来分析call,apply和bind之间的区别

1.call()方法

call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
理解call()的原理,首先要理解this的指向问题,即谁调用函数,函数的this就指向谁。

let obj = {
    a: 1
 }
 function bar() {
	 let a = 10;
    console.log(this.a); // 10
 }
 bar();  //10
 bar.call(obj);  // 1

上述例子可以看出来,当没有使用call()的时候,this.a打印出来的是10,是通过this的上下文取到了a的值,而通过call()方法之后,改变了bar的this指向,结果打印出了1;
关于call()方法,相信大家都会使用,在这里我们就不多叙述了,主要是讲怎么从原理来理解call()方法;
通过观察上述例子,可以发现,调用call(),其实就是改变this的指向,而bar.call(obj)就是讲bar的this指向obj,那么通俗来讲就是讲bar函数放进obj对象中,即:

let obj = {
	a: 1,
	bar() {
		return this.a;
	}
}
此时调用obj.bar(); 则this.a = 1;
即:
Function.prototype.call2 = function(context) {
  context.fn = this;
  context.fn();
  delete context.fn;
}
bar.call2(obj); //1

已经初步实现了一个call()方法,但是我们知道,cal(),方法可接收多个参数,所以我们还要根据接收参数来做处理。

Function.prototype.call3 = function(context) {
  context.fn = this;
  let args = [];
  for ( let i = 1; i < arguments.length; i++ ) {
    args.push('arguments['+ i + ']');
  }
  eval('context.fn(' + args +')');
  delete context.fn;
}
let obj = {
	a: 1
}
function baz(b, c) {
	return this.a + b + c
}
baz.call3(obj, 3,5) //9

到这一步,我们基本已经封装好了call()方法,但是call()的第一个参数也可以传入null,当传入null的时候,this是指向window的,所以我们还要讲第一个参数为null,指向window的情况考虑进去;

Function.prototype.call4 = function(context) {
  var context = context || window;
  context.fn = this;
  let args = [];
  for ( let i = 1; i < arguments.length; i++ ) {
    args.push('arguments['+ i + ']');
  }
  eval('context.fn(' + args +')');
  delete context.fn;
}
let a = 10;
function baz(b, c) {
	return this.a +  b + c
}
baz.call3(null, 3,5) //18

到这里已经是一个完整的call()函数,但是在上述call()方法中,我们是用eval和arguments来执行了代码,在ES6发布之后,我们是有…扩展运算符,所以我们可以用…扩展运算符来写出更简单的call()方法,也方便大家更容易理解。

Function.prototype.call5 = function(context, ...args) {
  let obj = context || window;
  obj.fn = this;
  const result = obj.fn(...args);
  delete obj.fn;
  return result;
}

2.apply()方法

apply() 方法的作用和call()方法类似,区别就是call()方法接受的是参数列表,而apply()方法接受的是一个参数数组。在此我们就不叙述怎样去使用apply()方法了,相信大家都会使用。
和call()方法一样,只是apply()方法只有2个参数,第二个参数为数组,所以我们要先判断第二个参数是否为数组。若不是数组则抛出错误;

Function.prototype.apply2 = function(context, array) {
  var context = context || window;
  context.fn = this;
  var result;
  if ( !array ) {
    result = context.fn();
  } else {
    if ( !Array.isArray(array) ) throw new ('params must be array');
    var args = [];
    for ( var i = 0; i < array.length; i++ ) {
      args.push('array[' + i + ']')
    }
    result = eval('context.fn(' + args + ')')
  }
  delete context.fn;
  return result;
}

也可以使用ES6的…扩展运算符来封装apply方法

Function.prototype.applySource = function(obj, array) {
  let context = obj || window;
  context.fn = this;
  let result;
  if ( !array ) {
    result = context.fn();
  } else {
    if ( !Array.isArray(array) ) throw new Error('参数必须是数组')
    result = context.fn(...array)
  }
  delete context.fn;
  return result;
}

3.bind()方法

bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
返回的函数是原函数的拷贝,并且拥有指定this值和初始参数。
bind() 函数会创建一个新的绑定函数。绑定函数是一个 exotic function object(怪异函数对象,ECMAScript 2015 中的术语),它包装了原函数对象。调用绑定函数通常会导致执行包装函数。
那么在没有bind函数的时候,我们又是怎样模拟bind函数的呢。

if (!Function.prototype.bind) (function(){
 Function.prototype.bindSource = function(oThis) {
        if ( typeof this !== 'function' ) throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
        var aArgs = Array.prototype.slice.call(arguments, 1);
        var fToBind = this;
        var fNOP = function() {};
        var fBound = function() {
            return fToBind.apply(this instanceof fBound? this: oThis, aArgs.concat(Array.prototype.slice.call(arguments)));
        }
        if ( this.prototype ) fNOP.prototype = this.prototype;
        fBound.prototype= new fNOP();
        return fBound;
    }
})();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值