简单介绍Apply,Call,Bind概念与使用

作用与区别

apply,call,bind的作用都是改变运行时上下文的(即函数中的this对象),区别是apply与call是立即执行,而bind的作用是改变运行上下文后返回新的函数,用于以后执行的函数;apply与call的区别在于使用方式不同,apply中传递的参数是一个数组,而call则是传递了一系列参数,下面通过一系列例子来具体说明。

举例

var tom= {
    name: 'tom',
    introduce: function(){
        console.log('my name is '+this.name);
    }
}

var jerry = {
    name: 'jerry'
}

tom.introduce();
// my name is tom

tom.introduce.apply(jerry);
// my name is jerry

tom.introduce.call(jerry);
// my name is jerry

var newTom = tom.introduce.bind(jerry);
newTom();
// my name is jerry

上述例子中定义了两个对象tom和jerry,tom中包含了一个function:introduce,其中的this指的是tom对象本身,故打印的结果是my name is tom,而当使用tom.introduce.apply(jerry);时,将this指向到对象jerry,故打印的结果变成my name is jerry,同时也可以看到使用tom.introduce.call(jerry);tom.introduce.apply(jerry);的作用是一样的;使用var newTom = tom.introduce.bind(jerry);后,将改变上下文后的function返回给新的newTom对象,所以在调用newTom();后打印的结果是my name is jerry

上面的例子介绍的是apply, call, bind是如何改变运行上下文的,下面继续通过例子来介绍包含参数的情况。

var tom= {
    name: 'tom',
    introduce: function(age, sex){
        console.log('my name is '+this.name+', I am '+age+' years old, I am a '+sex);
    }
}
var jerry = {
    name: 'jerry'
}

tom.introduce(12,'boy');
// my name is tom, I am 12 years old, I am a boy

tom.introduce.call(jerry,15,'girl');
// my name is jerry, I am 15 years old, I am a girl

tom.introduce.apply(jerry,[15,'girl']);
// my name is jerry, I am 15 years old, I am a girl

很简单,call是通过将参数全部列出的方式来调用的tom.introduce.call(jerry,15,'girl');,而apply则是通过将参数通过数组的形式调用的tom.introduce.apply(jerry,[15,'girl']);;这里都是参数确定的情况,如果参数不定呢?那么可以通过arguments来实现。

arguments对象不是一个 Array 。它类似于Array,但除了length属性和索引元素之外没有任何Array属性。例如,它没有 pop 方法。但是它可以被转换为一个真正的Array

var args = Array.prototype.slice.call(arguments);
var args = [].slice.call(arguments);

// ES2015
const args = Array.from(arguments);
function logs(){
    const loglist = Array.prototype.slice.call(arguments);
    loglist.forEach((log) => {
        console.log(log);
    })
}

logs.apply(this,[1,2,3,4]);
// 1
// 2
// 3
// 4

以上的代码很简单,以数组的形式将参数传递到logs中去,再依次打印出传递的参数,其中通过了Array.prototype.slice.call(arguments);将arguments转为了数组,这里可以看到也是通过call/apply方法来实现转换的,下面来介绍一下原理

// This will work for genuine arrays, array-like objects, 
// NamedNodeMap (attributes, entities, notations),
// NodeList (e.g., getElementsByTagName), HTMLCollection (e.g., childNodes),
// and will not fail on other DOM objects (as do DOM elements in IE < 9)
Array.prototype.slice = function(begin, end) {
  // IE < 9 gets unhappy with an undefined end argument
  // 如果未指定结束index,则end取this对象的长度
  end = (typeof end !== 'undefined') ? end : this.length;

  // For native Array objects, we use the native slice function
  // 如果是原生的数组类型,那么直接调用_slice.call()
  if (Object.prototype.toString.call(this) === '[object Array]'){
    return _slice.call(this, begin, end); 
  }

  // For array like object we handle it ourselves.
  var i, cloned = [],
    size, len = this.length;

  // Handle negative value for "begin"
  // 如果未指定begin,则start取0
  var start = begin || 0;
  // 如果小于0,那么取Math.max(0, len + start);即从数组的末尾算起
  start = (start >= 0) ? start : Math.max(0, len + start);

  // Handle negative value for "end"
  // 判断end是否超出了数组的长度
  var upTo = (typeof end == 'number') ? Math.min(end, len) : len;
  // 如果end小于0,那么也是从末尾算起
  if (end < 0) {
    upTo = len + end;
  }

  // Actual expected size of the slice
  // 计算返回的数组的长度
  size = upTo - start;

  if (size > 0) {
  	// 根据前面计算的数组的长度声明一个新的数组
    cloned = new Array(size);
    // 如果this对象是一个字符串,那么截取字符串的部分返回,否则认为this对象是一个数组,返回指定区间内的数组
    if (this.charAt) {
      for (i = 0; i < size; i++) {
        cloned[i] = this.charAt(start + i);
      }
    } else {
      for (i = 0; i < size; i++) {
        cloned[i] = this[start + i];
      }
    }
  }

  return cloned;
};

上面是从网上找的js中slice函数的逻辑,结合我添加的注释可以看到,通过call/apply将函数内的this对象改变了传递的arguments后,可以返回一个Array类型的对象,其中arguments中应包含length属性(end = (typeof end !== 'undefined') ? end : this.length;),并且需要包含有序的key(cloned[i] = this[start + i];)才可以使用,而arguments作为一个伪数组刚好满足条件,即可实现转换为数组。类似的伪数组还包含dom节点(如通过$(".class")dom.getElementByTabName("div")等等取得的dom节点)等。

除了slice外,还可以使用Array.prototype.push.apply(arr1,arr2)合并数组,使用Math.max.apply(Math, [...])Math.min.apply(Math, numbers)来获取最大值最小值等等,也都是改变运行上下文实现的,具体原理不再赘述。

总结

通过上述的例子可以看出,apply, call, bind的作用都是改变运行时上下文,即this对象,而apply/call和bind的不同点在于,bind是返回一个新的对象,并且不会立即执行新的函数,而apply/call则会立即执行新的函数;而apply和call的不同点主要则在于传递参数方式的不同,同时也介绍了arguments的简单应用。

本文只是通过一些例子简单介绍了apply, call, bind的概念和简单使用,在正式的使用时建议浏览一些比较官方权威的文档为好;网上介绍apply, call, bind相关的佳作多如牛毛,本文只是记录了本人对于这些对象的个人理解,如有错误或遗漏之处,还望不吝指教。

原文地址: 简单介绍Apply,Call,Bind概念与使用

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值