Javascript小學生都知道了javascript中的函數調用時會 隱性的接收兩個附加的參數:this和arguments。參數this在javascript編程中佔據中很是重要的地位,它的值取決於調用的模式。總的來講Javascript中函數一共有4中調用模式:方法調用模式、普通函數調用模式、構造器調用模式、apply/call調用模式。這些模式在如何初始化關鍵參數this上存在差別。「可能還有小夥伴不知道它們之間的區別,那我就勉爲其難擼一擼吧!」javascript
方法調用模式:函數是在某個明確的上下文對象中調用的,this綁定的是那個上下文對象。
html
普通函數調用模式:默認狀況下,若是函數是被直接調用的,若是在嚴格模式下,就綁定到undefined,不然綁定到全局對象。java
構造器調用模式:函數經過new操做符調用,this綁定的是新建立的對象。web
apply/call調用模式:函數經過apply或者call調用,this綁定的是指定的對象,若是把null或者undefined做爲this的綁定對象傳入call/apply,在調用時會被忽略,實際應用的是默認綁定規則。ajax
下面舉一個簡單的綜合例子:編程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
var
a=2;
function
foo(b) {
this
.b=b;
console.log(
this
.a);
}
var
obj={
a:4,
foo:foo
};
foo();
//普通函數調用,輸出2
obj.foo();
//做爲對象方法調用,輸出4
foo.call(obj);
//call顯示綁定,輸出4
foo.call(
null
);
//輸出2
var
bar=
new
foo(8);
//構造函數調用,輸出了undefined(由console.log(a)打印)
console.log(bar.b)
//輸出8
|
上面的例子在瀏覽器環境中已經測試經過了,在Node環境中在函數外面定義的變量不會成爲全局對象的屬性,理解這個例子的輸出結果對於上面提到的四種調用方式大概就理解了。在大多數狀況下,每次遇到函數調用(注意是每次,無論調用時這個函數位於哪裏,只要遇到調用這個函數就要停下來肯定裏面的this),只要仔細區分上面的四種調用模式,就能很快肯定函數中的this綁定的是哪一個對象。可是有一類狀況很特殊,你不能一眼或者兩眼就能看出函數調用的模式,那就是JavaScript中的異步函數調用。下面介紹幾種實際開發過程當中經常使用的異步函數調用中this綁定的例子。瀏覽器
超時調用須要使用 window 對象的 setTimeout() 方法,它接受兩個參數:要執行的代碼和以毫秒表示的時間(即在執行代碼前須要等待多少毫秒)。其中,第一個參數能夠是一個包含JavaScript代碼的字符串(就和在eval() 函數中使用的字符串同樣),也能夠是一個函數。setTimeout() 的第二個參數告訴 JavaScript 再過多長時間把當前任務添加到隊列中。若是隊列是空的,那麼添加的代碼會當即執行;若是隊列不是空的,那麼它就要等前面的代碼執行完了之後再執行。
下面對setTimeout()的兩次調用都會在一秒鐘後顯示一個警告框。
app
1
2
3
|
setTimeout(
function
() {
alert(
"Hello world!"
);
}, 1000);
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
var
a=5;
function
foo() {
this
.a++;
setTimeout(
function
(){
console.log(
this
.++a);
},1000);
}
var
obj={
a:2
};
foo.call(obj);
console.log(obj.a);
|
在瀏覽器環境測試,上述代碼的輸出結果是3 6,爲何會是3 和6呢,首先咱們知道超時函數的回調函數是異步的,因此先輸出的是最後一條語句執行的結果。foo.call(obj)語句經過call綁定obj,因此foo函數執行時內部的this綁定的是obj,因此this.a++使得obj的a屬性增長了1.接下來經過超時函數設置回調的匿名函數一秒後加入到任務隊列。因此在執行最後一條語句時,超時函數裏的回調函數尚未執行,因此最後一條語句輸出爲3,接下來當任務隊列裏的回調函數被調用執行時,輸出的是6,也就是全局變量a加1,所以超時調用的回調代碼都是在全局做用域中執行的,函數中的this的值指向全局對象,這裏補充說明一下在嚴格模式下this綁定的是undefined。異步
那麼間歇調用setInterval方法是什麼狀況呢。稍微小改一下上面的代碼:函數
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
var
a=5;
function
foo() {
this
.a++;
setInterval(
function
(){
console.log(++
this
.a);
},1000);
}
var
obj={
a:2
};
foo.call(obj);
console.log(obj.a);
|
上面的代碼輸出爲3 6 7 8 9·····
也就是說間歇調用和超時調用的狀況同樣,回調函數也是在全局環境中執行的。
(1)HTML事件處理程序
1
2
|
<!-- 輸出 "Click Me" -->
<
input
type
=
"button"
value
=
"Click Me"
onclick
=
"alert(this.value)"
>
|
因此你以爲你懂這個東東了,《JS高程》紅寶書中說,直接在HTML中添加事件處理會動態建立一個事件處理函數,執行這個函數時的this爲目標元素。那咱們看個例子,瞧瞧本身是否是真的懂了。
點擊一個div,讓div裏的文本從5每隔一秒遞減一直到0
<!doctype html><html lang="en"><head> <meta charset="UTF-8"> <title></title><script type="text/javascript"> function test(){ this.innerHTML='5'; var timer=setInterval(function(){ if (this.innerHTML==1) { clearInterval(timer); } this.innerHTML--; },1000) }</script></head><body><div onclick="test()">沐浴星光</div> </body></html>
<!doctype html><html lang="en"><head> <meta charset="UTF-8"> <title></title><script type="text/javascript"> function test(target){ target.innerHTML='5'; var timer=setInterval(function(){ if (target.innerHTML==1) { clearInterval(timer); } target.innerHTML--; },1000) }</script></head><body><div onclick="test(event.target)">沐浴星光</div> </body></html>
(2)DOM0 級事件處理程序
使用DOM0級方法指定的事件處理程序被認爲是元素的方法。所以,這時候的事件處理程序是在元素的做用域中運行;換句話說,程序中的 this 引用當前元素。來看一個例子。
1
2
3
4
|
var
btn = document.getElementById(
"myBtn"
);
btn.onclick =
function
(){
alert(
this
.id);
//"myBtn"
};
|
(3)DOM2 級事件處理程序
1
2
3
4
5
|
var
btn = document.getElementById(
"myBtn"
);
btn.addEventListener(
"click"
,
function
(){
alert(
this
.id);
//"myBtn"
},
false
);
|
1
2
3
4
|
var
btn = document.getElementById(
"myBtn"
);
btn.attachEvent(
"onclick"
,
function
(){
alert(
this
=== window);
//true
});
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
functionJSClass(){
this
.m_Text =
'division element'
;
this
.m_Element = document.createElement(
'div'
);
this
.m_Element.innerHTML =
this
.m_Text;
this
.m_Element.addEventListener(
'click'
,
this
.func);
// this.m_Element.onclick = this.func;
}
JSClass.prototype.Render=
function
(){
document.body.appendChild(
this
.m_Element);
}
JSClass.prototype.func =
function
(){
alert(
this
.m_Text);
};
var
jc =newJSClass();
jc.Render();
// add div
jc.func();
// 輸出 division element
|
click添加的div元素division element會輸出underfined,爲何?
答案:division element undefined
解析:第一次輸出很好理解,func()做爲對象的方法調用,因此輸出division element,點擊添加的元素時,this其實已經指向this.m_Element,也就是事件的目標元素(事件對象的currentTarget屬性值-或者說是註冊事件處理程序的元素),由於是this.m_Element調用的addEventListener函數,因此內部的this全指向它了,而這個元素並無m_Text屬性,因此輸出undefined。
1
2
3
4
5
|
<ul id=
"myLinks"
>
<li id=
"goSomewhere"
>Go somewhere</li>
<li id=
"doSomething"
>Do something</li>
<li id=
"sayHi"
>Say hi</li>
</ul>
|
1
2
3
4
5
6
7
8
9
10
11
12
|
var
item1 = document.getElementById(
"goSomewhere"
);
var
item2 = document.getElementById(
"doSomething"
);
var
item3 = document.getElementById(
"sayHi"
);
item1.addEventListener(
"click"
,
function
(event){
alert(
this
.id);
//"goSomewhere"
});
item2.addEventListener(
"click"
,
function
(event){
alert(
this
.id);
//"oSomething"
});
item3.addEventListener(
"click"
,
function
(event){
alert(
this
.id);
//"sayHi"
});
|
1
2
3
4
|
var
list=document.getElementById(
'"myLinks'
);
list.addEventListener(
'click'
,
function
(event){
alert(
this
.id);
})
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<!DOCTYPE html>
<
html
>
<
head
>
<
meta
charset
=
"utf-8"
/>
<
title
></
title
>
</
head
>
<
body
>
<
ul
id
=
"myLinks"
>
<
li
id
=
"goSomewhere"
>Go somewhere</
li
>
<
li
id
=
"doSomething"
>Do something</
li
>
<
li
id
=
"sayHi"
>Say hi</
li
>
</
ul
>
</
body
>
<
script
type
=
"text/javascript"
>
var list=document.getElementById('myLinks');
list.addEventListener('click',function(event){
alert(this.id);
})
</
script
>
</
html
>
|
測試結果
也就是說不論點擊哪個列表,彈出的是父元素的ID,那麼該怎麼改寫才能實現預期的功能呢?咱們知道事件對象event有不少屬性,其中包括兩個屬性currentTarget和target,在事件處理程序內部,對象this 始終等於currentTarget 的值(也就是添加事件處理程序的元素),而target則只包含事件的實際目標。若是直接將事件處理程序指定給了目標元素則 this、currentTarget 和target包含相同的值。若是事件處理程序是被委託代理的,那麼這些值通常不一樣。來看下面的例子。
1
2
3
4
5
6
|
var
list=document.getElementById(
'myLinks'
);
list.addEventListener(
'click'
,
function
(event){
alert(event.currentTarget===list)
//ture
alert(
this
===list)
//ture
});
|
這也解釋了上面錯誤的事件委託爲何一直彈出「myLinks」了。正確的事件委託程序是:
1
2
3
4
|
var
list=document.getElementById(
'myLinks'
);
list.addEventListener(
'click'
,
function
(event){
alert(event.target.id);
})
|
最後簡要說明一下ajax請求中的this
1
2
3
4
5
6
7
8
9
10
11
12
|
var
xhr =
new
XMLHttpRequest();
xhr.onreadystatechange =
function
(){
if
(xhr.readyState == 4){
if
((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
}
else
{
alert(
"Request was unsuccessful: "
+ xhr.status);
}
}
};
xhr.open(
"get"
,
"example.txt"
,
true
);
xhr.send(
null
);
|
這個例子在onreadystatechange事件處理程序中使用了xhr對象,沒有使用this對象,緣由是onreadystatechange事件處理程序的做用域問題。若是使用this對象,在有的瀏覽器中會致使函數執行失敗,或者致使錯誤發生。所以,使用實際的XHR對象實例變量是較爲可靠的一種方式。
參考:
《JavaScript高級程序設計》
《You Don't Konw JS:This&Object Prototypes》
《JavaScript語言精粹》