浅谈javascript中的call、apply、bind


Posted in Javascript onMarch 06, 2016

在JavaScript中,call、apply和bind 是Function对象自带的三个方法,这三个方法的主要作用是改变函数中的this指向,从而可以达到`接花移木`的效果。本文将对这三个方法进行详细的讲解,并列出几个经典应用场景。 

call(thisArgs [,args...])

该方法可以传递一个thisArgs参数和一个参数列表,thisArgs指定了函数在运行期的调用者,也就是函数中的this对象,而参数列表会被传入调用函数中。thisArgs的取值有以下4种情况:

(1) 不传,或者传null,undefined, 函数中的this指向window对象

(2) 传递另一个函数的函数名,函数中的this指向这个函数的引用

(3) 传递字符串、数值或布尔类型等基础类型,函数中的this指向其对应的包装对象,如 String、Number、Boolean

(4) 传递一个对象,函数中的this指向这个对象

function a(){
 console.log(this); //输出函数a中的this对象
}
function b(){} //定义函数b

var obj = {name:'onepixel'}; //定义对象obj

a.call(); //window
a.call(null); //window
a.call(undefined);//window
a.call(1); //Number
a.call(''); //String
a.call(true); //Boolean
a.call(b);// function b(){}
a.call(obj); //Object

这是call的核心功能,它允许你在一个对象上调用该对象没有定义的方法,并且这个方法可以访问该对象中的属性,至于这样做有什么好处,我待会再讲,我们先看一个简单的例子:

var a = {

 name:'onepixel', //定义a的属性

 say:function(){ //定义a的方法
  console.log("Hi,I'm function a!");
 }
};

function b(name){
 console.log("Post params: "+ name);
 console.log("I'm "+ this.name);
 this.say();
}

b.call(a,'test');
>>
Post params: test
I'm onepixel
I'm function a!

当执行b.call时,字符串`test`作为参数传递给了函数b,由于call的作用,函数b中的this指向了对象a, 因此相当于调用了对象a上的函数b,而实际上a中没有定义b 。

apply(thisArgs[,args[]])

apply和call的唯一区别是第二个参数的传递方式不同,apply的第二个参数必须是一个数组,而call允许传递一个参数列表。值得你注意的是,虽然apply接收的是一个参数数组,但在传递给调用函数时,却是以参数列表的形式传递,我们看个简单的例子:

function b(x,y,z){
 console.log(x,y,z);
}

b.apply(null,[1,2,3]); // 1 2 3

apply的这个特性很重要,我们会在下面的应用场景中提到这个特性。

bind(thisArgs [,args...])

bind是ES5新增的一个方法,它的传参和call类似,但又和call/apply有着显著的不同,即调用call或apply都会自动执行对应的函数,而bind不会执行对应的函数,只是返回了对函数的引用。粗略一看,bind似乎比call/apply要落后一些,那ES5为什么还要引入bind呢?

其实,ES5引入bind的真正目的是为了弥补call/apply的不足,由于call/apply会对目标函数自动执行,从而导致它无法在事件绑定函数中使用,因为事件绑定函数不需要我们手动执行,它是在事件被触发时由JS内部自动执行的。而bind在实现改变函数this的同时又不会自动执行目标函数,因此可以完美的解决上述问题,看一个例子就能明白:

var obj = {name:'onepixel'};

/**
 * 给document添加click事件监听,并绑定onClick函数
 * 通过bind方法设置onClick的this为obj,并传递参数p1,p2
 */
document.addEventListener('click',onClick.bind(obj,'p1','p2'),false);

//当点击网页时触发并执行
function onClick(a,b){
 console.log(
   this.name, //onepixel
   a, //p1
   b //p2
 )
}

当点击网页时,onClick被触发执行,输出onepixel p1 p2, 说明onClick中的this被bind改变成了obj对象,为了对bind进行深入的理解,我们来看一下bind的polyfill实现:

if (!Function.prototype.bind) {
 Function.prototype.bind = function (oThis) {
  var aArgs = Array.prototype.slice.call(arguments, 1),
   fToBind = this, //this在这里指向的是目标函数
   fBound = function () {
    return fToBind.apply(
     //如果外部执行var obj = new fBound(),则将obj作为最终的this,放弃使用oThis
     this instanceof fToBind
       ? this //此时的this就是new出的obj
       : oThis || this, //如果传递的oThis无效,就将fBound的调用者作为this

     //将通过bind传递的参数和调用时传递的参数进行合并,并作为最终的参数传递
     aArgs.concat(Array.prototype.slice.call(arguments)));
   };

  //将目标函数的原型对象拷贝到新函数中,因为目标函数有可能被当作构造函数使用
  fBound.prototype = this.prototype;

  //返回fBond的引用,由外部按需调用
  return fBound;
 };
}

应用场景一:继承

大家知道,JavaScript中没有诸如Java、C#等高级语言中的extend 关键字,因此JS中没有继承的概念,如果一定要继承的话,call和apply可以实现这个功能:

function Animal(name,weight){
 this.name = name;
 this.weight = weight;
}

function Cat(){
 Animal.call(this,'cat','50');
 //Animal.apply(this,['cat','50']);

 this.say = function(){
  console.log("I am " + this.name+",my weight is " + this.weight);
 }
}

var cat = new Cat();
cat.say();//I am cat,my weight is 50

当通过new运算符产生了cat时,Cat中的this就指向了cat对象,而继承的关键是在于Cat中执行了Animal.call(this,'cat','50') 这句话,在call中将this作为thisArgs参数传递,于是Animal方法中的this就指向了Cat中的this,而cat中的this指向的是cat对象,所以Animal中的this指向的就是cat对象,在Animal中定义了name和weight属性,就相当于在cat中定义了这些属性,因此cat对象便拥有了Animal中定义的属性,从而达到了继承的目的。 

应用场景二:移花接木

在讲下面的内容之前,我们首先来认识一下JavaScript中的一个非标准专业术语:ArrayLike(类数组/伪数组)

ArrayLike 对象即拥有数组的一部分行为,在DOM中早已表现出来,而jQuery的崛起让ArrayLike在JavaScript中大放异彩。ArrayLike对象的精妙在于它和JS原生的Array类似,但是它是自由构建的,它来自开发者对JavaScript对象的扩展,也就是说:对于它的原型(prototype)我们可以自由定义,而不会污染到JS原生的Array。

ArrayLike对象在JS中被广泛使用,比如DOM中的NodeList, 函数中的arguments都是类数组对象,这些对象像数组一样存储着每一个元素,但它没有操作数组的方法,而我们可以通过call将数组的某些方法`移接`到ArrayLike对象,从而达到操作其元素的目的。比如我们可以这样遍历函数中的arguments:

function test(){
 //检测arguments是否为Array的实例
 console.log(
   arguments instanceof Array, //false
   Array.isArray(arguments) //false
 );
 //判断arguments是否有forEach方法
 console.log(arguments.forEach); //undefined

 // 将数组中的forEach应用到arguments上
 Array.prototype.forEach.call(arguments,function(item){
  console.log(item); // 1 2 3 4
 });

}
test(1,2,3,4);

除此之外,对于apply而言,我们上面提到了它独有的一个特性,即apply接收的是数组,在传递给调用函数的时候是以参数列表传递的。 这个特性让apply看起来比call 略胜一筹,比如有这样一个场景:给定一个数组[1,3,4,7],然后求数组中的最大元素,而你知道,数组中并没有获取最大值的方法,一般情况下,你需要通过编写代码来实现。而我们知道,Math对象中有一个获取最大值的方法,即Math.max(), max方法需要传递一个参数列表,然后返回这些参数中的最大值。而apply不仅可以将Math对象的max方法应用到其他对象上,还可以将一个数组转化为参数列表传递给max,看代码就能一目了然:

var arr = [2,3,1,5,4];

Math.max.apply(null,arr); // 5

以上便是call和apply比较经典的几个应用场景,熟练掌握这些技巧,并把这些特性应用到你的实际项目中,会使你的代码看起来更加耐人寻味!

Javascript 相关文章推荐
通过遮罩层实现浮层DIV登录的js代码
Feb 07 Javascript
jQuery控制TR显示隐藏的三种常用方法
Aug 21 Javascript
javascript实现判断鼠标的状态
Jul 10 Javascript
JQuery悬停控制图片轮播——代码简单
Aug 05 Javascript
jQuery右侧选项卡焦点图片轮播特效代码分享
Sep 05 Javascript
jquery UI Datepicker时间控件的使用方法(加强版)
Nov 07 Javascript
聊聊JavaScript如何实现继承及特点
Apr 07 Javascript
three.js中3D视野的缩放实现代码
Nov 16 Javascript
Vue props 单向数据流的实现
Nov 06 Javascript
跨域请求两种方法 jsonp和cors的实现
Nov 11 Javascript
Vue中使用create-keyframe-animation与动画钩子完成复杂动画
Apr 09 Javascript
vue 动态生成拓扑图的示例
Jan 03 Vue.js
一波JavaScript日期判断脚本分享
Mar 06 #Javascript
Node.js程序中的本地文件操作用法小结
Mar 06 #Javascript
JavaScript实现数据类型的相互转换
Mar 06 #Javascript
在React框架中实现一些AngularJS中ng指令的例子
Mar 06 #Javascript
javascript实现label标签跳出循环操作
Mar 06 #Javascript
使用JavaScript脚本判断页面是否在微信中被打开
Mar 06 #Javascript
JavaScript实现图片滑动切换的代码示例分享
Mar 06 #Javascript
You might like
php中get_defined_constants函数用法实例分析
2015/05/12 PHP
php生成带logo二维码方法小结
2016/04/08 PHP
php array_slice 取出数组中的一段序列实例
2016/11/04 PHP
利用PHP抓取百度阅读的方法示例
2016/12/18 PHP
javascript 面向对象编程基础:继承
2009/08/21 Javascript
JavaScript 对象的属性和方法4种不同的类型
2010/03/19 Javascript
js 点击页面其他地方关闭弹出层(示例代码)
2013/12/24 Javascript
node.js+Ajax实现获取HTTP服务器返回数据
2014/11/26 Javascript
jQuery前端分页示例分享
2015/02/10 Javascript
15款最好的Bootstrap在线编辑器
2016/08/03 Javascript
angularjs实现搜索的关键字在正文中高亮出来
2017/06/13 Javascript
nuxt框架中路由鉴权之Koa和Session的用法
2018/05/09 Javascript
关于jquery中attr()和prop()方法的区别
2018/05/28 jQuery
vue项目使用axios发送请求让ajax请求头部携带cookie的方法
2018/09/26 Javascript
vue路由--网站导航功能详解
2019/03/29 Javascript
发布订阅模式在vue中的实际运用实例详解
2019/06/09 Javascript
解决vue无法侦听数组及对象属性的变化问题
2020/07/17 Javascript
vue接口请求加密实例
2020/08/11 Javascript
详解 javascript对象创建模式
2020/10/30 Javascript
[29:59]完美世界DOTA2联赛PWL S3 Forest vs access 第二场 12.11
2020/12/13 DOTA
python获取局域网占带宽最大3个ip的方法
2015/07/09 Python
python开发利器之ulipad的使用实践
2017/03/16 Python
python编辑用户登入界面的实现代码
2018/07/16 Python
Python3 Tkinter选择路径功能的实现方法
2019/06/14 Python
python将字母转化为数字实例方法
2019/10/04 Python
python无序链表删除重复项的方法
2020/01/17 Python
利用Python将图片中扭曲矩形的复原
2020/09/07 Python
反四风个人对照检查材料思想汇报
2014/09/25 职场文书
个人四风问题原因分析及整改措施
2014/09/28 职场文书
办公室主任个人对照检查材料思想汇报
2014/10/11 职场文书
销售区域经理岗位职责
2015/04/10 职场文书
学校证明范文
2015/06/24 职场文书
《从现在开始》教学反思
2016/02/16 职场文书
导游词之云南丽江古城
2019/09/17 职场文书
MySQL创建定时任务
2022/01/22 MySQL
Java8 Stream API 提供了一种高效且易于使用的处理数据的方式
2022/04/13 Java/Android