跟我学习javascript的call(),apply(),bind()与回调


Posted in Javascript onNovember 16, 2015

一、call(),apply(),bind()方法

JavaScript 中通过call或者apply用来代替另一个对象调用一个方法,将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。简单的说就是改变函数执行的上下文,这是最基本的用法。两个方法基本区别在于传参不同。

call(obj,arg1,arg2,arg3); call第一个参数传对象,可以是null。参数以逗号分开进行传值,参数可以是任何类型。
apply(obj,[arg1,arg2,arg3]); apply第一个参数传对象,参数可以是数组或者arguments 对象。
1、语法
先来看看JS手册中对call的解释:

call 方法
调用一个对象的一个方法,以另一个对象替换当前对象。

call([thisObj[,arg1[, arg2[,   [,.argN]]]]])
参数

thisObj可选项。将被用作当前对象的对象。
 arg1, arg2,  , arg可选项。将被传递方法参数序列。
说明
call 方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。

如果没有提供 thisObj 参数,那么 Global 对象被用作 thisObj。
说明白一点其实就是更改对象的内部指针,即改变对象的this指向的内容。这在面向对象的js编程过程中有时是很有用的。

2、用法

因为function也是对象,所以每个函数都包含两个非继承而来的方法:apply()和call()。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内this 对象的值。首先,apply()方法接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是Array 的实例,也可以是arguments 对象。例如:

function sum(num1, num2){
 return num1 + num2;
}
function callSum1(num1, num2){
 return sum.apply(this, arguments); // 传入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 和一个参数数组。这两个函数都会正常执行并返回正确的结果。

在严格模式下,未指定环境对象而调用函数,则this 值不会转型为window。除非明确把函数添加到某个对象或者调用apply()或call(),否则this 值将是undefined

3、不同点

call()方法与apply()方法的作用相同,它们的区别仅在于接收参数的方式不同。对于call()方法而言,第一个参数是this 值没有变化,变化的是其余参数都直接传递给函数。换句话说,在使用call()方法时,传递给函数的参数必须逐个列举出来,如下面的例子所示。

function sum(num1, num2){
 return num1 + num2;
}
function callSum(num1, num2){
 return sum.call(this, num1, num2);
}
alert(callSum(10,10)); //20

在使用call()方法的情况下,callSum()必须明确地传入每一个参数。结果与使用apply()没有什么不同。至于是使用apply()还是call(),完全取决于你采取哪种给函数传递参数的方式最方便。如果你打算直接传入arguments 对象,或者包含函数中先接收到的也是一个数组,那么使用apply()肯定更方便;否则,选择call()可能更合适。(在不给函数传递参数的情况下,使用哪个方法都无所谓) 。

4、扩充函数运行的作用域

事实上,传递参数并非apply()和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

这个例子是在前面说明this 对象的示例基础上修改而成的。这一次,sayColor()也是作为全局函数定义的,而且当在全局作用域中调用它时,它确实会显示”red”——因为对this.color 的求值会转换成window.color 的求值。而sayColor.call(this)和sayColor.call(window),则是两种显式地在全局作用域中调用函数的方式,结果当然都会显示”red”。但是,当运行sayColor.call(o)时,函数的执行环境就不一样了,因为此时函数体内的this 对象指向了o,于是结果显示的是”blue”。使用call()(或apply())来扩充作用域的最大好处,就是对象不需要与方法有任何耦合关系。

在前面例子的第一个版本中,我们是先将sayColor()函数放到了对象o 中,然后再通过o 来调用它的;而在这里重写的例子中,就不需要先前那个多余的步骤了。

5、bind()方法

最后再来说 bind() 函数,上面讲的无论是 call() 也好, apply() 也好,都是立马就调用了对应的函数,而 bind() 不会, bind() 会生成一个新的函数,bind() 函数的参数跟 call() 一致,第一个参数也是绑定 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()函数。object-SayColor()函数的this 值等于o,因此即使是在全局作用域中调用这个函数,也会看到”blue”。

支持bind()方法的浏览器有IE9+、Firefox 4+、Safari 5.1+、Opera 12+和Chrome。

二、call(),apply()的继承和回调

类的继承

先来看这个例子:

function Person(name,age){
 this.name = name; 
 this.age=age; 
 this.alertName = function(){ 
 alert(this.name);

 }
 this.alertAge = function(){
 alert(this.age);
 }
}

function webDever(name,age,sex){
 Person.call(this,name,age); 
 this.sex=sex; 
 this.alertSex = function(){ 
 alert(this.sex); 
 }
}

var test= new webDever(“愚人码头”,28,”男”);

test.alertName();//愚人码头

test.alertAge();//28

test.alertSex();//男

这样 webDever类就继承Person类,Person.call(this,name,age) 的 意思就是使用 Person构造函数(也是函数)在this对象下执行,那么 webDever就有了Person的所有属性和方法,test对象就能够直接调用Person的方法以及属性了

用于回调
call 和 apply在回调行数中也非常有用,很多时候我们在开发过程中需要对改变回调函数的执行上下文,最常用的比如ajax或者定时什么的,一般情况下,Ajax都是全局的,也就是window对象下的,来看这个例子:

function Album(id, title, owner_id) {

 this.id = id;

 this.name = title;

 this.owner_id = owner_id;

};

Album.prototype.get_owner = function (callback) {

 var self = this;

 $.get(‘/owners/' + this.owner_id, function (data) {

 callback && callback.call(self, data.name);

 });

};

var album = new Album(1, ‘生活', 2);

album.get_owner(function (owner) {

 alert(‘The album' + this.name + ‘ belongs to ‘ + owner);

});

这里

album.get_owner(function (owner) {

 alert(‘The album' + this.name + ‘ belongs to ‘ + owner);

});

中的 this.name就能直接取到album对象中的name属性了。

三 、回调函数

说起回调函数,好多人虽然知道意思,但是还是一知半解。至于怎么用,还是有点糊涂。网上的一些相关的也没有详细的说一下是怎么回事,说的比较片面。下面我只是说说个人的一点理解,大牛勿喷。

定义
回调是什么?
看维基的 Callback_(computer_programming) 条目:

In computer programming, a callback is a reference to a piece of executable code that is passed as an argument to other code.

在JavaScript中,回调函数具体的定义为:函数A作为参数(函数引用)传递到另一个函数B中,并且这个函数B执行函数A。我们就说函数A叫做回调函数。如果没有名称(函数表达式),就叫做匿名回调函数。

举个例子:

你有事去隔壁寝室找同学,发现人不在,你怎么办呢?
    方法1,每隔几分钟再去趟隔壁寝室,看人在不
    方法2,拜托与他同寝室的人,看到他回来时叫一下你
前者是轮询,后者是回调。
那你说,我直接在隔壁寝室等到同学回来可以吗?
可以啊,只不过这样原本你可以省下时间做其他事,现在必须浪费在等待上了。
把原来的非阻塞的异步调用变成了阻塞的同步调用。
JavaScript的回调是在异步调用场景下使用的,使用回调性能好于轮询。
因此callback 不一定用于异步,一般同步(阻塞)的场景下也经常用到回调,比如要求执行某些操作后执行回调函数。

一个同步(阻塞)中使用回调的例子,目的是在func1代码执行完成后执行func2。

var func1=function(callback){
 //do something.
 (callback && typeof(callback) === "function") && callback();
}

func1(func2);
 var func2=function(){
}

异步回调的例子:

$(document).ready(callback);

$.ajax({
 url: "test.html",
 context: document.body
}).done(function() { 
 $(this).addClass("done");
}).fail(function() { alert("error");
}).always(function() { alert("complete"); 
});

回调什么时候执行

回调函数,一般在同步情境下是最后执行的,而在异步情境下有可能不执行,因为事件没有被触发或者条件不满足。另外,最好保证回调存在且必须是函数引用或者函数表达式:

(callback && typeof(callback) === "function") && callback();
我们来看一下一个粗略的一个定义“函数a有一个参数,这个参数是个函数b,当函数a执行完以后执行函数b。那么这个过程就叫回调。”,这句话的意思是函数b以一个参数的形式传入函数a并执行,顺序是先执行a ,然后执行参数b,b就是所谓的回调函数。我们先来看下面的例子。

function a(callback){
 alert('a');
 callback.call(this);//或者是 callback(), callback.apply(this),看个人喜好
 }
 function b(){
 alert('b');
 }
 //调用
 a(b);

这样的结果是先弹出 ‘a',再弹出‘b'。这样估计会有人问了“写这样的代码有什么意思呢?好像没太大的作用呢!”

是的,其实我也觉得这样写没啥意思,“如果调用一个函数就直接在函数里面调用它不就行了”。我这只是给大家写个小例子,做初步的理解。真正写代码的过程中很少用这样无参数的,因为在大部分场景中,我们要传递参数。来个带参数的:

function c(callback){
 alert('c');
 callback.call(this,'d');
 }
//调用
c(function(e){
 alert(e);
});

这个调用看起来是不是似曾相识,这里e参数被赋值为'd',我们只是简单的赋值为字符窜,其实也可以赋值为对象。Jquery里面是不是也有个e参数?

回调函数的使用场合

  • 资源加载:动态加载js文件后执行回调,加载iframe后执行回调,ajax操作回调,图片加载完成执行回调,AJAX等等。
  • DOM事件及Node.js事件基于回调机制(Node.js回调可能会出现多层回调嵌套的问题)。
  • setTimeout的延迟时间为0,这个hack经常被用到,settimeout调用的函数其实就是一个callback的体现
  • 链式调用:链式调用的时候,在赋值器(setter)方法中(或者本身没有返回值的方法中)很容易实现链式调用,而取值器(getter)相对来说不好实现链式调用,因为你需要取值器返回你需要的数据而不是this指针,如果要实现链式方法,可以用回调函数来实现
  • setTimeout、setInterval的函数调用得到其返回值。由于两个函数都是异步的,即:他们的调用时序和程序的主流程是相对独立的,所以没有办法在主体里面等待它们的返回值,它们被打开的时候程序也不会停下来等待,否则也就失去了setTimeout及setInterval的意义了,所以用return已经没有意义,只能使用callback。callback的意义在于将timer执行的结果通知给代理函数进行及时处理。

当函数的实现过程非常漫长,你是选择等待函数完成处理,还是使用回调函数进行异步处理呢?这种情况下,使用回调函数变得至关重要,例如:AJAX请求。若是使用回调函数进行处理,代码就可以继续进行其他任务,而无需空等。实际开发中,经常在javascript中使用异步调用,甚至在这里强烈推荐使用!

下面有个更加全面的使用AJAX加载XML文件的示例,并且使用了call()函数,在请求对象(requested object)上下文中调用回调函数。

function fn(url, callback){
 var httpRequest;
//创建XHR
 httpRequest = window.XMLHttpRequest ? new XMLHttpRequest() :
 


 window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : undefined;
 //针对IE进行功能性检测

 httpRequest.onreadystatechange = function(){

 if(httpRequest.readystate === 4 && httpRequest.status === 200){
//状态判断
  callback.call(httpRequest.responseXML); 

 }

 };

 httpRequest.open("GET", url);
 httpRequest.send();
}

fn("text.xml", function(){

//调用函数
 console.log(this); 
//此语句后输出
});
console.log("this will run before the above callback.");
//此语句先输出

我们请求异步处理,意味着我们开始请求时,就告诉它们完成之时调用我们的函数。在实际情况中,onreadystatechange事件处理程序还得考虑请求失败的情况,这里我们是假设xml文件存在并且能被浏览器成功加载。这个例子中,异步函数分配给了onreadystatechange事件,因此不会立刻执行。

最终,第二个console.log语句先执行,因为回调函数直到请求完成才执行。

以上就是本文的全部内容,希望对大家的学习有所帮助。

详细介绍请查看: 《详解JavaScript的回调函数》

Javascript 相关文章推荐
jQuery学习4 浏览器的事件模型
Feb 07 Javascript
jquery如何获取复选框的值
Dec 12 Javascript
JS获取select的value和text值的简单实例
Feb 26 Javascript
jquery中常用的函数和属性详细解析
Mar 07 Javascript
Node.js实现在目录中查找某个字符串及所在文件
Sep 03 Javascript
JavaScript中的类(Class)详细介绍
Dec 30 Javascript
js实现鼠标悬停图片上时滚动文字说明的方法
Feb 17 Javascript
jquery插件EasyUI中form表单提交实例分享
Jan 11 Javascript
详解ES6中的Map与Set集合
Mar 22 Javascript
微信小程序实现搜索历史功能
Mar 26 Javascript
Electron + vue 打包桌面操作流程详解
Jun 24 Javascript
JavaScript代码压缩工具UglifyJS和Google Closure Compiler的基本用法
Apr 13 Javascript
跟我学习javascript的函数调用和构造函数调用
Nov 16 #Javascript
12种JavaScript常用的MVC框架比较分析
Nov 16 #Javascript
跟我学习javascript的函数和函数表达式
Nov 16 #Javascript
使用JQuery FancyBox插件实现图片展示特效
Nov 16 #Javascript
uploadify多文件上传参数设置技巧
Nov 16 #Javascript
跟我学习javascript的var预解析与函数声明提升
Nov 16 #Javascript
跟我学习javascript的全局变量
Nov 16 #Javascript
You might like
PHP中SESSION使用中的一点经验总结
2012/03/30 PHP
php数组一对一替换实现代码
2012/08/31 PHP
PHP使用DOMDocument类生成HTML实例(包含常见标签元素)
2014/06/25 PHP
php调用淘宝开放API实现根据卖家昵称获取卖家店铺ID的方法
2015/07/29 PHP
THINKPHP在添加数据的时候获取主键id的值方法
2017/04/03 PHP
PHP 多任务秒级定时器的实现方法
2018/05/13 PHP
深入理解javaScript中的事件驱动
2013/05/21 Javascript
JavaScript将数字转换成大写中文的方法
2015/03/23 Javascript
javascript框架设计之浏览器的嗅探和特征侦测
2015/06/23 Javascript
详解JavaScript中的4种类型识别方法
2015/09/14 Javascript
Javascript页面跳转常见实现方式汇总
2015/11/28 Javascript
js+html5实现canvas绘制网页时钟的方法
2016/05/21 Javascript
jQuery事件处理的特征(事件命名机制)
2016/08/23 Javascript
Bootstrap中点击按钮后变灰并显示加载中实例代码
2016/09/23 Javascript
浅谈js对象属性 通过点(.) 和方括号([]) 的不同之处
2016/10/29 Javascript
JavaScript下拉菜单功能实例代码
2017/03/01 Javascript
详解基于Node.js的微信JS-SDK后端接口实现代码
2017/07/15 Javascript
AngularJS 打开新的标签页实现代码
2017/09/07 Javascript
js定时器+简单的动画效果实例
2017/11/10 Javascript
基于vue cli重构多页面脚手架过程详解
2018/01/23 Javascript
Vue 莹石摄像头直播视频实例代码
2018/08/31 Javascript
javascript实现贪吃蛇小游戏
2020/07/28 Javascript
巧用Python装饰器 免去调用父类构造函数的麻烦
2012/05/18 Python
Python判断字符串是否为字母或者数字(浮点数)的多种方法
2018/08/03 Python
如何在django中实现分页功能
2020/04/22 Python
全球速卖通法国在线交易平台:AliExpress法国
2017/07/07 全球购物
创造美妙香氛体验:Aera扩散器和香水
2018/11/25 全球购物
财务管理专业自荐信范文
2013/12/24 职场文书
构建高效课堂实施方案
2014/03/13 职场文书
机电专业求职信
2014/06/14 职场文书
幼儿园辞职信范文
2015/02/27 职场文书
2015最新婚礼司仪主持词
2015/06/30 职场文书
加强党性修养心得体会
2016/01/21 职场文书
护士年终工作总结不会写?各科护士模板总结
2020/01/02 职场文书
pytorch 如何使用batch训练lstm网络
2021/05/28 Python
防止web项目中的SQL注入
2021/12/06 MySQL