Home >Web Front-end >JS Tutorial >Follow me to learn javascript call(), apply(), bind() and callback_javascript skills

Follow me to learn javascript call(), apply(), bind() and callback_javascript skills

WBOY
WBOYOriginal
2016-05-16 15:32:161273browse

1. call(), apply(), bind() methods

In JavaScript, call or apply is used to call a method instead of another object, changing the object context of a function from the initial context to the new object specified by thisObj. Simply put, it is to change the context of function execution. This is the most basic usage. The basic difference between the two methods lies in the different parameters passed.

call(obj,arg1,arg2,arg3); The first parameter of call is an object, which can be null. Parameters are separated by commas and can be of any type.
apply(obj,[arg1,arg2,arg3]); The first parameter of apply is an object, and the parameter can be an array or arguments object.
1. Grammar
Let’s first take a look at the explanation of call in the JS manual:

call method
Call a method on an object to replace the current object with another object.

call([thisObj[,arg1[, arg2[, [,.argN]]]]])
Parameters

thisObj is optional. The object that will be used as the current object.
arg1, arg2, , arg optional. A sequence of method parameters will be passed.
Description
The call method can be used to call a method on behalf of another object. The call method changes the object context of a function from the initial context to the new object specified by thisObj.

If no thisObj parameter is provided, the Global object is used as thisObj.
To make it clear, it actually means changing the internal pointer of the object, that is, changing the content pointed to by this of the object. This is sometimes useful in object-oriented js programming.

2. Usage

Because function is also an object, each function contains two non-inherited methods: apply() and call(). The purpose of these two methods is to call a function in a specific scope, which is actually equivalent to setting the value of this object in the function body. First, the apply() method accepts two parameters: one is the scope in which the function is run, and the other is the parameter array. Among them, the second parameter can be an instance of Array or an arguments object. For example:

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

In the above example, callSum1() passes in this as the this value (because it is called in the global scope, so the window object is passed in) and the arguments object when executing the sum() function. And callSum2 also calls the sum() function, but it passes in this and a parameter array. Both functions will execute normally and return correct results.

In strict mode, if the function is called without specifying an environment object, the this value will not be converted to window. Unless the function is explicitly added to an object or apply() or call() is called, the value of this will be undefined

3. Differences

The call() method has the same function as the apply() method. The only difference lies in the way they receive parameters. For the call() method, the first parameter is this value, which does not change. What changes is that the remaining parameters are passed directly to the function. In other words, when using the call() method, the parameters passed to the function must be enumerated one by one, as shown in the example below.

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

When using the call() method, callSum() must explicitly pass in each parameter. The result is no different than using apply(). As for whether to use apply() or call(), it all depends on which method of passing parameters to the function is most convenient for you. If you plan to pass in the arguments object directly, or the first thing received in the containing function is also an array, then it is definitely more convenient to use apply(); otherwise, call() may be more appropriate. (Without passing parameters to the function, it doesn't matter which method is used).

4. Expand the scope of function operation

In fact, passing parameters is not the real use of apply() and call(); their real power is their ability to extend functions
The scope in which it operates. Let’s look at an example.

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的回调函数》

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn