一、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, , argN
可选项。将被传递方法参数序列。
说明
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
语句先执行,因为回调函数直到请求完成才执行。