JavaScript中的call和apply的用途以及区别


Posted in Javascript onJanuary 11, 2017

apply 接受两个参数,第一个参数指定了函数体内this 对象的指向,第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类数组,apply 方法把这个集合中的元素作为参数传递给被调用的函数:

var func = function( a, b, c ){
  alert ( [ a, b, c ] ); // 输出 [ 1, 2, 3 ]
};
func.apply( null, [ 1, 2, 3 ] );

在这段代码中,参数 1、2、3 被放在数组中一起传入func函数,它们分别对应func参数列表中的a、b、c。

call 传入的参数数量不固定,跟apply 相同的是,第一个参数也是代表函数体内的this 指向,从第二个参数开始往后,每个参数被依次传入函数:

var func = function( a, b, c ){
  alert ( [ a, b, c ] ); // 输出 [ 1, 2, 3 ]
};
func.call( null, 1, 2, 3 );

当调用一个函数时,JavaScript 的解释器并不会计较形参和实参在数量、类型以及顺序上的区别,JavaScript 的参数在内部就是用一个数组来表示的。从这个意义上说,apply比call的使用率更高,我们不必关心具体有多少参数被传入函数,只要用apply 一股脑地推过去就可以了。call是包装在apply上面的一颗语法糖,如果我们明确地知道函数接受多少个参数,而且想一目了然地表达形参和实参的对应关系,那么也可以用call 来传送参数。

call和apply的用途

1. 改变this 指向

call 和apply 最常见的用途是改变函数内部的this 指向,我们来看个例子:

var obj1 = {
  name: 'sven'
};
var obj2 = {
  name: 'anne'
};
window.name = 'window';
var getName = function(){
  alert ( this.name );
};
getName(); // 输出: window
getName.call( obj1 ); // 输出: sven
getName.call( obj2 ); // 输出: anne

当执行getName.call( obj1 )这句代码时,getName 函数体内的this 就指向obj1 对象,所以此处的

var getName = function(){
alert ( this.name );
};

实际上相当于:

var getName = function(){
alert ( obj1.name ); // 输出: sven
};

在实际开发中,经常会遇到this指向被不经意改变的场景,比如有一个div节点,div节点的onclick 事件中的this 本来是指向这个div的:

document.getElementById( 'div1' ).onclick = function(){
  alert( this.id ); // 输出:div1
};

假如该事件函数中有一个内部函数func,在事件内部调用func 函数时,func 函数体内的this就指向了window,而不是我们预期的div,见如下代码:

document.getElementById( 'div1' ).onclick = function(){
  alert( this.id ); // 输出:div1
  var func = function(){
    alert ( this.id ); // 输出:undefined
  }
  func();
};

这时候我们用call 来修正func 函数内的this,使其依然指向div:

document.getElementById( 'div1' ).onclick = function(){
  var func = function(){
    alert ( this.id ); // 输出:div1
  }
  func.call( this );
};

2. Function.prototype.bind

大部分高级浏览器都实现了内置的Function.prototype.bind,用来指定函数内部的this 指向,即使没有原生的Function.prototype.bind 实现,我们来模拟一个也不是难事,代码如下:

Function.prototype.bind = function( context ){
var self = this; // 保存原函数
return function(){ // 返回一个新的函数
    return self.apply( context, arguments ); // 执行新的函数的时候,会   把之前传入的context
  // 当作新函数体内的this
  }
};
var obj = {
  name: 'sven'
};
var func = function(){
  alert ( this.name ); // 输出:sven
}.bind( obj);
func();

我们通过Function.prototype.bind 来“包装”func 函数,并且传入一个对象context 当作参数,这个context 对象就是我们想修正的this 对象。

在Function.prototype.bind 的内部实现中,我们先把func 函数的引用保存起来,然后返回一个新的函数。当我们在将来执行func 函数时,实际上先执行的是这个刚刚返回的新函数。在新函数内部,self.apply( context, arguments )这句代码才是执行原来的func 函数,并且指定context对象为func 函数体内的this。

这是一个简化版的Function.prototype.bind 实现,通常我们还会把它实现得稍微复杂一点,

使得可以往func 函数中预先填入一些参数:

Function.prototype.bind = function(){
  var self = this, // 保存原函数
  context = [].shift.call( arguments ), // 需要绑定的this 上下文
  args = [].slice.call( arguments ); // 剩余的参数转成数组
  return function(){ // 返回一个新的函数
    return self.apply( context, [].concat.call( args, [].slice.call(  arguments ) ) );
    // 执行新的函数的时候,会把之前传入的context 当作新函数体内的this
    // 并且组合两次分别传入的参数,作为新函数的参数
  }
};
var obj = {
  name: 'sven'
};
var func = function( a, b, c, d ){
  alert ( this.name ); // 输出:sven
  alert ( [ a, b, c, d ] ) // 输出:[ 1, 2, 3, 4 ]
}.bind( obj, 1, 2 );
func( 3, 4 );

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持三水点靠木!

Javascript 相关文章推荐
JS 建立对象的方法
Apr 21 Javascript
caller和callee的区别介绍及演示结果
Mar 10 Javascript
jquery如何实现在加载完iframe的内容后再进行操作
Sep 10 Javascript
js获取URL的参数的方法(getQueryString)示例
Sep 29 Javascript
15个jquery常用方法、小技巧分享
Jan 13 Javascript
简单讲解AngularJS的Routing路由的定义与使用
Mar 05 Javascript
AngularJS基础 ng-keypress 指令简单示例
Aug 02 Javascript
基于jstree使用AJAX请求获取数据形成树
Aug 29 Javascript
微信小程序之页面跳转和参数传递的实现
Sep 29 Javascript
分析JS中this引发的bug
Dec 12 Javascript
VSCode搭建Vue项目的方法
Apr 30 Javascript
使用vue编写h5公众号跳转小程序的实现代码
Nov 27 Vue.js
Vue数据驱动模拟实现2
Jan 11 #Javascript
jQuery实现对象转为url参数的方法
Jan 11 #Javascript
将鼠标焦点定位到文本框最后(代码分享)
Jan 11 #Javascript
移动端界面的适配
Jan 11 #Javascript
bootstrapValidator bootstrap-select验证不可用的解决办法
Jan 11 #Javascript
HTML5 js实现拖拉上传文件功能
Nov 20 #Javascript
Bootstrap表单控件使用方法详解
Jan 11 #Javascript
You might like
PHP实现将textarea的值根据回车换行拆分至数组
2015/06/10 PHP
PHP简单获取多个checkbox值的方法
2016/06/13 PHP
PHP中for循环与foreach的区别
2017/03/06 PHP
php删除二维数组中的重复值方法
2018/03/12 PHP
js 判断checkbox是否选中的操作方法
2012/11/09 Javascript
JS实现title标题栏文字不间断滚动显示效果
2016/09/07 Javascript
JavaScript通过改变文字透明度实现的文字闪烁效果实例
2017/04/27 Javascript
Javascript中this关键字指向问题的测试与详解
2017/08/11 Javascript
bootstrap可编辑下拉框jquery.editable-select
2017/10/12 jQuery
使用Vue构建可重用的分页组件
2018/03/26 Javascript
Vue.set 全局操作简单示例
2019/09/19 Javascript
JavaScript实现指定数量的并发限制的示例代码
2020/03/10 Javascript
vue中解决微信html5原生ios虚拟键返回不刷新问题
2020/10/20 Javascript
vue监听滚动事件的方法
2020/12/21 Vue.js
react-native 实现购物车滑动删除效果的示例代码
2021/01/15 Javascript
[54:05]DOTA2-DPC中国联赛定级赛 SAG vs iG BO3第一场 1月9日
2021/03/11 DOTA
Python实现随机生成有效手机号码及身份证功能示例
2017/06/05 Python
Python中max函数用于二维列表的实例
2018/04/03 Python
python3 Scrapy爬虫框架ip代理配置的方法
2020/01/17 Python
Pytest框架之fixture的详细使用教程
2020/04/07 Python
keras使用Sequence类调用大规模数据集进行训练的实现
2020/06/22 Python
CSS3中的5个有趣的新技术
2009/04/02 HTML / CSS
凯特方迪化妆品官网:Kat Von D Beauty
2016/11/15 全球购物
美国快时尚彩妆品牌:Winky Lux(透明花瓣润唇膏)
2018/11/06 全球购物
总经理岗位职责
2013/11/09 职场文书
报社实习生自荐信
2014/01/24 职场文书
甜美蛋糕店创业计划书
2014/01/30 职场文书
《吃水不忘挖井人》教学反思
2014/04/15 职场文书
《欢乐的泼水节》教学反思
2014/04/22 职场文书
高校教师个人总结
2015/02/10 职场文书
2015年机关党委工作总结
2015/05/23 职场文书
2016年重阳节慰问信
2015/12/01 职场文书
使用vue-element-admin框架从后端动态获取菜单功能的实现
2021/04/29 Vue.js
MySQL为id选择合适的数据类型
2021/06/07 MySQL
一篇文章带你学习Mybatis-Plus(新手入门)
2021/08/02 Java/Android
python高温预警数据获取实例
2022/07/23 Python