跟我學習javascript的call(),apply(),bind()與回撥
一、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
語句先執行,因為回撥函式直到請求完成才執行。
相關文章
- Javascript - apply、call、bindJavaScriptAPP
- JavaScript 中的 apply、call、bindJavaScriptAPP
- JavaScript-apply、bind、callJavaScriptAPP
- javascript 物件導向學習(三)——this,bind、apply 和 callJavaScript物件APP
- JavaScript重識bind、call、applyJavaScriptAPP
- [譯] Javascript: call()、apply() 和 bind()JavaScriptAPP
- JavaScript中apply、call、bind的區別與用法JavaScriptAPP
- this指向與call,apply,bindAPP
- 前端戰五渣學JavaScript——call、apply以及bind前端JavaScriptAPP
- this、apply、call、bindAPP
- JavaScript中call,apply,bind方法的總結。JavaScriptAPP
- 淺談JavaScript中的apply、call和bindJavaScriptAPP
- JavaScript之call, apply, bind, new的實現JavaScriptAPP
- 談談JavaScript中的call、apply和bindJavaScriptAPP
- 【JavaScript】深入理解call,以及與apply、bind的區別JavaScriptAPP
- this與new、call、apply、bind的關係APP
- apply call bind的用法與實現APP
- apply,call,bind的用法APP
- javascript中call()、apply()、bind()的用法終於理解JavaScriptAPP
- this, call, apply 和 bindAPP
- JavaScript自我實現系列(2):call,apply,bindJavaScriptAPP
- [譯] 如何在 JavaScript 中使用 apply(?),call(?),bind(➰)JavaScriptAPP
- JS中的call、apply、bindJSAPP
- 手寫call,apply,bindAPP
- call、apply、bind 區別APP
- apply call bind 簡介APP
- call apply bind區別APP
- apply & call & bind 原始碼APP原始碼
- bind/call/apply 深度理解APP
- 手寫call、apply、bindAPP
- JavaScript進階之模擬call,apply和bindJavaScriptAPP
- bind、call、apply的區別與實現原理APP
- js call、apply、bind的實現JSAPP
- call,apply和bind的區別APP
- JavaScript—call, apply, bind 函式能幹啥?(全)(20)JavaScriptAPP函式
- 理解JS中的call、apply、bind方法(********************************************************JSAPP
- js中call、apply、bind的區別JSAPP
- call、apply、bind應用的介紹APP